From 7811ad0d2410afb56e7cfd8554b02b986f22e13a Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:30:49 +0600 Subject: [PATCH 001/352] docs: update info about config file (#6547) Co-authored-by: simar7 <1254783+simar7@users.noreply.github.com> --- .../references/configuration/cli/trivy_aws.md | 2 +- .../references/configuration/config-file.md | 224 +++++++++++++++++- pkg/flag/cloud_flags.go | 2 +- pkg/flag/kubernetes_flags.go | 18 +- pkg/flag/scan_flags.go | 2 +- 5 files changed, 225 insertions(+), 23 deletions(-) diff --git a/docs/docs/references/configuration/cli/trivy_aws.md b/docs/docs/references/configuration/cli/trivy_aws.md index b87bfce2bc30..0997f062b55e 100644 --- a/docs/docs/references/configuration/cli/trivy_aws.md +++ b/docs/docs/references/configuration/cli/trivy_aws.md @@ -87,7 +87,7 @@ trivy aws [flags] --ignorefile string specify .trivyignore file (default ".trivyignore") --include-non-failures include successes and exceptions, available with '--scanners misconfig' --list-all-pkgs enabling the option will output all packages regardless of vulnerability - --max-cache-age duration The maximum age of the cloud cache. Cached data will be requeried from the cloud provider if it is older than this. (default 24h0m0s) + --max-cache-age duration The maximum age of the cloud cache. Cached data will be required from the cloud provider if it is older than this. (default 24h0m0s) --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) -o, --output string output file name --output-plugin-arg string [EXPERIMENTAL] output plugin arguments diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index f649d2a213b6..755913a0bf20 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -81,6 +81,15 @@ severity: - MEDIUM - HIGH - CRITICAL + +scan: + # Same as '--compliance' + # Default is empty + compliance: + + # Same as '--show-suppressed' + # Default is false + show-suppressed: false ``` ## Scan Options @@ -106,7 +115,7 @@ scan: # Same as '--offline-scan' # Default is false - offline-scan: false + offline: false # Same as '--scanners' # Default depends on subcommand @@ -115,6 +124,24 @@ scan: - misconfig - secret - license + - + # Same as '--parallel' + # Default is 5 + parallel: 1 + + # Same as '--sbom-sources' + # Default is empty + sbom-sources: + - oci + - rekor + + # Same as '--rekor-url' + # Default is 'https://rekor.sigstore.dev' + rekor-url: https://rekor.sigstore.dev + + # Same as '--include-dev-deps' + # Default is false + include-dev-deps: false ``` ## Cache Options @@ -131,6 +158,9 @@ cache: # Redis options redis: + # Same as '--redis-tls' + # Default is false + tls: # Same as '--redis-ca' # Default is empty ca: @@ -148,21 +178,25 @@ cache: ```yaml db: - # Same as '--skip-db-update' - # Default is false - skip-update: false - # Same as '--no-progress' # Default is false no-progress: false + + # Same as '--skip-db-update' + # Default is false + skip-update: false # Same as '--db-repository' - # Default is 'ghcr.io/aquasecurity/trivy-db' - repository: ghcr.io/aquasecurity/trivy-db + # Default is 'ghcr.io/aquasecurity/trivy-db:2' + repository: ghcr.io/aquasecurity/trivy-db:2 + + # Same as '--skip-java-db-update' + # Default is false + java-skip-update: false # Same as '--java-db-repository' - # Default is 'ghcr.io/aquasecurity/trivy-java-db' - java-repository: ghcr.io/aquasecurity/trivy-java-db + # Default is 'ghcr.io/aquasecurity/trivy-java-db:1' + java-repository: ghcr.io/aquasecurity/trivy-java-db:1 ``` ## Registry Options @@ -197,7 +231,19 @@ image: # Same as '--platform' # Default is empty - platform: + platform: + + # Same as '--image-src' + # Default is 'docker,containerd,podman,remote' + source: + - podman + - docker + + # Same as '--image-config-scanners' + # Default is empty + image-config-scanners: + - misconfig + - secret docker: # Same as '--docker-host' @@ -224,6 +270,67 @@ vulnerability: # Same as '--ignore-unfixed' # Default is false ignore-unfixed: false + + # Same as '--ignore-unfixed' + # Default is empty + ignore-status: + - end_of_life +``` + +## License Options +Available with license scanning + +```yaml +license: + # Same as '--license-full' + # Default is false + full: false + + # Same as '--ignored-licenses' + # Default is empty + ignored: + - MPL-2.0 + - MIT + + # Same as '--license-confidence-level' + # Default is 0.9 + confidenceLevel: 0.9 + + # Set list of forbidden licenses + # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L171 + forbidden: + - AGPL-1.0 + - AGPL-3.0 + + # Set list of restricted licenses + # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L199 + restricted: + - AGPL-1.0 + - AGPL-3.0 + + # Set list of reciprocal licenses + # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L238 + reciprocal: + - AGPL-1.0 + - AGPL-3.0 + + # Set list of notice licenses + # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L260 + notice: + - AGPL-1.0 + - AGPL-3.0 + + # Set list of permissive licenses + # Default is empty + permissive: + - AGPL-1.0 + - AGPL-3.0 + + # Set list of unencumbered licenses + # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L334 + unencumbered: + - AGPL-1.0 + - AGPL-3.0 ``` ## Secret Options @@ -239,11 +346,15 @@ secret: ## Rego Options ```yaml -rego +rego: # Same as '--trace' # Default is false trace: false + # Same as '--skip-policy-update' + # Default is false + skip-policy-update: false + # Same as '--config-policy' # Default is empty policy: @@ -271,6 +382,10 @@ misconfiguration: # Same as '--include-non-failures' # Default is false include-non-failures: false + + # Same as '--policy-bundle-repository' + # Default is 'ghcr.io/aquasecurity/trivy-checks:0' + policy-bundle-repository: ghcr.io/aquasecurity/trivy-checks:0 # Same as '--miconfig-scanners' # Default is all scanners @@ -313,6 +428,12 @@ misconfiguration: # Same as '--tf-exclude-downloaded-modules' # Default is false exclude-downloaded-modules: false + + # Same as '--cf-params' + # Default is false + cloudformation: + params: + - params.json ``` ## Kubernetes Options @@ -327,6 +448,58 @@ kubernetes: # Same as '--namespace' # Default is empty namespace: + + # Same as '--kubeconfig' + # Default is empty + kubeconfig: ~/.kube/config2 + + # Same as '--components' + # Default is 'workload,infra' + components: + - workload + - infra + + # Same as '--k8s-version' + # Default is empty + k8s-version: 1.21.0 + + # Same as '--tolerations' + # Default is empty + tolerations: + - key1=value1:NoExecute + - key2=value2:NoSchedule + + # Same as '--all-namespaces' + # Default is false + all-namespaces: false + + node-collector: + # Same as '--node-collector-namespace' + # Default is 'trivy-temp' + namespace: ~/.kube/config2 + + # Same as '--node-collector-imageref' + # Default is 'ghcr.io/aquasecurity/node-collector:0.0.9' + imageref: ghcr.io/aquasecurity/node-collector:0.0.9 + + exclude: + # Same as '--exclude-owned' + # Default is false + owned: true + + # Same as '--exclude-nodes' + # Default is empty + nodes: + - kubernetes.io/arch:arm64 + - team:dev + + # Same as '--qps' + # Default is 5.0 + qps: 5.0 + + # Same as '--burst' + # Default is 10 + burst: 10 ``` ## Repository Options @@ -397,6 +570,35 @@ cloud: # the aws account to use (this will be determined from your environment when not set) account: 123456789012 + + # the aws specific services + service: + - s3 + - ec2 + + # the aws specific arn + arn: arn:aws:s3:::example-bucket + + # skip the aws specific services + skip-service: + - s3 + - ec2 +``` + +## Module Options +Available for modules + +```yaml +module: + # Same as '--module-dir' + # Default is '$HOME/.trivy/modules' + dir: $HOME/.trivy/modules + + # Same as '--enable-modules' + # Default is empty + enable-modules: + - trivy-module-spring4shell + - trivy-module-wordpress ``` [example]: https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/examples/trivy-conf/trivy.yaml diff --git a/pkg/flag/cloud_flags.go b/pkg/flag/cloud_flags.go index dfff5a997f7a..fd96c206d496 100644 --- a/pkg/flag/cloud_flags.go +++ b/pkg/flag/cloud_flags.go @@ -12,7 +12,7 @@ var ( Name: "max-cache-age", ConfigName: "cloud.max-cache-age", Default: time.Hour * 24, - Usage: "The maximum age of the cloud cache. Cached data will be requeried from the cloud provider if it is older than this.", + Usage: "The maximum age of the cloud cache. Cached data will be required from the cloud provider if it is older than this.", } ) diff --git a/pkg/flag/kubernetes_flags.go b/pkg/flag/kubernetes_flags.go index 7a87040ba698..a2f47ace08d9 100644 --- a/pkg/flag/kubernetes_flags.go +++ b/pkg/flag/kubernetes_flags.go @@ -44,7 +44,7 @@ var ( } K8sVersionFlag = Flag[string]{ Name: "k8s-version", - ConfigName: "kubernetes.k8s.version", + ConfigName: "kubernetes.k8s-version", Usage: "specify k8s version to validate outdated api by it (example: 1.21.0)", } TolerationsFlag = Flag[[]string]{ @@ -54,16 +54,22 @@ var ( } AllNamespaces = Flag[bool]{ Name: "all-namespaces", - ConfigName: "kubernetes.all.namespaces", + ConfigName: "kubernetes.all-namespaces", Shorthand: "A", Usage: "fetch resources from all cluster namespaces", } NodeCollectorNamespace = Flag[string]{ Name: "node-collector-namespace", - ConfigName: "node.collector.namespace", + ConfigName: "kubernetes.node-collector.namespace", Default: "trivy-temp", Usage: "specify the namespace in which the node-collector job should be deployed", } + NodeCollectorImageRef = Flag[string]{ + Name: "node-collector-imageref", + ConfigName: "kubernetes.node-collector.imageref", + Default: "ghcr.io/aquasecurity/node-collector:0.0.9", + Usage: "indicate the image reference for the node-collector scan job", + } ExcludeOwned = Flag[bool]{ Name: "exclude-owned", ConfigName: "kubernetes.exclude.owned", @@ -74,12 +80,6 @@ var ( ConfigName: "kubernetes.exclude.nodes", Usage: "indicate the node labels that the node-collector job should exclude from scanning (example: kubernetes.io/arch:arm64,team:dev)", } - NodeCollectorImageRef = Flag[string]{ - Name: "node-collector-imageref", - ConfigName: "kubernetes.node.collector.imageref", - Default: "ghcr.io/aquasecurity/node-collector:0.0.9", - Usage: "indicate the image reference for the node-collector scan job", - } QPS = Flag[float64]{ Name: "qps", ConfigName: "kubernetes.qps", diff --git a/pkg/flag/scan_flags.go b/pkg/flag/scan_flags.go index e2128816e849..102e16e2fdd4 100644 --- a/pkg/flag/scan_flags.go +++ b/pkg/flag/scan_flags.go @@ -98,7 +98,7 @@ var ( } IncludeDevDepsFlag = Flag[bool]{ Name: "include-dev-deps", - ConfigName: "include-dev-deps", + ConfigName: "scan.include-dev-deps", Usage: "include development dependencies in the report (supported: npm, yarn)", } ) From 9aca98cca87d037ad756a3dbe61931cd2ddf1fc0 Mon Sep 17 00:00:00 2001 From: Yaney <44939500+y4ney@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:15:29 +0800 Subject: [PATCH 002/352] fix(debian): sort dpkg info before parsing due to exclude directories (#6551) --- pkg/fanal/analyzer/pkg/dpkg/dpkg.go | 37 +++++++++++++++--------- pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go | 3 +- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go index d73c905fd413..a83592e82523 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go +++ b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go @@ -115,31 +115,42 @@ func (a dpkgAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis // parseDpkgInfoList parses /var/lib/dpkg/info/*.list func (a dpkgAnalyzer) parseDpkgInfoList(scanner *bufio.Scanner) ([]string, error) { - var installedFiles []string - var previous string + var ( + allLines []string + installedFiles []string + previous string + ) + for scanner.Scan() { current := scanner.Text() if current == "/." { continue } + allLines = append(allLines, current) + } + + if err := scanner.Err(); err != nil { + return nil, xerrors.Errorf("scan error: %w", err) + } - // Add the file if it is not directory. - // e.g. - // /usr/sbin - // /usr/sbin/tarcat - // - // In the above case, we should take only /usr/sbin/tarcat since /usr/sbin is a directory + // Add the file if it is not directory. + // e.g. + // /usr/sbin + // /usr/sbin/tarcat + // + // In the above case, we should take only /usr/sbin/tarcat since /usr/sbin is a directory + // sort first,see here:https://github.com/aquasecurity/trivy/discussions/6543 + sort.Strings(allLines) + for _, current := range allLines { if !strings.HasPrefix(current, previous+"/") { installedFiles = append(installedFiles, previous) } previous = current } - // Add the last file - installedFiles = append(installedFiles, previous) - - if err := scanner.Err(); err != nil { - return nil, xerrors.Errorf("scan error: %w", err) + // // Add the last file + if previous != "" && !strings.HasSuffix(previous, "/") { + installedFiles = append(installedFiles, previous) } return installedFiles, nil diff --git a/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go b/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go index 4dc627823200..c131b900d899 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go +++ b/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go @@ -1423,7 +1423,7 @@ func Test_dpkgAnalyzer_Analyze(t *testing.T) { want: &analyzer.AnalysisResult{ SystemInstalledFiles: []string{ "/bin/tar", - "/etc", + "/etc/rmt", "/usr/lib/mime/packages/tar", "/usr/sbin/rmt-tar", "/usr/sbin/tarcat", @@ -1436,7 +1436,6 @@ func Test_dpkgAnalyzer_Analyze(t *testing.T) { "/usr/share/man/man1/tar.1.gz", "/usr/share/man/man1/tarcat.1.gz", "/usr/share/man/man8/rmt-tar.8.gz", - "/etc/rmt", }, }, }, From 3d66cb8d887ead93255086736867f4a70060f32e Mon Sep 17 00:00:00 2001 From: zhaixiaojuan <67671683+zhaixiaojuan@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:44:24 +0800 Subject: [PATCH 003/352] chore: fix sqlite to support loong64 (#6511) --- go.mod | 22 ++++++++------------ go.sum | 65 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 41 insertions(+), 46 deletions(-) diff --git a/go.mod b/go.mod index dd1020151d77..463dccf98019 100644 --- a/go.mod +++ b/go.mod @@ -107,7 +107,7 @@ require ( go.etcd.io/bbolt v1.3.9 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa - golang.org/x/mod v0.15.0 + golang.org/x/mod v0.16.0 golang.org/x/net v0.23.0 golang.org/x/sync v0.6.0 golang.org/x/term v0.18.0 @@ -117,7 +117,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.29.1 k8s.io/utils v0.0.0-20231127182322-b307cd553661 - modernc.org/sqlite v1.28.0 + modernc.org/sqlite v1.29.7 ) require ( @@ -287,7 +287,6 @@ require ( github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20230406165453-00490a63f317 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect @@ -310,7 +309,6 @@ require ( github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.17.2 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect @@ -341,6 +339,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect @@ -397,9 +396,9 @@ require ( go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.17.0 // indirect + golang.org/x/tools v0.19.0 // indirect google.golang.org/api v0.155.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect @@ -420,14 +419,11 @@ require ( k8s.io/klog/v2 v2.120.0 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect k8s.io/kubectl v0.29.0 // indirect - lukechampine.com/uint128 v1.2.0 // indirect - modernc.org/cc/v3 v3.40.0 // indirect - modernc.org/ccgo/v3 v3.16.13 // indirect - modernc.org/libc v1.29.0 // indirect + modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect + modernc.org/libc v1.49.3 // indirect modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/opt v0.1.3 // indirect - modernc.org/strutil v1.1.3 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/strutil v1.2.0 // indirect modernc.org/token v1.1.0 // indirect oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/go.sum b/go.sum index c9ffe4225097..aa2ebaa46191 100644 --- a/go.sum +++ b/go.sum @@ -1010,8 +1010,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ= -github.com/google/pprof v0.0.0-20230406165453-00490a63f317/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= @@ -1170,8 +1170,6 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -1287,8 +1285,8 @@ github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vq github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 h1:TLygBUBxikNJJfLwgm+Qwdgq1FtfV8Uh7bcxRyTzK8s= @@ -1363,6 +1361,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -1827,8 +1827,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2069,8 +2069,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2169,8 +2169,9 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= 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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2519,34 +2520,32 @@ k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= -modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= -modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= -modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= -modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v1.29.0 h1:tTFRFq69YKCF2QyGNuRUQxKBm1uZZLubf6Cjh/pVHXs= -modernc.org/libc v1.29.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ= +modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk= +modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA= +modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg= +modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= -modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= -modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= -modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.29.7 h1:Puwf5TIYuOipbcRnpFnLlGlR03DKenw8ggf3ijnuNQ0= +modernc.org/sqlite v1.29.7/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= -modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From 5da053f3027593c1c6e2d9a8cee9f3dbe90b0e59 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Sat, 27 Apr 2024 11:08:47 +0400 Subject: [PATCH 004/352] docs: mention `--show-suppressed` is available in table (#6571) Signed-off-by: knqyf263 --- docs/docs/configuration/filtering.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/docs/configuration/filtering.md b/docs/docs/configuration/filtering.md index 75e880af4f49..965c2873c25e 100644 --- a/docs/docs/configuration/filtering.md +++ b/docs/docs/configuration/filtering.md @@ -237,6 +237,9 @@ You can filter the results by To show the suppressed results, use the `--show-suppressed` flag. +!!! note + This flag is currently available only in the table format. + ```bash $ trivy image --vex debian11.csaf.vex --ignorefile .trivyignore.yaml --show-suppressed debian:11 ... From a018ee1f9b94d82b8903e2a4084d6211ff8e6fcf Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Sat, 27 Apr 2024 14:40:32 +0600 Subject: [PATCH 005/352] ci: disable `Go` cache for `reusable-release.yaml` (#6572) --- .github/workflows/reusable-release.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/reusable-release.yaml b/.github/workflows/reusable-release.yaml index 752d48e5138e..1c52b70166e8 100644 --- a/.github/workflows/reusable-release.yaml +++ b/.github/workflows/reusable-release.yaml @@ -77,6 +77,7 @@ jobs: uses: actions/setup-go@v5 with: go-version-file: go.mod + cache: false # Disable cache to avoid free space issues during `Post Setup Go` step. - name: Generate SBOM uses: CycloneDX/gh-gomod-generate-sbom@v2 From 6343e4fc7112d0e8709d9ad4690b203509ee19ed Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Sat, 27 Apr 2024 13:15:12 +0400 Subject: [PATCH 006/352] feat: add relationships (#6563) Signed-off-by: knqyf263 --- .../testdata/composer.lock.json.golden | 2 + integration/testdata/conan.json.golden | 7 + integration/testdata/npm-with-dev.json.golden | 13 - integration/testdata/npm.json.golden | 12 - integration/testdata/nuget.json.golden | 2 + integration/testdata/poetry.json.golden | 3 + .../testdata/pom-cyclonedx.json.golden | 3 +- integration/testdata/pubspec.lock.json.golden | 2 + integration/testdata/yarn.json.golden | 1 + pkg/dependency/parser/c/conan/parse.go | 3 +- pkg/dependency/parser/c/conan/parse_test.go | 37 +- pkg/dependency/parser/dart/pub/parse.go | 23 +- pkg/dependency/parser/dart/pub/parse_test.go | 27 +- pkg/dependency/parser/golang/binary/parse.go | 14 +- .../parser/golang/binary/parse_test.go | 122 +- pkg/dependency/parser/golang/mod/parse.go | 6 +- .../parser/golang/mod/parse_testcase.go | 168 +-- .../parser/gradle/lockfile/parse.go | 5 +- .../parser/gradle/lockfile/parse_test.go | 21 +- pkg/dependency/parser/java/pom/artifact.go | 14 +- pkg/dependency/parser/java/pom/parse.go | 36 +- pkg/dependency/parser/java/pom/parse_test.go | 759 ++++++------ pkg/dependency/parser/java/pom/pom.go | 11 +- pkg/dependency/parser/nodejs/npm/parse.go | 16 +- .../parser/nodejs/npm/parse_testcase.go | 887 +++++++------- pkg/dependency/parser/nodejs/pnpm/parse.go | 13 +- .../parser/nodejs/pnpm/parse_testcase.go | 723 +++++++++-- pkg/dependency/parser/nuget/lock/parse.go | 9 +- .../parser/nuget/lock/parse_testcase.go | 1062 +++++++++++++++-- pkg/dependency/parser/php/composer/parse.go | 10 +- pkg/dependency/parser/ruby/bundler/parse.go | 10 +- .../parser/ruby/bundler/parse_test.go | 218 ++-- pkg/dependency/parser/rust/binary/parse.go | 9 +- .../parser/rust/binary/parse_test.go | 16 +- pkg/dependency/types/types.go | 61 +- pkg/fanal/analyzer/analyzer_test.go | 36 +- pkg/fanal/analyzer/language/analyze.go | 30 +- .../analyzer/language/c/conan/conan_test.go | 20 +- .../language/dart/pub/pubspec_test.go | 64 +- .../language/dotnet/nuget/nuget_test.go | 42 +- .../language/golang/binary/binary_test.go | 18 +- pkg/fanal/analyzer/language/golang/mod/mod.go | 5 +- .../analyzer/language/golang/mod/mod_test.go | 48 +- .../language/java/gradle/lockfile_test.go | 32 +- .../analyzer/language/java/pom/pom_test.go | 89 +- .../analyzer/language/nodejs/npm/npm_test.go | 29 +- .../language/nodejs/pnpm/pnpm_test.go | 7 +- .../analyzer/language/nodejs/yarn/yarn.go | 2 + .../language/nodejs/yarn/yarn_test.go | 334 +++--- .../language/php/composer/composer.go | 5 +- .../language/php/composer/composer_test.go | 66 +- .../analyzer/language/python/poetry/poetry.go | 5 +- .../language/python/poetry/poetry_test.go | 95 +- .../language/rust/binary/binary_test.go | 17 +- .../analyzer/language/rust/cargo/cargo.go | 2 + .../language/rust/cargo/cargo_test.go | 277 +++-- pkg/fanal/artifact/image/image_test.go | 562 +++++---- .../vuln-image1.2.3.expectedlibs.golden | 67 +- pkg/fanal/types/artifact.go | 15 +- pkg/report/github/github.go | 2 +- pkg/report/github/github_test.go | 16 +- pkg/report/table/vulnerability.go | 11 +- pkg/report/table/vulnerability_test.go | 72 +- pkg/sbom/io/encode.go | 58 +- pkg/sbom/io/encode_test.go | 241 +++- 65 files changed, 4349 insertions(+), 2243 deletions(-) diff --git a/integration/testdata/composer.lock.json.golden b/integration/testdata/composer.lock.json.golden index a3bdf13f9ece..b2a341f96e31 100644 --- a/integration/testdata/composer.lock.json.golden +++ b/integration/testdata/composer.lock.json.golden @@ -31,6 +31,7 @@ "Licenses": [ "MIT" ], + "Relationship": "direct", "DependsOn": [ "guzzlehttp/psr7@1.8.3" ], @@ -53,6 +54,7 @@ "MIT" ], "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { diff --git a/integration/testdata/conan.json.golden b/integration/testdata/conan.json.golden index ee60780c1d5f..4de31b4c3a87 100644 --- a/integration/testdata/conan.json.golden +++ b/integration/testdata/conan.json.golden @@ -29,6 +29,7 @@ }, "Version": "1.0.8", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -45,6 +46,7 @@ }, "Version": "2.4.8", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -61,6 +63,7 @@ }, "Version": "1.1.1q", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -77,6 +80,7 @@ }, "Version": "8.43", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "bzip2/1.0.8", "zlib/1.2.12" @@ -96,6 +100,7 @@ "PURL": "pkg:conan/poco@1.9.4" }, "Version": "1.9.4", + "Relationship": "direct", "DependsOn": [ "pcre/8.43", "zlib/1.2.12", @@ -119,6 +124,7 @@ }, "Version": "3.39.2", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -135,6 +141,7 @@ }, "Version": "1.2.12", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { diff --git a/integration/testdata/npm-with-dev.json.golden b/integration/testdata/npm-with-dev.json.golden index a512e5247910..fb1ecb091209 100644 --- a/integration/testdata/npm-with-dev.json.golden +++ b/integration/testdata/npm-with-dev.json.golden @@ -28,7 +28,6 @@ "PURL": "pkg:npm/asap@2.0.6" }, "Version": "2.0.6", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -47,7 +46,6 @@ "Licenses": [ "MIT" ], - "Indirect": true, "Layer": {}, "Locations": [ { @@ -63,7 +61,6 @@ "PURL": "pkg:npm/js-tokens@4.0.0" }, "Version": "4.0.0", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -79,7 +76,6 @@ "PURL": "pkg:npm/loose-envify@1.4.0" }, "Version": "1.4.0", - "Indirect": true, "DependsOn": [ "js-tokens@4.0.0" ], @@ -98,7 +94,6 @@ "PURL": "pkg:npm/object-assign@4.1.1" }, "Version": "4.1.1", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -117,7 +112,6 @@ "Licenses": [ "MIT" ], - "Indirect": true, "DependsOn": [ "asap@2.0.6" ], @@ -136,7 +130,6 @@ "PURL": "pkg:npm/prop-types@15.7.2" }, "Version": "15.7.2", - "Indirect": true, "DependsOn": [ "loose-envify@1.4.0", "object-assign@4.1.1", @@ -160,7 +153,6 @@ "Licenses": [ "MIT" ], - "Indirect": true, "DependsOn": [ "loose-envify@1.4.0", "object-assign@4.1.1", @@ -185,7 +177,6 @@ "Licenses": [ "MIT" ], - "Indirect": true, "Layer": {}, "Locations": [ { @@ -204,7 +195,6 @@ "Licenses": [ "MIT" ], - "Indirect": true, "DependsOn": [ "loose-envify@1.4.0", "symbol-observable@1.2.0" @@ -224,7 +214,6 @@ "PURL": "pkg:npm/scheduler@0.13.6" }, "Version": "0.13.6", - "Indirect": true, "DependsOn": [ "loose-envify@1.4.0", "object-assign@4.1.1" @@ -244,7 +233,6 @@ "PURL": "pkg:npm/symbol-observable@1.2.0" }, "Version": "1.2.0", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -264,7 +252,6 @@ "Licenses": [ "MIT" ], - "Indirect": true, "Layer": {}, "Locations": [ { diff --git a/integration/testdata/npm.json.golden b/integration/testdata/npm.json.golden index 9bdb9ae37649..a576da82c72e 100644 --- a/integration/testdata/npm.json.golden +++ b/integration/testdata/npm.json.golden @@ -28,7 +28,6 @@ "PURL": "pkg:npm/asap@2.0.6" }, "Version": "2.0.6", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -47,7 +46,6 @@ "Licenses": [ "MIT" ], - "Indirect": true, "Layer": {}, "Locations": [ { @@ -63,7 +61,6 @@ "PURL": "pkg:npm/js-tokens@4.0.0" }, "Version": "4.0.0", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -79,7 +76,6 @@ "PURL": "pkg:npm/loose-envify@1.4.0" }, "Version": "1.4.0", - "Indirect": true, "DependsOn": [ "js-tokens@4.0.0" ], @@ -98,7 +94,6 @@ "PURL": "pkg:npm/object-assign@4.1.1" }, "Version": "4.1.1", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -117,7 +112,6 @@ "Licenses": [ "MIT" ], - "Indirect": true, "DependsOn": [ "asap@2.0.6" ], @@ -136,7 +130,6 @@ "PURL": "pkg:npm/prop-types@15.7.2" }, "Version": "15.7.2", - "Indirect": true, "DependsOn": [ "loose-envify@1.4.0", "object-assign@4.1.1", @@ -160,7 +153,6 @@ "Licenses": [ "MIT" ], - "Indirect": true, "DependsOn": [ "loose-envify@1.4.0", "object-assign@4.1.1", @@ -185,7 +177,6 @@ "Licenses": [ "MIT" ], - "Indirect": true, "Layer": {}, "Locations": [ { @@ -204,7 +195,6 @@ "Licenses": [ "MIT" ], - "Indirect": true, "DependsOn": [ "loose-envify@1.4.0", "symbol-observable@1.2.0" @@ -224,7 +214,6 @@ "PURL": "pkg:npm/scheduler@0.13.6" }, "Version": "0.13.6", - "Indirect": true, "DependsOn": [ "loose-envify@1.4.0", "object-assign@4.1.1" @@ -244,7 +233,6 @@ "PURL": "pkg:npm/symbol-observable@1.2.0" }, "Version": "1.2.0", - "Indirect": true, "Layer": {}, "Locations": [ { diff --git a/integration/testdata/nuget.json.golden b/integration/testdata/nuget.json.golden index 064fb1e32d2f..6c5a2f19b9ac 100644 --- a/integration/testdata/nuget.json.golden +++ b/integration/testdata/nuget.json.golden @@ -28,6 +28,7 @@ "PURL": "pkg:nuget/Newtonsoft.Json@12.0.3" }, "Version": "12.0.3", + "Relationship": "direct", "Layer": {}, "Locations": [ { @@ -43,6 +44,7 @@ "PURL": "pkg:nuget/NuGet.Frameworks@5.7.0" }, "Version": "5.7.0", + "Relationship": "direct", "DependsOn": [ "Newtonsoft.Json@12.0.3" ], diff --git a/integration/testdata/poetry.json.golden b/integration/testdata/poetry.json.golden index 394977d3ea02..a17528965df9 100644 --- a/integration/testdata/poetry.json.golden +++ b/integration/testdata/poetry.json.golden @@ -28,6 +28,7 @@ "PURL": "pkg:pypi/click@8.1.3" }, "Version": "8.1.3", + "Relationship": "direct", "DependsOn": [ "colorama@0.4.6" ], @@ -41,6 +42,7 @@ }, "Version": "0.4.6", "Indirect": true, + "Relationship": "indirect", "Layer": {} }, { @@ -50,6 +52,7 @@ "PURL": "pkg:pypi/werkzeug@0.14" }, "Version": "0.14", + "Relationship": "direct", "Layer": {} } ], diff --git a/integration/testdata/pom-cyclonedx.json.golden b/integration/testdata/pom-cyclonedx.json.golden index 27f54a71848b..0baa2382d58c 100644 --- a/integration/testdata/pom-cyclonedx.json.golden +++ b/integration/testdata/pom-cyclonedx.json.golden @@ -91,8 +91,7 @@ { "ref": "3ff14136-e09f-4df9-80ea-000000000002", "dependsOn": [ - "pkg:maven/com.example/log4shell@1.0-SNAPSHOT", - "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1" + "pkg:maven/com.example/log4shell@1.0-SNAPSHOT" ] }, { diff --git a/integration/testdata/pubspec.lock.json.golden b/integration/testdata/pubspec.lock.json.golden index 7a101cd02eca..cd941a66652f 100644 --- a/integration/testdata/pubspec.lock.json.golden +++ b/integration/testdata/pubspec.lock.json.golden @@ -28,6 +28,7 @@ "PURL": "pkg:pub/http@0.13.2" }, "Version": "0.13.2", + "Relationship": "direct", "Layer": {} }, { @@ -38,6 +39,7 @@ }, "Version": "1.3.1", "Indirect": true, + "Relationship": "indirect", "Layer": {} } ], diff --git a/integration/testdata/yarn.json.golden b/integration/testdata/yarn.json.golden index 1e5cd3da24b3..452dcd0172a6 100644 --- a/integration/testdata/yarn.json.golden +++ b/integration/testdata/yarn.json.golden @@ -31,6 +31,7 @@ "Licenses": [ "MIT" ], + "Relationship": "direct", "Layer": {}, "Locations": [ { diff --git a/pkg/dependency/parser/c/conan/parse.go b/pkg/dependency/parser/c/conan/parse.go index 2020377a4663..f5417afd007f 100644 --- a/pkg/dependency/parser/c/conan/parse.go +++ b/pkg/dependency/parser/c/conan/parse.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/liamg/jfather" + "github.com/samber/lo" "golang.org/x/exp/slices" "golang.org/x/xerrors" @@ -70,7 +71,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // Determine if the package is a direct dependency or not direct := slices.Contains(directDeps, i) - lib.Indirect = !direct + lib.Relationship = lo.Ternary(direct, types.RelationshipDirect, types.RelationshipIndirect) parsed[i] = lib } diff --git a/pkg/dependency/parser/c/conan/parse_test.go b/pkg/dependency/parser/c/conan/parse_test.go index a22ec90afc1b..7502fde76271 100644 --- a/pkg/dependency/parser/c/conan/parse_test.go +++ b/pkg/dependency/parser/c/conan/parse_test.go @@ -25,9 +25,10 @@ func TestParse(t *testing.T) { inputFile: "testdata/happy.lock", wantLibs: []types.Library{ { - ID: "pkga/0.0.1", - Name: "pkga", - Version: "0.0.1", + ID: "pkga/0.0.1", + Name: "pkga", + Version: "0.0.1", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 13, @@ -36,10 +37,10 @@ func TestParse(t *testing.T) { }, }, { - ID: "pkgb/system", - Name: "pkgb", - Version: "system", - Indirect: true, + ID: "pkgb/system", + Name: "pkgb", + Version: "system", + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 23, @@ -48,9 +49,10 @@ func TestParse(t *testing.T) { }, }, { - ID: "pkgc/0.1.1", - Name: "pkgc", - Version: "0.1.1", + ID: "pkgc/0.1.1", + Name: "pkgc", + Version: "0.1.1", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 30, @@ -73,9 +75,10 @@ func TestParse(t *testing.T) { inputFile: "testdata/happy2.lock", wantLibs: []types.Library{ { - ID: "openssl/3.0.3", - Name: "openssl", - Version: "3.0.3", + ID: "openssl/3.0.3", + Name: "openssl", + Version: "3.0.3", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 12, @@ -84,10 +87,10 @@ func TestParse(t *testing.T) { }, }, { - ID: "zlib/1.2.12", - Name: "zlib", - Version: "1.2.12", - Indirect: true, + ID: "zlib/1.2.12", + Name: "zlib", + Version: "1.2.12", + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 23, diff --git a/pkg/dependency/parser/dart/pub/parse.go b/pkg/dependency/parser/dart/pub/parse.go index efd8d9aa5d41..4d38fd686252 100644 --- a/pkg/dependency/parser/dart/pub/parse.go +++ b/pkg/dependency/parser/dart/pub/parse.go @@ -11,7 +11,8 @@ import ( ) const ( - idFormat = "%s@%s" + directMain = "direct main" + directDev = "direct dev" transitiveDep = "transitive" ) @@ -31,7 +32,7 @@ type Dep struct { Version string `yaml:"version"` } -func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { l := &lock{} if err := yaml.NewDecoder(r).Decode(&l); err != nil { return nil, nil, xerrors.Errorf("failed to decode pubspec.lock: %w", err) @@ -44,13 +45,23 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, er // It will be confusing if we exclude direct dev dependencies and include transitive dev dependencies. // We decided to keep all dev dependencies until Pub will add support for "transitive main" and "transitive dev". lib := types.Library{ - ID: dependency.ID(ftypes.Pub, name, dep.Version), - Name: name, - Version: dep.Version, - Indirect: dep.Dependency == transitiveDep, + ID: dependency.ID(ftypes.Pub, name, dep.Version), + Name: name, + Version: dep.Version, + Relationship: p.relationship(dep.Dependency), } libs = append(libs, lib) } return libs, nil, nil } + +func (p Parser) relationship(dep string) types.Relationship { + switch dep { + case directMain, directDev: + return types.RelationshipDirect + case transitiveDep: + return types.RelationshipIndirect + } + return types.RelationshipUnknown +} diff --git a/pkg/dependency/parser/dart/pub/parse_test.go b/pkg/dependency/parser/dart/pub/parse_test.go index ef62ab971658..7c28355e5a92 100644 --- a/pkg/dependency/parser/dart/pub/parse_test.go +++ b/pkg/dependency/parser/dart/pub/parse_test.go @@ -25,20 +25,22 @@ func TestParser_Parse(t *testing.T) { inputFile: "testdata/happy.lock", want: []types.Library{ { - ID: "crypto@3.0.2", - Name: "crypto", - Version: "3.0.2", + ID: "crypto@3.0.2", + Name: "crypto", + Version: "3.0.2", + Relationship: types.RelationshipDirect, }, { - ID: "flutter_test@0.0.0", - Name: "flutter_test", - Version: "0.0.0", + ID: "flutter_test@0.0.0", + Name: "flutter_test", + Version: "0.0.0", + Relationship: types.RelationshipDirect, }, { - ID: "uuid@3.0.6", - Name: "uuid", - Version: "3.0.6", - Indirect: true, + ID: "uuid@3.0.6", + Name: "uuid", + Version: "3.0.6", + Relationship: types.RelationshipIndirect, }, }, wantErr: assert.NoError, @@ -66,10 +68,7 @@ func TestParser_Parse(t *testing.T) { return } - sort.Slice(gotLibs, func(i, j int) bool { - return gotLibs[i].ID < gotLibs[j].ID - }) - + sort.Sort(types.Libraries(gotLibs)) assert.Equal(t, tt.want, gotLibs) }) } diff --git a/pkg/dependency/parser/golang/binary/parse.go b/pkg/dependency/parser/golang/binary/parse.go index a47a74cdd28f..94fe3900b006 100644 --- a/pkg/dependency/parser/golang/binary/parse.go +++ b/pkg/dependency/parser/golang/binary/parse.go @@ -50,18 +50,20 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, libs := make([]types.Library, 0, len(info.Deps)+2) libs = append(libs, []types.Library{ - { - // Add the Go version used to build this binary. - Name: "stdlib", - Version: strings.TrimPrefix(info.GoVersion, "go"), - }, { // Add main module Name: info.Main.Path, // Only binaries installed with `go install` contain semver version of the main module. // Other binaries use the `(devel)` version. // See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477. - Version: p.checkVersion(info.Main.Path, info.Main.Version), + Version: p.checkVersion(info.Main.Path, info.Main.Version), + Relationship: types.RelationshipRoot, + }, + { + // Add the Go version used to build this binary. + Name: "stdlib", + Version: strings.TrimPrefix(info.GoVersion, "go"), + Relationship: types.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages. }, }...) diff --git a/pkg/dependency/parser/golang/binary/parse_test.go b/pkg/dependency/parser/golang/binary/parse_test.go index bd1430525244..14038194b2c4 100644 --- a/pkg/dependency/parser/golang/binary/parse_test.go +++ b/pkg/dependency/parser/golang/binary/parse_test.go @@ -12,6 +12,31 @@ import ( ) func TestParse(t *testing.T) { + wantLibs := []types.Library{ + { + Name: "github.com/aquasecurity/test", + Version: "", + Relationship: types.RelationshipRoot, + }, + { + Name: "stdlib", + Version: "1.15.2", + Relationship: types.RelationshipDirect, + }, + { + Name: "github.com/aquasecurity/go-pep440-version", + Version: "v0.0.0-20210121094942-22b2f8951d46", + }, + { + Name: "github.com/aquasecurity/go-version", + Version: "v0.0.0-20210121072130-637058cfe492", + }, + { + Name: "golang.org/x/xerrors", + Version: "v0.0.0-20200804184101-5ec99f83aff1", + }, + } + tests := []struct { name string inputFile string @@ -21,100 +46,39 @@ func TestParse(t *testing.T) { { name: "ELF", inputFile: "testdata/test.elf", - want: []types.Library{ - { - Name: "github.com/aquasecurity/go-pep440-version", - Version: "v0.0.0-20210121094942-22b2f8951d46", - }, - { - Name: "github.com/aquasecurity/go-version", - Version: "v0.0.0-20210121072130-637058cfe492", - }, - { - Name: "github.com/aquasecurity/test", - Version: "", - }, - { - Name: "golang.org/x/xerrors", - Version: "v0.0.0-20200804184101-5ec99f83aff1", - }, - { - Name: "stdlib", - Version: "1.15.2", - }, - }, + want: wantLibs, }, { name: "PE", inputFile: "testdata/test.exe", - want: []types.Library{ - { - Name: "github.com/aquasecurity/go-pep440-version", - Version: "v0.0.0-20210121094942-22b2f8951d46", - }, - { - Name: "github.com/aquasecurity/go-version", - Version: "v0.0.0-20210121072130-637058cfe492", - }, - { - Name: "github.com/aquasecurity/test", - Version: "", - }, - { - Name: "golang.org/x/xerrors", - Version: "v0.0.0-20200804184101-5ec99f83aff1", - }, - { - Name: "stdlib", - Version: "1.15.2", - }, - }, + want: wantLibs, }, { name: "Mach-O", inputFile: "testdata/test.macho", - want: []types.Library{ - { - Name: "github.com/aquasecurity/go-pep440-version", - Version: "v0.0.0-20210121094942-22b2f8951d46", - }, - { - Name: "github.com/aquasecurity/go-version", - Version: "v0.0.0-20210121072130-637058cfe492", - }, - { - Name: "github.com/aquasecurity/test", - Version: "", - }, - { - Name: "golang.org/x/xerrors", - Version: "v0.0.0-20200804184101-5ec99f83aff1", - }, - { - Name: "stdlib", - Version: "1.15.2", - }, - }, + want: wantLibs, }, { name: "with replace directive", inputFile: "testdata/replace.elf", want: []types.Library{ { - Name: "github.com/davecgh/go-spew", - Version: "v1.1.1", + Name: "github.com/ebati/trivy-mod-parse", + Version: "", + Relationship: types.RelationshipRoot, }, { - Name: "github.com/ebati/trivy-mod-parse", - Version: "", + Name: "stdlib", + Version: "1.16.4", + Relationship: types.RelationshipDirect, }, { - Name: "github.com/go-sql-driver/mysql", - Version: "v1.5.0", + Name: "github.com/davecgh/go-spew", + Version: "v1.1.1", }, { - Name: "stdlib", - Version: "1.16.4", + Name: "github.com/go-sql-driver/mysql", + Version: "v1.5.0", }, }, }, @@ -123,12 +87,14 @@ func TestParse(t *testing.T) { inputFile: "testdata/semver-main-module-version.macho", want: []types.Library{ { - Name: "go.etcd.io/bbolt", - Version: "v1.3.5", + Name: "go.etcd.io/bbolt", + Version: "v1.3.5", + Relationship: types.RelationshipRoot, }, { - Name: "stdlib", - Version: "1.20.6", + Name: "stdlib", + Version: "1.20.6", + Relationship: types.RelationshipDirect, }, }, }, diff --git a/pkg/dependency/parser/golang/mod/parse.go b/pkg/dependency/parser/golang/mod/parse.go index dded3d6527d3..b506128997ad 100644 --- a/pkg/dependency/parser/golang/mod/parse.go +++ b/pkg/dependency/parser/golang/mod/parse.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/samber/lo" "golang.org/x/exp/maps" "golang.org/x/mod/modfile" "golang.org/x/xerrors" @@ -84,6 +85,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, skipIndirect = lessThan117(modFileParsed.Go.Version) } + // Required modules for _, require := range modFileParsed.Require { // Skip indirect dependencies less than Go 1.17 if skipIndirect && require.Indirect { @@ -93,7 +95,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, ID: packageID(require.Mod.Path, require.Mod.Version[1:]), Name: require.Mod.Path, Version: require.Mod.Version[1:], - Indirect: require.Indirect, + Relationship: lo.Ternary(require.Indirect, types.RelationshipIndirect, types.RelationshipDirect), ExternalReferences: p.GetExternalRefs(require.Mod.Path), } } @@ -128,7 +130,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, ID: packageID(rep.New.Path, rep.New.Version[1:]), Name: rep.New.Path, Version: rep.New.Version[1:], - Indirect: old.Indirect, + Relationship: old.Relationship, ExternalReferences: p.GetExternalRefs(rep.New.Path), } } diff --git a/pkg/dependency/parser/golang/mod/parse_testcase.go b/pkg/dependency/parser/golang/mod/parse_testcase.go index 35a396e53c1c..4edeccc2b581 100644 --- a/pkg/dependency/parser/golang/mod/parse_testcase.go +++ b/pkg/dependency/parser/golang/mod/parse_testcase.go @@ -6,10 +6,10 @@ var ( // execute go mod tidy in normal folder GoModNormal = []types.Library{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20211224170007-df43bca6b6ff", - Indirect: false, + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -18,16 +18,16 @@ var ( }, }, { - ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", - Name: "golang.org/x/xerrors", - Version: "0.0.0-20200804184101-5ec99f83aff1", - Indirect: true, + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Relationship: types.RelationshipIndirect, }, { - ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", - Name: "gopkg.in/yaml.v3", - Version: "3.0.0-20210107192922-496545a6307b", - Indirect: true, + ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", + Name: "gopkg.in/yaml.v3", + Version: "3.0.0-20210107192922-496545a6307b", + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -40,10 +40,10 @@ var ( // execute go mod tidy in replaced folder GoModReplaced = []types.Library{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20220406074731-71021a481237", - Indirect: false, + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20220406074731-71021a481237", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -52,20 +52,20 @@ var ( }, }, { - ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", - Name: "golang.org/x/xerrors", - Version: "0.0.0-20200804184101-5ec99f83aff1", - Indirect: true, + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Relationship: types.RelationshipIndirect, }, } // execute go mod tidy in replaced folder GoModUnreplaced = []types.Library{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211110174639-8257534ffed3", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20211110174639-8257534ffed3", - Indirect: false, + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211110174639-8257534ffed3", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211110174639-8257534ffed3", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -74,20 +74,20 @@ var ( }, }, { - ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", - Name: "golang.org/x/xerrors", - Version: "0.0.0-20200804184101-5ec99f83aff1", - Indirect: true, + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Relationship: types.RelationshipIndirect, }, } // execute go mod tidy in replaced-with-version folder GoModReplacedWithVersion = []types.Library{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20220406074731-71021a481237", - Indirect: false, + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20220406074731-71021a481237", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -96,20 +96,20 @@ var ( }, }, { - ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", - Name: "golang.org/x/xerrors", - Version: "0.0.0-20200804184101-5ec99f83aff1", - Indirect: true, + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Relationship: types.RelationshipIndirect, }, } // execute go mod tidy in replaced-with-version-mismatch folder GoModReplacedWithVersionMismatch = []types.Library{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20211224170007-df43bca6b6ff", - Indirect: false, + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -118,16 +118,16 @@ var ( }, }, { - ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", - Name: "golang.org/x/xerrors", - Version: "0.0.0-20200804184101-5ec99f83aff1", - Indirect: true, + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Relationship: types.RelationshipIndirect, }, { - ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", - Name: "gopkg.in/yaml.v3", - Version: "3.0.0-20210107192922-496545a6307b", - Indirect: true, + ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", + Name: "gopkg.in/yaml.v3", + Version: "3.0.0-20210107192922-496545a6307b", + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -140,10 +140,10 @@ var ( // execute go mod tidy in replaced-with-local-path folder GoModReplacedWithLocalPath = []types.Library{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20211224170007-df43bca6b6ff", - Indirect: false, + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -152,10 +152,10 @@ var ( }, }, { - ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", - Name: "gopkg.in/yaml.v3", - Version: "3.0.0-20210107192922-496545a6307b", - Indirect: true, + ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", + Name: "gopkg.in/yaml.v3", + Version: "3.0.0-20210107192922-496545a6307b", + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -168,10 +168,10 @@ var ( // execute go mod tidy in replaced-with-local-path-and-version folder GoModReplacedWithLocalPathAndVersion = []types.Library{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20211224170007-df43bca6b6ff", - Indirect: false, + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -180,10 +180,10 @@ var ( }, }, { - ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", - Name: "gopkg.in/yaml.v3", - Version: "3.0.0-20210107192922-496545a6307b", - Indirect: true, + ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", + Name: "gopkg.in/yaml.v3", + Version: "3.0.0-20210107192922-496545a6307b", + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -196,10 +196,10 @@ var ( // execute go mod tidy in replaced-with-local-path-and-version-mismatch folder GoModReplacedWithLocalPathAndVersionMismatch = []types.Library{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20211224170007-df43bca6b6ff", - Indirect: false, + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -208,16 +208,16 @@ var ( }, }, { - ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", - Name: "golang.org/x/xerrors", - Version: "0.0.0-20200804184101-5ec99f83aff1", - Indirect: true, + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Relationship: types.RelationshipIndirect, }, { - ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", - Name: "gopkg.in/yaml.v3", - Version: "3.0.0-20210107192922-496545a6307b", - Indirect: true, + ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", + Name: "gopkg.in/yaml.v3", + Version: "3.0.0-20210107192922-496545a6307b", + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -230,10 +230,10 @@ var ( // execute go mod tidy in go116 folder GoMod116 = []types.Library{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20211224170007-df43bca6b6ff", - Indirect: false, + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, @@ -246,10 +246,10 @@ var ( // execute go mod tidy in no-go-version folder GoModNoGoVersion = []types.Library{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20211224170007-df43bca6b6ff", - Indirect: false, + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20211224170007-df43bca6b6ff", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefVCS, diff --git a/pkg/dependency/parser/gradle/lockfile/parse.go b/pkg/dependency/parser/gradle/lockfile/parse.go index 6d466570d2ff..47827faca036 100644 --- a/pkg/dependency/parser/gradle/lockfile/parse.go +++ b/pkg/dependency/parser/gradle/lockfile/parse.go @@ -46,10 +46,7 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, er EndLine: lineNum, }, }, - // There is no reliable way to determine direct dependencies (even using other files). - // Therefore, we mark all dependencies as Indirect. - // This is necessary to try to guess direct dependencies and build a dependency tree. - Indirect: true, + Relationship: types.RelationshipUnknown, }) } diff --git a/pkg/dependency/parser/gradle/lockfile/parse_test.go b/pkg/dependency/parser/gradle/lockfile/parse_test.go index 49cc7fe1c3a3..e9f76883e4e5 100644 --- a/pkg/dependency/parser/gradle/lockfile/parse_test.go +++ b/pkg/dependency/parser/gradle/lockfile/parse_test.go @@ -21,10 +21,9 @@ func TestParser_Parse(t *testing.T) { inputFile: "testdata/happy.lockfile", want: []types.Library{ { - ID: "cglib:cglib-nodep:2.1.2", - Name: "cglib:cglib-nodep", - Version: "2.1.2", - Indirect: true, + ID: "cglib:cglib-nodep:2.1.2", + Name: "cglib:cglib-nodep", + Version: "2.1.2", Locations: []types.Location{ { StartLine: 4, @@ -33,10 +32,9 @@ func TestParser_Parse(t *testing.T) { }, }, { - ID: "org.springframework:spring-asm:3.1.3.RELEASE", - Name: "org.springframework:spring-asm", - Version: "3.1.3.RELEASE", - Indirect: true, + ID: "org.springframework:spring-asm:3.1.3.RELEASE", + Name: "org.springframework:spring-asm", + Version: "3.1.3.RELEASE", Locations: []types.Location{ { StartLine: 5, @@ -45,10 +43,9 @@ func TestParser_Parse(t *testing.T) { }, }, { - ID: "org.springframework:spring-beans:5.0.5.RELEASE", - Name: "org.springframework:spring-beans", - Version: "5.0.5.RELEASE", - Indirect: true, + ID: "org.springframework:spring-beans:5.0.5.RELEASE", + Name: "org.springframework:spring-beans", + Version: "5.0.5.RELEASE", Locations: []types.Location{ { StartLine: 6, diff --git a/pkg/dependency/parser/java/pom/artifact.go b/pkg/dependency/parser/java/pom/artifact.go index 5f94dbfc3739..1e849aa6cab5 100644 --- a/pkg/dependency/parser/java/pom/artifact.go +++ b/pkg/dependency/parser/java/pom/artifact.go @@ -25,19 +25,19 @@ type artifact struct { Exclusions map[string]struct{} - Module bool - Root bool - Direct bool + Module bool + Relationship types.Relationship Locations types.Locations } func newArtifact(groupID, artifactID, version string, licenses []string, props map[string]string) artifact { return artifact{ - GroupID: evaluateVariable(groupID, props, nil), - ArtifactID: evaluateVariable(artifactID, props, nil), - Version: newVersion(evaluateVariable(version, props, nil)), - Licenses: licenses, + GroupID: evaluateVariable(groupID, props, nil), + ArtifactID: evaluateVariable(artifactID, props, nil), + Version: newVersion(evaluateVariable(version, props, nil)), + Licenses: licenses, + Relationship: types.RelationshipIndirect, // default } } diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index c4ee45c5024b..59551a0e98ff 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -117,7 +117,7 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty queue := newArtifactQueue() // Enqueue root POM - root.Root = true + root.Relationship = types.RelationshipRoot root.Module = false queue.enqueue(root) @@ -160,8 +160,8 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty } // mark artifact as Direct, if saved artifact is Direct // take a look `hard requirement for the specified version` test - if uniqueArt.Direct { - art.Direct = true + if uniqueArt.Relationship == types.RelationshipRoot || uniqueArt.Relationship == types.RelationshipDirect { + art.Relationship = uniqueArt.Relationship } // We don't need to overwrite dependency location for hard links if uniqueArt.Locations != nil { @@ -174,14 +174,13 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty return nil, nil, xerrors.Errorf("resolve error (%s): %w", art, err) } - if art.Root { + if art.Relationship == types.RelationshipRoot { // Managed dependencies in the root POM affect transitive dependencies rootDepManagement = p.resolveDepManagement(result.properties, result.dependencyManagement) - // mark root artifact and its dependencies as Direct - art.Direct = true + // mark its dependencies as "direct" result.dependencies = lo.Map(result.dependencies, func(dep artifact, _ int) artifact { - dep.Direct = true + dep.Relationship = types.RelationshipDirect return dep }) } @@ -205,11 +204,10 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty if !art.IsEmpty() { // Override the version uniqArtifacts[art.Name()] = artifact{ - Version: art.Version, - Licenses: result.artifact.Licenses, - Direct: art.Direct, - Root: art.Root, - Locations: art.Locations, + Version: art.Version, + Licenses: result.artifact.Licenses, + Relationship: art.Relationship, + Locations: art.Locations, } // save only dependency names @@ -224,12 +222,12 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty // Convert to []types.Library and []types.Dependency for name, art := range uniqArtifacts { lib := types.Library{ - ID: packageID(name, art.Version.String()), - Name: name, - Version: art.Version.String(), - License: art.JoinLicenses(), - Indirect: !art.Direct, - Locations: art.Locations, + ID: packageID(name, art.Version.String()), + Name: name, + Version: art.Version.String(), + License: art.JoinLicenses(), + Relationship: art.Relationship, + Locations: art.Locations, } libs = append(libs, lib) @@ -275,7 +273,7 @@ func (p *parser) parseModule(currentPath, relativePath string) (artifact, error) } moduleArtifact := module.artifact() - moduleArtifact.Module = true + moduleArtifact.Module = true // TODO: introduce RelationshipModule? p.cache.put(moduleArtifact, result) diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go index 3db282b84e22..7c14839ec3fa 100644 --- a/pkg/dependency/parser/java/pom/parse_test.go +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -30,16 +30,18 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:happy:1.0.0", - Name: "com.example:happy", - Version: "1.0.0", - License: "BSD-3-Clause", - }, - { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", + ID: "com.example:happy:1.0.0", + Name: "com.example:happy", + Version: "1.0.0", + License: "BSD-3-Clause", + Relationship: types.RelationshipRoot, + }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 32, @@ -48,9 +50,10 @@ func TestPom_Parse(t *testing.T) { }, }, { - ID: "org.example:example-runtime:1.0.0", - Name: "org.example:example-runtime", - Version: "1.0.0", + ID: "org.example:example-runtime:1.0.0", + Name: "org.example:example-runtime", + Version: "1.0.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 37, @@ -75,16 +78,18 @@ func TestPom_Parse(t *testing.T) { local: false, want: []types.Library{ { - ID: "com.example:happy:1.0.0", - Name: "com.example:happy", - Version: "1.0.0", - License: "BSD-3-Clause", + ID: "com.example:happy:1.0.0", + Name: "com.example:happy", + Version: "1.0.0", + License: "BSD-3-Clause", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 32, @@ -93,9 +98,10 @@ func TestPom_Parse(t *testing.T) { }, }, { - ID: "org.example:example-runtime:1.0.0", - Name: "org.example:example-runtime", - Version: "1.0.0", + ID: "org.example:example-runtime:1.0.0", + Name: "org.example:example-runtime", + Version: "1.0.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 37, @@ -120,14 +126,16 @@ func TestPom_Parse(t *testing.T) { local: false, want: []types.Library{ { - ID: "com.example:happy:1.0.0", - Name: "com.example:happy", - Version: "1.0.0", + ID: "com.example:happy:1.0.0", + Name: "com.example:happy", + Version: "1.0.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-dependency:1.2.3-SNAPSHOT", - Name: "org.example:example-dependency", - Version: "1.2.3-SNAPSHOT", + ID: "org.example:example-dependency:1.2.3-SNAPSHOT", + Name: "org.example:example-dependency", + Version: "1.2.3-SNAPSHOT", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 14, @@ -152,9 +160,10 @@ func TestPom_Parse(t *testing.T) { offline: true, want: []types.Library{ { - ID: "org.example:example-offline:2.3.4", - Name: "org.example:example-offline", - Version: "2.3.4", + ID: "org.example:example-offline:2.3.4", + Name: "org.example:example-offline", + Version: "2.3.4", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 17, @@ -170,16 +179,18 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:child:1.0.0", - Name: "com.example:child", - Version: "1.0.0", - License: "Apache 2.0", + ID: "com.example:child:1.0.0", + Name: "com.example:child", + Version: "1.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 33, @@ -203,15 +214,17 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:child:2.0.0", - Name: "com.example:child", - Version: "2.0.0", + ID: "com.example:child:2.0.0", + Name: "com.example:child", + Version: "2.0.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:2.0.0", - Name: "org.example:example-api", - Version: "2.0.0", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 18, @@ -235,15 +248,17 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:test:0.0.1-SNAPSHOT", - Name: "com.example:test", - Version: "0.0.1-SNAPSHOT", + ID: "com.example:test:0.0.1-SNAPSHOT", + Name: "com.example:test", + Version: "0.0.1-SNAPSHOT", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:2.0.0", - Name: "org.example:example-api", - Version: "2.0.0", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 18, @@ -267,20 +282,16 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:child:1.0.0", - Name: "com.example:child", - Version: "1.0.0", - }, - { - ID: "org.example:example-api:4.0.0", - Name: "org.example:example-api", - Version: "4.0.0", - Indirect: true, + ID: "com.example:child:1.0.0", + Name: "com.example:child", + Version: "1.0.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-dependency:1.2.3", - Name: "org.example:example-dependency", - Version: "1.2.3", + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 22, @@ -288,6 +299,12 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + ID: "org.example:example-api:4.0.0", + Name: "org.example:example-api", + Version: "4.0.0", + Relationship: types.RelationshipIndirect, + }, }, wantDeps: []types.Dependency{ { @@ -310,16 +327,18 @@ func TestPom_Parse(t *testing.T) { local: false, want: []types.Library{ { - ID: "com.example:child:1.0.0-SNAPSHOT", - Name: "com.example:child", - Version: "1.0.0-SNAPSHOT", - License: "Apache 2.0", + ID: "com.example:child:1.0.0-SNAPSHOT", + Name: "com.example:child", + Version: "1.0.0-SNAPSHOT", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, }, }, wantDeps: []types.Dependency{ @@ -337,16 +356,18 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:child:3.0.0", - Name: "com.example:child", - Version: "3.0.0", - License: "Apache 2.0", + ID: "com.example:child:3.0.0", + Name: "com.example:child", + Version: "3.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 26, @@ -370,23 +391,18 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:base:4.0.0", - Name: "com.example:base", - Version: "4.0.0", - License: "Apache 2.0", + ID: "com.example:base:4.0.0", + Name: "com.example:base", + Version: "4.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Indirect: true, - }, - { - ID: "org.example:example-child:2.0.0", - Name: "org.example:example-child", - Version: "2.0.0", - License: "Apache 2.0", + ID: "org.example:example-child:2.0.0", + Name: "org.example:example-child", + Version: "2.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 28, @@ -394,6 +410,13 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipIndirect, + }, }, wantDeps: []types.Dependency{ { @@ -416,16 +439,18 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:child:1.0.0", - Name: "com.example:child", - Version: "1.0.0", - License: "Apache 2.0", + ID: "com.example:child:1.0.0", + Name: "com.example:child", + Version: "1.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 26, @@ -449,14 +474,16 @@ func TestPom_Parse(t *testing.T) { local: false, want: []types.Library{ { - ID: "com.example:child:1.0.0-SNAPSHOT", - Name: "com.example:child", - Version: "1.0.0-SNAPSHOT", + ID: "com.example:child:1.0.0-SNAPSHOT", + Name: "com.example:child", + Version: "1.0.0-SNAPSHOT", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.1.1", - Name: "org.example:example-api", - Version: "1.1.1", + ID: "org.example:example-api:1.1.1", + Name: "org.example:example-api", + Version: "1.1.1", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 19, @@ -480,16 +507,18 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "org.example:child:1.0.0", - Name: "org.example:child", - Version: "1.0.0", - License: "Apache 2.0", + ID: "org.example:child:1.0.0", + Name: "org.example:child", + Version: "1.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 25, @@ -518,16 +547,18 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:soft:1.0.0", - Name: "com.example:soft", - Version: "1.0.0", - License: "Apache 2.0", + ID: "com.example:soft:1.0.0", + Name: "com.example:soft", + Version: "1.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 32, @@ -536,9 +567,10 @@ func TestPom_Parse(t *testing.T) { }, }, { - ID: "org.example:example-dependency:1.2.3", - Name: "org.example:example-dependency", - Version: "1.2.3", + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 37, @@ -575,21 +607,16 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:soft-transitive:1.0.0", - Name: "com.example:soft-transitive", - Version: "1.0.0", - }, - { - ID: "org.example:example-api:2.0.0", - Name: "org.example:example-api", - Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Indirect: true, + ID: "com.example:soft-transitive:1.0.0", + Name: "com.example:soft-transitive", + Version: "1.0.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-dependency2:2.3.4", - Name: "org.example:example-dependency2", - Version: "2.3.4", + ID: "org.example:example-dependency2:2.3.4", + Name: "org.example:example-dependency2", + Version: "2.3.4", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 18, @@ -598,9 +625,10 @@ func TestPom_Parse(t *testing.T) { }, }, { - ID: "org.example:example-dependency:1.2.3", - Name: "org.example:example-dependency", - Version: "1.2.3", + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 13, @@ -608,6 +636,13 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipIndirect, + }, }, wantDeps: []types.Dependency{ { @@ -643,22 +678,17 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:hard:1.0.0", - Name: "com.example:hard", - Version: "1.0.0", - License: "Apache 2.0", - }, - { - ID: "org.example:example-api:2.0.0", - Name: "org.example:example-api", - Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Indirect: true, + ID: "com.example:hard:1.0.0", + Name: "com.example:hard", + Version: "1.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-dependency:1.2.3", - Name: "org.example:example-dependency", - Version: "1.2.3", + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 33, @@ -667,9 +697,10 @@ func TestPom_Parse(t *testing.T) { }, }, { - ID: "org.example:example-nested:3.3.4", - Name: "org.example:example-nested", - Version: "3.3.4", + ID: "org.example:example-nested:3.3.4", + Name: "org.example:example-nested", + Version: "3.3.4", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 28, @@ -677,6 +708,13 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipIndirect, + }, }, wantDeps: []types.Dependency{ { @@ -706,10 +744,11 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:hard:1.0.0", - Name: "com.example:hard", - Version: "1.0.0", - License: "Apache 2.0", + ID: "com.example:hard:1.0.0", + Name: "com.example:hard", + Version: "1.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, }, }, @@ -719,16 +758,18 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:import:2.0.0", - Name: "com.example:import", - Version: "2.0.0", - License: "Apache 2.0", + ID: "com.example:import:2.0.0", + Name: "com.example:import", + Version: "2.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 34, @@ -752,16 +793,18 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:import:2.0.0", - Name: "com.example:import", - Version: "2.0.0", - License: "Apache 2.0", + ID: "com.example:import:2.0.0", + Name: "com.example:import", + Version: "2.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 42, @@ -785,20 +828,16 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:exclusions:3.0.0", - Name: "com.example:exclusions", - Version: "3.0.0", + ID: "com.example:exclusions:3.0.0", + Name: "com.example:exclusions", + Version: "3.0.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-dependency:1.2.3", - Name: "org.example:example-dependency", - Version: "1.2.3", - Indirect: true, - }, - { - ID: "org.example:example-nested:3.3.3", - Name: "org.example:example-nested", - Version: "3.3.3", + ID: "org.example:example-nested:3.3.3", + Name: "org.example:example-nested", + Version: "3.3.3", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 14, @@ -806,6 +845,12 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Relationship: types.RelationshipIndirect, + }, }, wantDeps: []types.Dependency{ { @@ -828,27 +873,16 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:example:1.0.0", - Name: "com.example:example", - Version: "1.0.0", - }, - { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - Indirect: true, - License: "The Apache Software License, Version 2.0", + ID: "com.example:example:1.0.0", + Name: "com.example:example", + Version: "1.0.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-dependency:1.2.3", - Name: "org.example:example-dependency", - Version: "1.2.3", - Indirect: true, - }, - { - ID: "org.example:example-exclusions:4.0.0", - Name: "org.example:example-exclusions", - Version: "4.0.0", + ID: "org.example:example-exclusions:4.0.0", + Name: "org.example:example-exclusions", + Version: "4.0.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 10, @@ -856,6 +890,19 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Relationship: types.RelationshipIndirect, + }, }, wantDeps: []types.Dependency{ { @@ -879,14 +926,16 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:wildcard-exclusions:4.0.0", - Name: "com.example:wildcard-exclusions", - Version: "4.0.0", + ID: "com.example:wildcard-exclusions:4.0.0", + Name: "com.example:wildcard-exclusions", + Version: "4.0.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-dependency2:2.3.4", - Name: "org.example:example-dependency2", - Version: "2.3.4", + ID: "org.example:example-dependency2:2.3.4", + Name: "org.example:example-dependency2", + Version: "2.3.4", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 25, @@ -895,9 +944,10 @@ func TestPom_Parse(t *testing.T) { }, }, { - ID: "org.example:example-dependency:1.2.3", - Name: "org.example:example-dependency", - Version: "1.2.3", + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 14, @@ -906,9 +956,10 @@ func TestPom_Parse(t *testing.T) { }, }, { - ID: "org.example:example-nested:3.3.3", - Name: "org.example:example-nested", - Version: "3.3.3", + ID: "org.example:example-nested:3.3.3", + Name: "org.example:example-nested", + Version: "3.3.3", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 36, @@ -934,28 +985,31 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:aggregation:1.0.0", - Name: "com.example:aggregation", - Version: "1.0.0", - License: "Apache 2.0", + ID: "com.example:aggregation:1.0.0", + Name: "com.example:aggregation", + Version: "1.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "com.example:module:1.1.1", - Name: "com.example:module", - Version: "1.1.1", - License: "Apache 2.0", + ID: "com.example:module:1.1.1", + Name: "com.example:module", + Version: "1.1.1", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, // TODO: Several root modules break SBOM relationships }, { - ID: "org.example:example-api:2.0.0", - Name: "org.example:example-api", - Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Indirect: true, + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Relationship: types.RelationshipDirect, }, { - ID: "org.example:example-dependency:1.2.3", - Name: "org.example:example-dependency", - Version: "1.2.3", + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipIndirect, }, }, // maven doesn't include modules in dep tree of root pom @@ -997,25 +1051,29 @@ func TestPom_Parse(t *testing.T) { want: []types.Library{ // as module { - ID: "org.example:module-1:2.0.0", - Name: "org.example:module-1", - Version: "2.0.0", + ID: "org.example:module-1:2.0.0", + Name: "org.example:module-1", + Version: "2.0.0", + Relationship: types.RelationshipRoot, }, - // as dependency { - ID: "org.example:module-1:2.0.0", - Name: "org.example:module-1", - Version: "2.0.0", + ID: "org.example:module-2:3.0.0", + Name: "org.example:module-2", + Version: "3.0.0", + Relationship: types.RelationshipRoot, // TODO: Several root modules break SBOM relationships }, { - ID: "org.example:module-2:3.0.0", - Name: "org.example:module-2", - Version: "3.0.0", + ID: "org.example:root:1.0.0", + Name: "org.example:root", + Version: "1.0.0", + Relationship: types.RelationshipRoot, // TODO: Several root modules break SBOM relationships }, + // as dependency { - ID: "org.example:root:1.0.0", - Name: "org.example:root", - Version: "1.0.0", + ID: "org.example:module-1:2.0.0", + Name: "org.example:module-1", + Version: "2.0.0", + Relationship: types.RelationshipDirect, }, }, wantDeps: []types.Dependency{ @@ -1033,31 +1091,36 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:aggregation:1.0.0", - Name: "com.example:aggregation", - Version: "1.0.0", + ID: "com.example:aggregation:1.0.0", + Name: "com.example:aggregation", + Version: "1.0.0", + Relationship: types.RelationshipRoot, }, { - ID: "com.example:module1:1.1.1", - Name: "com.example:module1", - Version: "1.1.1", + ID: "com.example:module1:1.1.1", + Name: "com.example:module1", + Version: "1.1.1", + Relationship: types.RelationshipRoot, }, { - ID: "com.example:module2:1.1.1", - Name: "com.example:module2", - Version: "1.1.1", + ID: "com.example:module2:1.1.1", + Name: "com.example:module2", + Version: "1.1.1", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, }, { - ID: "org.example:example-api:2.0.0", - Name: "org.example:example-api", - Version: "2.0.0", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, }, }, wantDeps: []types.Dependency{ @@ -1081,29 +1144,16 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:root-pom-dep-management:1.0.0", - Name: "com.example:root-pom-dep-management", - Version: "1.0.0", - }, - { - ID: "org.example:example-api:2.0.0", - Name: "org.example:example-api", - Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Indirect: true, - }, - // dependency version is taken from `com.example:root-pom-dep-management` from dependencyManagement - // not from `com.example:example-nested` from `com.example:example-nested` - { - ID: "org.example:example-dependency:1.2.4", - Name: "org.example:example-dependency", - Version: "1.2.4", - Indirect: true, + ID: "com.example:root-pom-dep-management:1.0.0", + Name: "com.example:root-pom-dep-management", + Version: "1.0.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-nested:3.3.3", - Name: "org.example:example-nested", - Version: "3.3.3", + ID: "org.example:example-nested:3.3.3", + Name: "org.example:example-nested", + Version: "3.3.3", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 20, @@ -1111,6 +1161,21 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipIndirect, + }, + // dependency version is taken from `com.example:root-pom-dep-management` from dependencyManagement + // not from `com.example:example-nested` from `com.example:example-nested` + { + ID: "org.example:example-dependency:1.2.4", + Name: "org.example:example-dependency", + Version: "1.2.4", + Relationship: types.RelationshipIndirect, + }, }, wantDeps: []types.Dependency{ { @@ -1138,19 +1203,17 @@ func TestPom_Parse(t *testing.T) { inputFile: filepath.Join("testdata", "transitive-dependency-management", "pom.xml"), local: true, want: []types.Library{ - // Managed dependencies (org.example:example-api:1.7.30) in org.example:example-dependency-management3 - // should not affect dependencies of example-dependency (org.example:example-api:2.0.0) { - ID: "org.example:example-api:2.0.0", - Name: "org.example:example-api", - Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Indirect: true, + ID: "org.example:transitive-dependency-management:2.0.0", + Name: "org.example:transitive-dependency-management", + Version: "2.0.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-dependency-management3:1.1.1", - Name: "org.example:example-dependency-management3", - Version: "1.1.1", + ID: "org.example:example-dependency-management3:1.1.1", + Name: "org.example:example-dependency-management3", + Version: "1.1.1", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 14, @@ -1158,16 +1221,20 @@ func TestPom_Parse(t *testing.T) { }, }, }, + // Managed dependencies (org.example:example-api:1.7.30) in org.example:example-dependency-management3 + // should not affect dependencies of example-dependency (org.example:example-api:2.0.0) { - ID: "org.example:example-dependency:1.2.3", - Name: "org.example:example-dependency", - Version: "1.2.3", - Indirect: true, + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipIndirect, }, { - ID: "org.example:transitive-dependency-management:2.0.0", - Name: "org.example:transitive-dependency-management", - Version: "2.0.0", + ID: "org.example:example-dependency:1.2.3", + Name: "org.example:example-dependency", + Version: "1.2.3", + Relationship: types.RelationshipIndirect, }, }, wantDeps: []types.Dependency{ @@ -1197,16 +1264,18 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:no-parent:1.0-SNAPSHOT", - Name: "com.example:no-parent", - Version: "1.0-SNAPSHOT", - License: "Apache 2.0", + ID: "com.example:no-parent:1.0-SNAPSHOT", + Name: "com.example:no-parent", + Version: "1.0-SNAPSHOT", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-api:1.7.30", - Name: "org.example:example-api", - Version: "1.7.30", - License: "The Apache Software License, Version 2.0", + ID: "org.example:example-api:1.7.30", + Name: "org.example:example-api", + Version: "1.7.30", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 27, @@ -1230,15 +1299,17 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:not-found-dependency:1.0.0", - Name: "com.example:not-found-dependency", - Version: "1.0.0", - License: "Apache 2.0", + ID: "com.example:not-found-dependency:1.0.0", + Name: "com.example:not-found-dependency", + Version: "1.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, { - ID: "org.example:example-not-found:999", - Name: "org.example:example-not-found", - Version: "999", + ID: "org.example:example-not-found:999", + Name: "org.example:example-not-found", + Version: "999", + Relationship: types.RelationshipDirect, Locations: types.Locations{ { StartLine: 21, @@ -1262,10 +1333,11 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:aggregation:1.0.0", - Name: "com.example:aggregation", - Version: "1.0.0", - License: "Apache 2.0", + ID: "com.example:aggregation:1.0.0", + Name: "com.example:aggregation", + Version: "1.0.0", + License: "Apache 2.0", + Relationship: types.RelationshipRoot, }, }, }, @@ -1275,10 +1347,11 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:multiply-licenses:1.0.0", - Name: "com.example:multiply-licenses", - Version: "1.0.0", - License: "MIT, Apache 2.0", + ID: "com.example:multiply-licenses:1.0.0", + Name: "com.example:multiply-licenses", + Version: "1.0.0", + License: "MIT, Apache 2.0", + Relationship: types.RelationshipRoot, }, }, }, @@ -1288,10 +1361,11 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example.app:submodule:1.0.0", - Name: "com.example.app:submodule", - Version: "1.0.0", - License: "Apache-2.0", + ID: "com.example.app:submodule:1.0.0", + Name: "com.example.app:submodule", + Version: "1.0.0", + License: "Apache-2.0", + Relationship: types.RelationshipRoot, }, }, }, @@ -1301,10 +1375,11 @@ func TestPom_Parse(t *testing.T) { local: true, want: []types.Library{ { - ID: "com.example:child:1.0.0", - Name: "com.example:child", - Version: "1.0.0", - License: "The Apache Software License, Version 2.0", + ID: "com.example:child:1.0.0", + Name: "com.example:child", + Version: "1.0.0", + License: "The Apache Software License, Version 2.0", + Relationship: types.RelationshipRoot, }, }, }, diff --git a/pkg/dependency/parser/java/pom/pom.go b/pkg/dependency/parser/java/pom/pom.go index a994301ea75f..7ae73fd6edd9 100644 --- a/pkg/dependency/parser/java/pom/pom.go +++ b/pkg/dependency/parser/java/pom/pom.go @@ -297,11 +297,12 @@ func (d pomDependency) ToArtifact(opts analysisOptions) artifact { } return artifact{ - GroupID: d.GroupID, - ArtifactID: d.ArtifactID, - Version: newVersion(d.Version), - Exclusions: exclusions, - Locations: locations, + GroupID: d.GroupID, + ArtifactID: d.ArtifactID, + Version: newVersion(d.Version), + Exclusions: exclusions, + Locations: locations, + Relationship: types.RelationshipIndirect, // default } } diff --git a/pkg/dependency/parser/nodejs/npm/parse.go b/pkg/dependency/parser/nodejs/npm/parse.go index 46b62c609502..e98bf25bba6d 100644 --- a/pkg/dependency/parser/nodejs/npm/parse.go +++ b/pkg/dependency/parser/nodejs/npm/parse.go @@ -135,7 +135,9 @@ func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types. // we need to add location for each these dependencies if savedLib, ok := libs[pkgID]; ok { savedLib.Dev = savedLib.Dev && pkg.Dev - savedLib.Indirect = savedLib.Indirect && pkgIndirect + if savedLib.Relationship == types.RelationshipIndirect && !pkgIndirect { + savedLib.Relationship = types.RelationshipDirect + } if ref.URL != "" && !slices.Contains(savedLib.ExternalReferences, ref) { savedLib.ExternalReferences = append(savedLib.ExternalReferences, ref) @@ -153,7 +155,7 @@ func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types. ID: pkgID, Name: pkgName, Version: pkg.Version, - Indirect: pkgIndirect, + Relationship: lo.Ternary(pkgIndirect, types.RelationshipIndirect, types.RelationshipDirect), Dev: pkg.Dev, ExternalReferences: lo.Ternary(ref.URL != "", []types.ExternalRef{ref}, nil), Locations: []types.Location{location}, @@ -280,11 +282,11 @@ func (p *Parser) parseV1(dependencies map[string]Dependency, versions map[string var deps []types.Dependency for pkgName, dep := range dependencies { lib := types.Library{ - ID: packageID(pkgName, dep.Version), - Name: pkgName, - Version: dep.Version, - Dev: dep.Dev, - Indirect: true, // lockfile v1 schema doesn't have information about Direct dependencies + ID: packageID(pkgName, dep.Version), + Name: pkgName, + Version: dep.Version, + Dev: dep.Dev, + Relationship: types.RelationshipUnknown, // lockfile v1 schema doesn't have information about direct dependencies ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, diff --git a/pkg/dependency/parser/nodejs/npm/parse_testcase.go b/pkg/dependency/parser/nodejs/npm/parse_testcase.go index 29b9e63d8f1c..d5f87cd33cc0 100644 --- a/pkg/dependency/parser/nodejs/npm/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/npm/parse_testcase.go @@ -14,11 +14,11 @@ var ( npmV1Libs = []types.Library{ { - ID: "@babel/helper-string-parser@7.19.4", - Name: "@babel/helper-string-parser", - Version: "7.19.4", - Dev: false, - Indirect: true, + ID: "@babel/helper-string-parser@7.19.4", + Name: "@babel/helper-string-parser", + Version: "7.19.4", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -33,11 +33,11 @@ var ( }, }, { - ID: "asap@2.0.6", - Name: "asap", - Version: "2.0.6", - Dev: false, - Indirect: true, + ID: "asap@2.0.6", + Name: "asap", + Version: "2.0.6", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -52,11 +52,11 @@ var ( }, }, { - ID: "body-parser@1.18.3", - Name: "body-parser", - Version: "1.18.3", - Dev: false, - Indirect: true, + ID: "body-parser@1.18.3", + Name: "body-parser", + Version: "1.18.3", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -71,11 +71,11 @@ var ( }, }, { - ID: "bytes@3.0.0", - Name: "bytes", - Version: "3.0.0", - Dev: false, - Indirect: true, + ID: "bytes@3.0.0", + Name: "bytes", + Version: "3.0.0", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -90,11 +90,11 @@ var ( }, }, { - ID: "content-type@1.0.5", - Name: "content-type", - Version: "1.0.5", - Dev: false, - Indirect: true, + ID: "content-type@1.0.5", + Name: "content-type", + Version: "1.0.5", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -109,11 +109,11 @@ var ( }, }, { - ID: "debug@2.5.2", - Name: "debug", - Version: "2.5.2", - Dev: true, - Indirect: true, + ID: "debug@2.5.2", + Name: "debug", + Version: "2.5.2", + Dev: true, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -128,11 +128,11 @@ var ( }, }, { - ID: "debug@2.6.9", - Name: "debug", - Version: "2.6.9", - Dev: false, - Indirect: true, + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -151,11 +151,11 @@ var ( }, }, { - ID: "depd@1.1.2", - Name: "depd", - Version: "1.1.2", - Dev: false, - Indirect: true, + ID: "depd@1.1.2", + Name: "depd", + Version: "1.1.2", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -170,11 +170,11 @@ var ( }, }, { - ID: "ee-first@1.1.1", - Name: "ee-first", - Version: "1.1.1", - Dev: false, - Indirect: true, + ID: "ee-first@1.1.1", + Name: "ee-first", + Version: "1.1.1", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -189,11 +189,11 @@ var ( }, }, { - ID: "encodeurl@1.0.2", - Name: "encodeurl", - Version: "1.0.2", - Dev: false, - Indirect: true, + ID: "encodeurl@1.0.2", + Name: "encodeurl", + Version: "1.0.2", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -208,11 +208,11 @@ var ( }, }, { - ID: "escape-html@1.0.3", - Name: "escape-html", - Version: "1.0.3", - Dev: false, - Indirect: true, + ID: "escape-html@1.0.3", + Name: "escape-html", + Version: "1.0.3", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -227,11 +227,11 @@ var ( }, }, { - ID: "finalhandler@1.1.1", - Name: "finalhandler", - Version: "1.1.1", - Dev: false, - Indirect: true, + ID: "finalhandler@1.1.1", + Name: "finalhandler", + Version: "1.1.1", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -246,11 +246,11 @@ var ( }, }, { - ID: "http-errors@1.6.3", - Name: "http-errors", - Version: "1.6.3", - Dev: false, - Indirect: true, + ID: "http-errors@1.6.3", + Name: "http-errors", + Version: "1.6.3", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -265,11 +265,11 @@ var ( }, }, { - ID: "iconv-lite@0.4.23", - Name: "iconv-lite", - Version: "0.4.23", - Dev: false, - Indirect: true, + ID: "iconv-lite@0.4.23", + Name: "iconv-lite", + Version: "0.4.23", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -284,11 +284,11 @@ var ( }, }, { - ID: "inherits@2.0.3", - Name: "inherits", - Version: "2.0.3", - Dev: false, - Indirect: true, + ID: "inherits@2.0.3", + Name: "inherits", + Version: "2.0.3", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -303,11 +303,11 @@ var ( }, }, { - ID: "media-typer@0.3.0", - Name: "media-typer", - Version: "0.3.0", - Dev: false, - Indirect: true, + ID: "media-typer@0.3.0", + Name: "media-typer", + Version: "0.3.0", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -322,11 +322,11 @@ var ( }, }, { - ID: "mime-db@1.52.0", - Name: "mime-db", - Version: "1.52.0", - Dev: false, - Indirect: true, + ID: "mime-db@1.52.0", + Name: "mime-db", + Version: "1.52.0", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -341,11 +341,11 @@ var ( }, }, { - ID: "mime-types@2.1.35", - Name: "mime-types", - Version: "2.1.35", - Dev: false, - Indirect: true, + ID: "mime-types@2.1.35", + Name: "mime-types", + Version: "2.1.35", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -360,11 +360,11 @@ var ( }, }, { - ID: "ms@0.7.2", - Name: "ms", - Version: "0.7.2", - Dev: true, - Indirect: true, + ID: "ms@0.7.2", + Name: "ms", + Version: "0.7.2", + Dev: true, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -379,11 +379,11 @@ var ( }, }, { - ID: "ms@1.0.0", - Name: "ms", - Version: "1.0.0", - Dev: false, - Indirect: true, + ID: "ms@1.0.0", + Name: "ms", + Version: "1.0.0", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -398,11 +398,11 @@ var ( }, }, { - ID: "ms@2.0.0", - Name: "ms", - Version: "2.0.0", - Dev: false, - Indirect: true, + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -421,11 +421,11 @@ var ( }, }, { - ID: "on-finished@2.3.0", - Name: "on-finished", - Version: "2.3.0", - Dev: false, - Indirect: true, + ID: "on-finished@2.3.0", + Name: "on-finished", + Version: "2.3.0", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -440,11 +440,11 @@ var ( }, }, { - ID: "parseurl@1.3.3", - Name: "parseurl", - Version: "1.3.3", - Dev: false, - Indirect: true, + ID: "parseurl@1.3.3", + Name: "parseurl", + Version: "1.3.3", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -459,11 +459,11 @@ var ( }, }, { - ID: "promise@8.3.0", - Name: "promise", - Version: "8.3.0", - Dev: false, - Indirect: true, + ID: "promise@8.3.0", + Name: "promise", + Version: "8.3.0", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -478,11 +478,11 @@ var ( }, }, { - ID: "qs@6.5.2", - Name: "qs", - Version: "6.5.2", - Dev: false, - Indirect: true, + ID: "qs@6.5.2", + Name: "qs", + Version: "6.5.2", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -497,11 +497,11 @@ var ( }, }, { - ID: "raw-body@2.3.3", - Name: "raw-body", - Version: "2.3.3", - Dev: false, - Indirect: true, + ID: "raw-body@2.3.3", + Name: "raw-body", + Version: "2.3.3", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -516,11 +516,11 @@ var ( }, }, { - ID: "safer-buffer@2.1.2", - Name: "safer-buffer", - Version: "2.1.2", - Dev: false, - Indirect: true, + ID: "safer-buffer@2.1.2", + Name: "safer-buffer", + Version: "2.1.2", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -535,11 +535,11 @@ var ( }, }, { - ID: "setprototypeof@1.1.0", - Name: "setprototypeof", - Version: "1.1.0", - Dev: false, - Indirect: true, + ID: "setprototypeof@1.1.0", + Name: "setprototypeof", + Version: "1.1.0", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -554,11 +554,11 @@ var ( }, }, { - ID: "statuses@1.4.0", - Name: "statuses", - Version: "1.4.0", - Dev: false, - Indirect: true, + ID: "statuses@1.4.0", + Name: "statuses", + Version: "1.4.0", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -573,11 +573,11 @@ var ( }, }, { - ID: "type-is@1.6.18", - Name: "type-is", - Version: "1.6.18", - Dev: false, - Indirect: true, + ID: "type-is@1.6.18", + Name: "type-is", + Version: "1.6.18", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -592,11 +592,11 @@ var ( }, }, { - ID: "unpipe@1.0.0", - Name: "unpipe", - Version: "1.0.0", - Dev: false, - Indirect: true, + ID: "unpipe@1.0.0", + Name: "unpipe", + Version: "1.0.0", + Dev: false, + Relationship: types.RelationshipUnknown, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -701,11 +701,11 @@ var ( // same as npmV2Libs. npmV2Libs = []types.Library{ { - ID: "@babel/helper-string-parser@7.19.4", - Name: "@babel/helper-string-parser", - Version: "7.19.4", - Dev: false, - Indirect: false, + ID: "@babel/helper-string-parser@7.19.4", + Name: "@babel/helper-string-parser", + Version: "7.19.4", + Dev: false, + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -720,106 +720,163 @@ var ( }, }, { - ID: "asap@2.0.6", - Name: "asap", - Version: "2.0.6", - Dev: false, - Indirect: true, + ID: "body-parser@1.18.3", + Name: "body-parser", + Version: "1.18.3", + Dev: false, + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + URL: "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", }, }, Locations: []types.Location{ { - StartLine: 32, - EndLine: 37, + StartLine: 38, + EndLine: 57, }, }, }, { - ID: "body-parser@1.18.3", - Name: "body-parser", - Version: "1.18.3", - Dev: false, - Indirect: false, + ID: "debug@2.5.2", + Name: "debug", + Version: "2.5.2", + Dev: true, + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", }, }, Locations: []types.Location{ { - StartLine: 38, - EndLine: 57, + StartLine: 87, + EndLine: 95, }, }, }, { - ID: "bytes@3.0.0", - Name: "bytes", - Version: "3.0.0", - Dev: false, - Indirect: true, + ID: "finalhandler@1.1.1", + Name: "finalhandler", + Version: "1.1.1", + Dev: false, + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + URL: "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", }, }, Locations: []types.Location{ { - StartLine: 71, - EndLine: 78, + StartLine: 128, + EndLine: 144, }, }, }, { - ID: "content-type@1.0.5", - Name: "content-type", - Version: "1.0.5", - Dev: false, - Indirect: true, + ID: "ms@1.0.0", + Name: "ms", + Version: "1.0.0", + Dev: false, + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + URL: "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz", }, }, Locations: []types.Location{ { - StartLine: 79, - EndLine: 86, + StartLine: 215, + EndLine: 219, }, }, }, { - ID: "debug@2.5.2", - Name: "debug", - Version: "2.5.2", - Dev: true, - Indirect: false, + ID: "promise@8.3.0", + Name: "promise", + Version: "8.3.0", + Dev: false, + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", + URL: "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", }, }, Locations: []types.Location{ { - StartLine: 87, - EndLine: 95, + StartLine: 239, + EndLine: 247, + }, + }, + }, + { + ID: "asap@2.0.6", + Name: "asap", + Version: "2.0.6", + Dev: false, + Relationship: types.RelationshipIndirect, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 32, + EndLine: 37, + }, + }, + }, + { + ID: "bytes@3.0.0", + Name: "bytes", + Version: "3.0.0", + Dev: false, + Relationship: types.RelationshipIndirect, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 71, + EndLine: 78, }, }, }, { - ID: "debug@2.6.9", - Name: "debug", - Version: "2.6.9", - Dev: false, - Indirect: true, + ID: "content-type@1.0.5", + Name: "content-type", + Version: "1.0.5", + Dev: false, + Relationship: types.RelationshipIndirect, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 79, + EndLine: 86, + }, + }, + }, + { + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -838,11 +895,11 @@ var ( }, }, { - ID: "depd@1.1.2", - Name: "depd", - Version: "1.1.2", - Dev: false, - Indirect: true, + ID: "depd@1.1.2", + Name: "depd", + Version: "1.1.2", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -857,11 +914,11 @@ var ( }, }, { - ID: "ee-first@1.1.1", - Name: "ee-first", - Version: "1.1.1", - Dev: false, - Indirect: true, + ID: "ee-first@1.1.1", + Name: "ee-first", + Version: "1.1.1", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -876,11 +933,11 @@ var ( }, }, { - ID: "encodeurl@1.0.2", - Name: "encodeurl", - Version: "1.0.2", - Dev: false, - Indirect: true, + ID: "encodeurl@1.0.2", + Name: "encodeurl", + Version: "1.0.2", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -895,11 +952,11 @@ var ( }, }, { - ID: "escape-html@1.0.3", - Name: "escape-html", - Version: "1.0.3", - Dev: false, - Indirect: true, + ID: "escape-html@1.0.3", + Name: "escape-html", + Version: "1.0.3", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -914,30 +971,11 @@ var ( }, }, { - ID: "finalhandler@1.1.1", - Name: "finalhandler", - Version: "1.1.1", - Dev: false, - Indirect: false, - ExternalReferences: []types.ExternalRef{ - { - Type: types.RefOther, - URL: "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - }, - }, - Locations: []types.Location{ - { - StartLine: 128, - EndLine: 144, - }, - }, - }, - { - ID: "http-errors@1.6.3", - Name: "http-errors", - Version: "1.6.3", - Dev: false, - Indirect: true, + ID: "http-errors@1.6.3", + Name: "http-errors", + Version: "1.6.3", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -952,11 +990,11 @@ var ( }, }, { - ID: "iconv-lite@0.4.23", - Name: "iconv-lite", - Version: "0.4.23", - Dev: false, - Indirect: true, + ID: "iconv-lite@0.4.23", + Name: "iconv-lite", + Version: "0.4.23", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -971,11 +1009,11 @@ var ( }, }, { - ID: "inherits@2.0.3", - Name: "inherits", - Version: "2.0.3", - Dev: false, - Indirect: true, + ID: "inherits@2.0.3", + Name: "inherits", + Version: "2.0.3", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -990,11 +1028,11 @@ var ( }, }, { - ID: "media-typer@0.3.0", - Name: "media-typer", - Version: "0.3.0", - Dev: false, - Indirect: true, + ID: "media-typer@0.3.0", + Name: "media-typer", + Version: "0.3.0", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1009,11 +1047,11 @@ var ( }, }, { - ID: "mime-db@1.52.0", - Name: "mime-db", - Version: "1.52.0", - Dev: false, - Indirect: true, + ID: "mime-db@1.52.0", + Name: "mime-db", + Version: "1.52.0", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1028,11 +1066,11 @@ var ( }, }, { - ID: "mime-types@2.1.35", - Name: "mime-types", - Version: "2.1.35", - Dev: false, - Indirect: true, + ID: "mime-types@2.1.35", + Name: "mime-types", + Version: "2.1.35", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1047,11 +1085,11 @@ var ( }, }, { - ID: "ms@0.7.2", - Name: "ms", - Version: "0.7.2", - Dev: true, - Indirect: true, + ID: "ms@0.7.2", + Name: "ms", + Version: "0.7.2", + Dev: true, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1066,30 +1104,11 @@ var ( }, }, { - ID: "ms@1.0.0", - Name: "ms", - Version: "1.0.0", - Dev: false, - Indirect: false, - ExternalReferences: []types.ExternalRef{ - { - Type: types.RefOther, - URL: "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz", - }, - }, - Locations: []types.Location{ - { - StartLine: 215, - EndLine: 219, - }, - }, - }, - { - ID: "ms@2.0.0", - Name: "ms", - Version: "2.0.0", - Dev: false, - Indirect: true, + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1108,11 +1127,11 @@ var ( }, }, { - ID: "on-finished@2.3.0", - Name: "on-finished", - Version: "2.3.0", - Dev: false, - Indirect: true, + ID: "on-finished@2.3.0", + Name: "on-finished", + Version: "2.3.0", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1127,11 +1146,11 @@ var ( }, }, { - ID: "parseurl@1.3.3", - Name: "parseurl", - Version: "1.3.3", - Dev: false, - Indirect: true, + ID: "parseurl@1.3.3", + Name: "parseurl", + Version: "1.3.3", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1146,30 +1165,11 @@ var ( }, }, { - ID: "promise@8.3.0", - Name: "promise", - Version: "8.3.0", - Dev: false, - Indirect: false, - ExternalReferences: []types.ExternalRef{ - { - Type: types.RefOther, - URL: "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - }, - }, - Locations: []types.Location{ - { - StartLine: 239, - EndLine: 247, - }, - }, - }, - { - ID: "qs@6.5.2", - Name: "qs", - Version: "6.5.2", - Dev: false, - Indirect: true, + ID: "qs@6.5.2", + Name: "qs", + Version: "6.5.2", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1184,11 +1184,11 @@ var ( }, }, { - ID: "raw-body@2.3.3", - Name: "raw-body", - Version: "2.3.3", - Dev: false, - Indirect: true, + ID: "raw-body@2.3.3", + Name: "raw-body", + Version: "2.3.3", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1203,11 +1203,11 @@ var ( }, }, { - ID: "safer-buffer@2.1.2", - Name: "safer-buffer", - Version: "2.1.2", - Dev: false, - Indirect: true, + ID: "safer-buffer@2.1.2", + Name: "safer-buffer", + Version: "2.1.2", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1222,11 +1222,11 @@ var ( }, }, { - ID: "setprototypeof@1.1.0", - Name: "setprototypeof", - Version: "1.1.0", - Dev: false, - Indirect: true, + ID: "setprototypeof@1.1.0", + Name: "setprototypeof", + Version: "1.1.0", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1241,11 +1241,11 @@ var ( }, }, { - ID: "statuses@1.4.0", - Name: "statuses", - Version: "1.4.0", - Dev: false, - Indirect: true, + ID: "statuses@1.4.0", + Name: "statuses", + Version: "1.4.0", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1260,11 +1260,11 @@ var ( }, }, { - ID: "type-is@1.6.18", - Name: "type-is", - Version: "1.6.18", - Dev: false, - Indirect: true, + ID: "type-is@1.6.18", + Name: "type-is", + Version: "1.6.18", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1279,11 +1279,11 @@ var ( }, }, { - ID: "unpipe@1.0.0", - Name: "unpipe", - Version: "1.0.0", - Dev: false, - Indirect: true, + ID: "unpipe@1.0.0", + Name: "unpipe", + Version: "1.0.0", + Dev: false, + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1315,10 +1315,10 @@ var ( // libraries are filled manually npmV3WithWorkspaceLibs = []types.Library{ { - ID: "debug@2.5.2", - Name: "debug", - Version: "2.5.2", - Indirect: false, + ID: "debug@2.5.2", + Name: "debug", + Version: "2.5.2", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1333,92 +1333,92 @@ var ( }, }, { - ID: "debug@2.6.9", - Name: "debug", - Version: "2.6.9", - Indirect: true, + ID: "function1", + Name: "function1", + Version: "", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + URL: "functions/func1", }, }, Locations: []types.Location{ { - StartLine: 31, - EndLine: 38, + StartLine: 18, + EndLine: 23, }, }, }, { - ID: "function1", - Name: "function1", - Version: "", - Indirect: false, + ID: "nested_func@1.0.0", + Name: "nested_func", + Version: "1.0.0", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: "functions/func1", + URL: "functions/nested_func", }, }, Locations: []types.Location{ { - StartLine: 18, - EndLine: 23, + StartLine: 24, + EndLine: 30, }, }, }, { - ID: "ms@0.7.2", - Name: "ms", - Version: "0.7.2", - Indirect: true, + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", }, }, Locations: []types.Location{ { - StartLine: 47, - EndLine: 51, + StartLine: 31, + EndLine: 38, }, }, }, { - ID: "ms@2.0.0", - Name: "ms", - Version: "2.0.0", - Indirect: true, + ID: "ms@0.7.2", + Name: "ms", + Version: "0.7.2", + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", }, }, Locations: []types.Location{ { - StartLine: 56, - EndLine: 60, + StartLine: 47, + EndLine: 51, }, }, }, { - ID: "nested_func@1.0.0", - Name: "nested_func", - Version: "1.0.0", - Indirect: false, + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: "functions/nested_func", + URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", }, }, Locations: []types.Location{ { - StartLine: 24, - EndLine: 30, + StartLine: 56, + EndLine: 60, }, }, }, @@ -1451,46 +1451,46 @@ var ( // libraries are filled manually npmV3WithoutRootDepsField = []types.Library{ { - ID: "debug@2.6.9", - Name: "debug", - Version: "2.6.9", - Indirect: true, + ID: "func1@1.0.0", + Name: "func1", + Version: "1.0.0", + Relationship: types.RelationshipDirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + URL: "functions/func1", }, }, Locations: []types.Location{ { - StartLine: 22, - EndLine: 29, + StartLine: 15, + EndLine: 21, }, }, }, { - ID: "func1@1.0.0", - Name: "func1", - Version: "1.0.0", - Indirect: false, + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: "functions/func1", + URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", }, }, Locations: []types.Location{ { - StartLine: 15, - EndLine: 21, + StartLine: 22, + EndLine: 29, }, }, }, { - ID: "ms@2.0.0", - Name: "ms", - Version: "2.0.0", - Indirect: true, + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Relationship: types.RelationshipIndirect, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1519,10 +1519,11 @@ var ( npmV3WithSameDevAndNonDevLibs = []types.Library{ { - ID: "fsevents@1.2.9", - Name: "fsevents", - Version: "1.2.9", - Dev: true, + ID: "fsevents@1.2.9", + Name: "fsevents", + Version: "1.2.9", + Relationship: types.RelationshipDirect, + Dev: true, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1537,11 +1538,11 @@ var ( }, }, { - ID: "minimist@0.0.8", - Name: "minimist", - Version: "0.0.8", - Indirect: false, - Dev: false, + ID: "minimist@0.0.8", + Name: "minimist", + Version: "0.0.8", + Relationship: types.RelationshipDirect, + Dev: false, ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, @@ -1560,11 +1561,11 @@ var ( }, }, { - ID: "mkdirp@0.5.1", - Name: "mkdirp", - Version: "0.5.1", - Indirect: true, - Dev: true, + ID: "mkdirp@0.5.1", + Name: "mkdirp", + Version: "0.5.1", + Relationship: types.RelationshipIndirect, + Dev: true, Locations: []types.Location{ { StartLine: 44, @@ -1573,11 +1574,11 @@ var ( }, }, { - ID: "node-pre-gyp@0.12.0", - Name: "node-pre-gyp", - Version: "0.12.0", - Indirect: true, - Dev: true, + ID: "node-pre-gyp@0.12.0", + Name: "node-pre-gyp", + Version: "0.12.0", + Relationship: types.RelationshipIndirect, + Dev: true, Locations: []types.Location{ { StartLine: 56, diff --git a/pkg/dependency/parser/nodejs/pnpm/parse.go b/pkg/dependency/parser/nodejs/pnpm/parse.go index 26012b747c4e..b11646851462 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/samber/lo" "golang.org/x/xerrors" "gopkg.in/yaml.v3" @@ -90,10 +91,10 @@ func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]types.Library, []t } libs = append(libs, types.Library{ - ID: pkgID, - Name: name, - Version: version, - Indirect: isIndirectLib(name, lockFile.Dependencies), + ID: pkgID, + Name: name, + Version: version, + Relationship: lo.Ternary(isDirectLib(name, lockFile.Dependencies), types.RelationshipDirect, types.RelationshipIndirect), }) if len(dependencies) > 0 { @@ -178,9 +179,9 @@ func (p *Parser) parseDepPath(depPath, versionSep string) (string, string) { return name, version } -func isIndirectLib(name string, directDeps map[string]interface{}) bool { +func isDirectLib(name string, directDeps map[string]interface{}) bool { _, ok := directDeps[name] - return !ok + return ok } func packageID(name, version string) string { diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go index 2b776cd0a9c8..d41ac4c0cee7 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go @@ -6,11 +6,26 @@ var ( // docker run --name node --rm -it node:16-alpine sh // npm install -g pnpm // pnpm add promise jquery - // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Indirect: true},\n")}' | sort -u + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u pnpmNormal = []types.Library{ - {ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Indirect: true}, - {ID: "jquery@3.6.0", Name: "jquery", Version: "3.6.0", Indirect: false}, - {ID: "promise@8.1.0", Name: "promise", Version: "8.1.0", Indirect: false}, + { + ID: "asap@2.0.6", + Name: "asap", + Version: "2.0.6", + Relationship: types.RelationshipIndirect, + }, + { + ID: "jquery@3.6.0", + Name: "jquery", + Version: "3.6.0", + Relationship: types.RelationshipDirect, + }, + { + ID: "promise@8.1.0", + Name: "promise", + Version: "8.1.0", + Relationship: types.RelationshipDirect, + }, } pnpmNormalDeps = []types.Dependency{ { @@ -23,14 +38,44 @@ var ( // npm install -g pnpm // pnpm add react redux // pnpm add -D mocha - // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Indirect: true},\n")}' | sort -u + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u pnpmWithDev = []types.Library{ - {ID: "@babel/runtime@7.18.3", Name: "@babel/runtime", Version: "7.18.3", Indirect: true}, - {ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Indirect: true}, - {ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", Indirect: true}, - {ID: "react@18.1.0", Name: "react", Version: "18.1.0", Indirect: false}, - {ID: "redux@4.2.0", Name: "redux", Version: "4.2.0", Indirect: false}, - {ID: "regenerator-runtime@0.13.9", Name: "regenerator-runtime", Version: "0.13.9", Indirect: true}, + { + ID: "@babel/runtime@7.18.3", + Name: "@babel/runtime", + Version: "7.18.3", + Relationship: types.RelationshipIndirect, + }, + { + ID: "js-tokens@4.0.0", + Name: "js-tokens", + Version: "4.0.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "loose-envify@1.4.0", + Name: "loose-envify", + Version: "1.4.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "react@18.1.0", + Name: "react", + Version: "18.1.0", + Relationship: types.RelationshipDirect, + }, + { + ID: "redux@4.2.0", + Name: "redux", + Version: "4.2.0", + Relationship: types.RelationshipDirect, + }, + { + ID: "regenerator-runtime@0.13.9", + Name: "regenerator-runtime", + Version: "0.13.9", + Relationship: types.RelationshipIndirect, + }, } pnpmWithDevDeps = []types.Dependency{ { @@ -55,88 +100,496 @@ var ( // npm install -g pnpm // pnpm add react redux lodash request chalk commander // pnpm add -D mocha - // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Indirect: true},\n")}' | sort -u + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u pnpmMany = []types.Library{ - {ID: "@babel/runtime@7.18.3", Name: "@babel/runtime", Version: "7.18.3", Indirect: true}, - {ID: "ajv@6.12.6", Name: "ajv", Version: "6.12.6", Indirect: true}, - {ID: "asn1@0.2.6", Name: "asn1", Version: "0.2.6", Indirect: true}, - {ID: "assert-plus@1.0.0", Name: "assert-plus", Version: "1.0.0", Indirect: true}, - {ID: "asynckit@0.4.0", Name: "asynckit", Version: "0.4.0", Indirect: true}, - {ID: "aws-sign2@0.7.0", Name: "aws-sign2", Version: "0.7.0", Indirect: true}, - {ID: "aws4@1.11.0", Name: "aws4", Version: "1.11.0", Indirect: true}, - {ID: "bcrypt-pbkdf@1.0.2", Name: "bcrypt-pbkdf", Version: "1.0.2", Indirect: true}, - {ID: "caseless@0.12.0", Name: "caseless", Version: "0.12.0", Indirect: true}, - {ID: "chalk@5.0.1", Name: "chalk", Version: "5.0.1", Indirect: false}, - {ID: "combined-stream@1.0.8", Name: "combined-stream", Version: "1.0.8", Indirect: true}, - {ID: "commander@9.3.0", Name: "commander", Version: "9.3.0", Indirect: false}, - {ID: "core-util-is@1.0.2", Name: "core-util-is", Version: "1.0.2", Indirect: true}, - {ID: "dashdash@1.14.1", Name: "dashdash", Version: "1.14.1", Indirect: true}, - {ID: "delayed-stream@1.0.0", Name: "delayed-stream", Version: "1.0.0", Indirect: true}, - {ID: "ecc-jsbn@0.1.2", Name: "ecc-jsbn", Version: "0.1.2", Indirect: true}, - {ID: "extend@3.0.2", Name: "extend", Version: "3.0.2", Indirect: true}, - {ID: "extsprintf@1.3.0", Name: "extsprintf", Version: "1.3.0", Indirect: true}, - {ID: "fast-deep-equal@3.1.3", Name: "fast-deep-equal", Version: "3.1.3", Indirect: true}, - {ID: "fast-json-stable-stringify@2.1.0", Name: "fast-json-stable-stringify", Version: "2.1.0", Indirect: true}, - {ID: "forever-agent@0.6.1", Name: "forever-agent", Version: "0.6.1", Indirect: true}, - {ID: "form-data@2.3.3", Name: "form-data", Version: "2.3.3", Indirect: true}, - {ID: "getpass@0.1.7", Name: "getpass", Version: "0.1.7", Indirect: true}, - {ID: "har-schema@2.0.0", Name: "har-schema", Version: "2.0.0", Indirect: true}, - {ID: "har-validator@5.1.5", Name: "har-validator", Version: "5.1.5", Indirect: true}, - {ID: "http-signature@1.2.0", Name: "http-signature", Version: "1.2.0", Indirect: true}, - {ID: "is-typedarray@1.0.0", Name: "is-typedarray", Version: "1.0.0", Indirect: true}, - {ID: "isstream@0.1.2", Name: "isstream", Version: "0.1.2", Indirect: true}, - {ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Indirect: true}, - {ID: "jsbn@0.1.1", Name: "jsbn", Version: "0.1.1", Indirect: true}, - {ID: "json-schema-traverse@0.4.1", Name: "json-schema-traverse", Version: "0.4.1", Indirect: true}, - {ID: "json-schema@0.4.0", Name: "json-schema", Version: "0.4.0", Indirect: true}, - {ID: "json-stringify-safe@5.0.1", Name: "json-stringify-safe", Version: "5.0.1", Indirect: true}, - {ID: "jsprim@1.4.2", Name: "jsprim", Version: "1.4.2", Indirect: true}, - {ID: "lodash@4.17.21", Name: "lodash", Version: "4.17.21", Indirect: false}, - {ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", Indirect: true}, - {ID: "mime-db@1.52.0", Name: "mime-db", Version: "1.52.0", Indirect: true}, - {ID: "mime-types@2.1.35", Name: "mime-types", Version: "2.1.35", Indirect: true}, - {ID: "oauth-sign@0.9.0", Name: "oauth-sign", Version: "0.9.0", Indirect: true}, - {ID: "performance-now@2.1.0", Name: "performance-now", Version: "2.1.0", Indirect: true}, - {ID: "psl@1.8.0", Name: "psl", Version: "1.8.0", Indirect: true}, - {ID: "punycode@2.1.1", Name: "punycode", Version: "2.1.1", Indirect: true}, - {ID: "qs@6.5.3", Name: "qs", Version: "6.5.3", Indirect: true}, - {ID: "react@18.1.0", Name: "react", Version: "18.1.0", Indirect: false}, - {ID: "redux@4.2.0", Name: "redux", Version: "4.2.0", Indirect: false}, - {ID: "regenerator-runtime@0.13.9", Name: "regenerator-runtime", Version: "0.13.9", Indirect: true}, - {ID: "request@2.88.2", Name: "request", Version: "2.88.2", Indirect: false}, - {ID: "safe-buffer@5.2.1", Name: "safe-buffer", Version: "5.2.1", Indirect: true}, - {ID: "safer-buffer@2.1.2", Name: "safer-buffer", Version: "2.1.2", Indirect: true}, - {ID: "sshpk@1.17.0", Name: "sshpk", Version: "1.17.0", Indirect: true}, - {ID: "tough-cookie@2.5.0", Name: "tough-cookie", Version: "2.5.0", Indirect: true}, - {ID: "tunnel-agent@0.6.0", Name: "tunnel-agent", Version: "0.6.0", Indirect: true}, - {ID: "tweetnacl@0.14.5", Name: "tweetnacl", Version: "0.14.5", Indirect: true}, - {ID: "uri-js@4.4.1", Name: "uri-js", Version: "4.4.1", Indirect: true}, - {ID: "uuid@3.4.0", Name: "uuid", Version: "3.4.0", Indirect: true}, - {ID: "verror@1.10.0", Name: "verror", Version: "1.10.0", Indirect: true}, + { + ID: "@babel/runtime@7.18.3", + Name: "@babel/runtime", + Version: "7.18.3", + Relationship: types.RelationshipIndirect, + }, + { + ID: "ajv@6.12.6", + Name: "ajv", + Version: "6.12.6", + Relationship: types.RelationshipIndirect, + }, + { + ID: "asn1@0.2.6", + Name: "asn1", + Version: "0.2.6", + Relationship: types.RelationshipIndirect, + }, + { + ID: "assert-plus@1.0.0", + Name: "assert-plus", + Version: "1.0.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "asynckit@0.4.0", + Name: "asynckit", + Version: "0.4.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "aws-sign2@0.7.0", + Name: "aws-sign2", + Version: "0.7.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "aws4@1.11.0", + Name: "aws4", + Version: "1.11.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "bcrypt-pbkdf@1.0.2", + Name: "bcrypt-pbkdf", + Version: "1.0.2", + Relationship: types.RelationshipIndirect, + }, + { + ID: "caseless@0.12.0", + Name: "caseless", + Version: "0.12.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "chalk@5.0.1", + Name: "chalk", + Version: "5.0.1", + Relationship: types.RelationshipDirect, + }, + { + ID: "combined-stream@1.0.8", + Name: "combined-stream", + Version: "1.0.8", + Relationship: types.RelationshipIndirect, + }, + { + ID: "commander@9.3.0", + Name: "commander", + Version: "9.3.0", + Relationship: types.RelationshipDirect, + }, + { + ID: "core-util-is@1.0.2", + Name: "core-util-is", + Version: "1.0.2", + Relationship: types.RelationshipIndirect, + }, + { + ID: "dashdash@1.14.1", + Name: "dashdash", + Version: "1.14.1", + Relationship: types.RelationshipIndirect, + }, + { + ID: "delayed-stream@1.0.0", + Name: "delayed-stream", + Version: "1.0.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "ecc-jsbn@0.1.2", + Name: "ecc-jsbn", + Version: "0.1.2", + Relationship: types.RelationshipIndirect, + }, + { + ID: "extend@3.0.2", + Name: "extend", + Version: "3.0.2", + Relationship: types.RelationshipIndirect, + }, + { + ID: "extsprintf@1.3.0", + Name: "extsprintf", + Version: "1.3.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "fast-deep-equal@3.1.3", + Name: "fast-deep-equal", + Version: "3.1.3", + Relationship: types.RelationshipIndirect, + }, + { + ID: "fast-json-stable-stringify@2.1.0", + Name: "fast-json-stable-stringify", + Version: "2.1.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "forever-agent@0.6.1", + Name: "forever-agent", + Version: "0.6.1", + Relationship: types.RelationshipIndirect, + }, + { + ID: "form-data@2.3.3", + Name: "form-data", + Version: "2.3.3", + Relationship: types.RelationshipIndirect, + }, + { + ID: "getpass@0.1.7", + Name: "getpass", + Version: "0.1.7", + Relationship: types.RelationshipIndirect, + }, + { + ID: "har-schema@2.0.0", + Name: "har-schema", + Version: "2.0.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "har-validator@5.1.5", + Name: "har-validator", + Version: "5.1.5", + Relationship: types.RelationshipIndirect, + }, + { + ID: "http-signature@1.2.0", + Name: "http-signature", + Version: "1.2.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "is-typedarray@1.0.0", + Name: "is-typedarray", + Version: "1.0.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "isstream@0.1.2", + Name: "isstream", + Version: "0.1.2", + Relationship: types.RelationshipIndirect, + }, + { + ID: "js-tokens@4.0.0", + Name: "js-tokens", + Version: "4.0.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "jsbn@0.1.1", + Name: "jsbn", + Version: "0.1.1", + Relationship: types.RelationshipIndirect, + }, + { + ID: "json-schema-traverse@0.4.1", + Name: "json-schema-traverse", + Version: "0.4.1", + Relationship: types.RelationshipIndirect, + }, + { + ID: "json-schema@0.4.0", + Name: "json-schema", + Version: "0.4.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "json-stringify-safe@5.0.1", + Name: "json-stringify-safe", + Version: "5.0.1", + Relationship: types.RelationshipIndirect, + }, + { + ID: "jsprim@1.4.2", + Name: "jsprim", + Version: "1.4.2", + Relationship: types.RelationshipIndirect, + }, + { + ID: "lodash@4.17.21", + Name: "lodash", + Version: "4.17.21", + Relationship: types.RelationshipDirect, + }, + { + ID: "loose-envify@1.4.0", + Name: "loose-envify", + Version: "1.4.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "mime-db@1.52.0", + Name: "mime-db", + Version: "1.52.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "mime-types@2.1.35", + Name: "mime-types", + Version: "2.1.35", + Relationship: types.RelationshipIndirect, + }, + { + ID: "oauth-sign@0.9.0", + Name: "oauth-sign", + Version: "0.9.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "performance-now@2.1.0", + Name: "performance-now", + Version: "2.1.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "psl@1.8.0", + Name: "psl", + Version: "1.8.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "punycode@2.1.1", + Name: "punycode", + Version: "2.1.1", + Relationship: types.RelationshipIndirect, + }, + { + ID: "qs@6.5.3", + Name: "qs", + Version: "6.5.3", + Relationship: types.RelationshipIndirect, + }, + { + ID: "react@18.1.0", + Name: "react", + Version: "18.1.0", + Relationship: types.RelationshipDirect, + }, + { + ID: "redux@4.2.0", + Name: "redux", + Version: "4.2.0", + Relationship: types.RelationshipDirect, + }, + { + ID: "regenerator-runtime@0.13.9", + Name: "regenerator-runtime", + Version: "0.13.9", + Relationship: types.RelationshipIndirect, + }, + { + ID: "request@2.88.2", + Name: "request", + Version: "2.88.2", + Relationship: types.RelationshipDirect, + }, + { + ID: "safe-buffer@5.2.1", + Name: "safe-buffer", + Version: "5.2.1", + Relationship: types.RelationshipIndirect, + }, + { + ID: "safer-buffer@2.1.2", + Name: "safer-buffer", + Version: "2.1.2", + Relationship: types.RelationshipIndirect, + }, + { + ID: "sshpk@1.17.0", + Name: "sshpk", + Version: "1.17.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "tough-cookie@2.5.0", + Name: "tough-cookie", + Version: "2.5.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "tunnel-agent@0.6.0", + Name: "tunnel-agent", + Version: "0.6.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "tweetnacl@0.14.5", + Name: "tweetnacl", + Version: "0.14.5", + Relationship: types.RelationshipIndirect, + }, + { + ID: "uri-js@4.4.1", + Name: "uri-js", + Version: "4.4.1", + Relationship: types.RelationshipIndirect, + }, + { + ID: "uuid@3.4.0", + Name: "uuid", + Version: "3.4.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "verror@1.10.0", + Name: "verror", + Version: "1.10.0", + Relationship: types.RelationshipIndirect, + }, } pnpmManyDeps = []types.Dependency{ - {ID: "@babel/runtime@7.18.3", DependsOn: []string{"regenerator-runtime@0.13.9"}}, - {ID: "ajv@6.12.6", DependsOn: []string{"fast-deep-equal@3.1.3", "fast-json-stable-stringify@2.1.0", "json-schema-traverse@0.4.1", "uri-js@4.4.1"}}, - {ID: "asn1@0.2.6", DependsOn: []string{"safer-buffer@2.1.2"}}, - {ID: "bcrypt-pbkdf@1.0.2", DependsOn: []string{"tweetnacl@0.14.5"}}, - {ID: "combined-stream@1.0.8", DependsOn: []string{"delayed-stream@1.0.0"}}, - {ID: "dashdash@1.14.1", DependsOn: []string{"assert-plus@1.0.0"}}, - {ID: "ecc-jsbn@0.1.2", DependsOn: []string{"jsbn@0.1.1", "safer-buffer@2.1.2"}}, - {ID: "form-data@2.3.3", DependsOn: []string{"asynckit@0.4.0", "combined-stream@1.0.8", "mime-types@2.1.35"}}, - {ID: "getpass@0.1.7", DependsOn: []string{"assert-plus@1.0.0"}}, - {ID: "har-validator@5.1.5", DependsOn: []string{"ajv@6.12.6", "har-schema@2.0.0"}}, - {ID: "http-signature@1.2.0", DependsOn: []string{"assert-plus@1.0.0", "jsprim@1.4.2", "sshpk@1.17.0"}}, - {ID: "jsprim@1.4.2", DependsOn: []string{"assert-plus@1.0.0", "extsprintf@1.3.0", "json-schema@0.4.0", "verror@1.10.0"}}, - {ID: "loose-envify@1.4.0", DependsOn: []string{"js-tokens@4.0.0"}}, - {ID: "mime-types@2.1.35", DependsOn: []string{"mime-db@1.52.0"}}, - {ID: "react@18.1.0", DependsOn: []string{"loose-envify@1.4.0"}}, - {ID: "redux@4.2.0", DependsOn: []string{"@babel/runtime@7.18.3"}}, - {ID: "request@2.88.2", DependsOn: []string{"aws-sign2@0.7.0", "aws4@1.11.0", "caseless@0.12.0", "combined-stream@1.0.8", "extend@3.0.2", "forever-agent@0.6.1", "form-data@2.3.3", "har-validator@5.1.5", "http-signature@1.2.0", "is-typedarray@1.0.0", "isstream@0.1.2", "json-stringify-safe@5.0.1", "mime-types@2.1.35", "oauth-sign@0.9.0", "performance-now@2.1.0", "qs@6.5.3", "safe-buffer@5.2.1", "tough-cookie@2.5.0", "tunnel-agent@0.6.0", "uuid@3.4.0"}}, - {ID: "sshpk@1.17.0", DependsOn: []string{"asn1@0.2.6", "assert-plus@1.0.0", "bcrypt-pbkdf@1.0.2", "dashdash@1.14.1", "ecc-jsbn@0.1.2", "getpass@0.1.7", "jsbn@0.1.1", "safer-buffer@2.1.2", "tweetnacl@0.14.5"}}, - {ID: "tough-cookie@2.5.0", DependsOn: []string{"psl@1.8.0", "punycode@2.1.1"}}, - {ID: "tunnel-agent@0.6.0", DependsOn: []string{"safe-buffer@5.2.1"}}, - {ID: "uri-js@4.4.1", DependsOn: []string{"punycode@2.1.1"}}, - {ID: "verror@1.10.0", DependsOn: []string{"assert-plus@1.0.0", "core-util-is@1.0.2", "extsprintf@1.3.0"}}, + { + ID: "@babel/runtime@7.18.3", + DependsOn: []string{"regenerator-runtime@0.13.9"}, + }, + { + ID: "ajv@6.12.6", + DependsOn: []string{ + "fast-deep-equal@3.1.3", + "fast-json-stable-stringify@2.1.0", + "json-schema-traverse@0.4.1", + "uri-js@4.4.1", + }, + }, + { + ID: "asn1@0.2.6", + DependsOn: []string{"safer-buffer@2.1.2"}, + }, + { + ID: "bcrypt-pbkdf@1.0.2", + DependsOn: []string{"tweetnacl@0.14.5"}, + }, + { + ID: "combined-stream@1.0.8", + DependsOn: []string{"delayed-stream@1.0.0"}, + }, + { + ID: "dashdash@1.14.1", + DependsOn: []string{"assert-plus@1.0.0"}, + }, + { + ID: "ecc-jsbn@0.1.2", + DependsOn: []string{ + "jsbn@0.1.1", + "safer-buffer@2.1.2", + }, + }, + { + ID: "form-data@2.3.3", + DependsOn: []string{ + "asynckit@0.4.0", + "combined-stream@1.0.8", + "mime-types@2.1.35", + }, + }, + { + ID: "getpass@0.1.7", + DependsOn: []string{"assert-plus@1.0.0"}, + }, + { + ID: "har-validator@5.1.5", + DependsOn: []string{ + "ajv@6.12.6", + "har-schema@2.0.0", + }, + }, + { + ID: "http-signature@1.2.0", + DependsOn: []string{ + "assert-plus@1.0.0", + "jsprim@1.4.2", + "sshpk@1.17.0", + }, + }, + { + ID: "jsprim@1.4.2", + DependsOn: []string{ + "assert-plus@1.0.0", + "extsprintf@1.3.0", + "json-schema@0.4.0", + "verror@1.10.0", + }, + }, + { + ID: "loose-envify@1.4.0", + DependsOn: []string{"js-tokens@4.0.0"}, + }, + { + ID: "mime-types@2.1.35", + DependsOn: []string{"mime-db@1.52.0"}, + }, + { + ID: "react@18.1.0", + DependsOn: []string{"loose-envify@1.4.0"}, + }, + { + ID: "redux@4.2.0", + DependsOn: []string{"@babel/runtime@7.18.3"}, + }, + { + ID: "request@2.88.2", + DependsOn: []string{ + "aws-sign2@0.7.0", + "aws4@1.11.0", + "caseless@0.12.0", + "combined-stream@1.0.8", + "extend@3.0.2", + "forever-agent@0.6.1", + "form-data@2.3.3", + "har-validator@5.1.5", + "http-signature@1.2.0", + "is-typedarray@1.0.0", + "isstream@0.1.2", + "json-stringify-safe@5.0.1", + "mime-types@2.1.35", + "oauth-sign@0.9.0", + "performance-now@2.1.0", + "qs@6.5.3", + "safe-buffer@5.2.1", + "tough-cookie@2.5.0", + "tunnel-agent@0.6.0", + "uuid@3.4.0", + }, + }, + { + ID: "sshpk@1.17.0", + DependsOn: []string{ + "asn1@0.2.6", + "assert-plus@1.0.0", + "bcrypt-pbkdf@1.0.2", + "dashdash@1.14.1", + "ecc-jsbn@0.1.2", + "getpass@0.1.7", + "jsbn@0.1.1", + "safer-buffer@2.1.2", + "tweetnacl@0.14.5", + }, + }, + { + ID: "tough-cookie@2.5.0", + DependsOn: []string{ + "psl@1.8.0", + "punycode@2.1.1", + }, + }, + { + ID: "tunnel-agent@0.6.0", + DependsOn: []string{"safe-buffer@5.2.1"}, + }, + { + ID: "uri-js@4.4.1", + DependsOn: []string{"punycode@2.1.1"}, + }, + { + ID: "verror@1.10.0", + DependsOn: []string{ + "assert-plus@1.0.0", + "core-util-is@1.0.2", + "extsprintf@1.3.0", + }, + }, } // docker run --name node --rm -it node@sha256:710a2c192ca426e03e4f3ec1869e5c29db855eb6969b74e6c50fd270ffccd3f1 sh @@ -157,15 +610,45 @@ var ( // pnpm update // pnpm add https://github.com/debug-js/debug/tarball/4.3.4 // pnpm add https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5 - // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Indirect: false},\n")}' | sort -u + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipDirect},\n")}' | sort -u // manually update `Indirect` fields pnpmArchives = []types.Library{ - {ID: "asynckit@0.4.0", Name: "asynckit", Version: "0.4.0", Indirect: true}, - {ID: "debug@4.3.4", Name: "debug", Version: "4.3.4", Indirect: false}, - {ID: "is-negative@2.0.1", Name: "is-negative", Version: "2.0.1", Indirect: false}, - {ID: "lodash@4.17.21", Name: "lodash", Version: "4.17.21", Indirect: false}, - {ID: "ms@2.1.2", Name: "ms", Version: "2.1.2", Indirect: true}, - {ID: "package1@1.0.0", Name: "package1", Version: "1.0.0", Indirect: false}, + { + ID: "asynckit@0.4.0", + Name: "asynckit", + Version: "0.4.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "debug@4.3.4", + Name: "debug", + Version: "4.3.4", + Relationship: types.RelationshipDirect, + }, + { + ID: "is-negative@2.0.1", + Name: "is-negative", + Version: "2.0.1", + Relationship: types.RelationshipDirect, + }, + { + ID: "lodash@4.17.21", + Name: "lodash", + Version: "4.17.21", + Relationship: types.RelationshipDirect, + }, + { + ID: "ms@2.1.2", + Name: "ms", + Version: "2.1.2", + Relationship: types.RelationshipIndirect, + }, + { + ID: "package1@1.0.0", + Name: "package1", + Version: "1.0.0", + Relationship: types.RelationshipDirect, + }, } pnpmArchivesDeps = []types.Dependency{ @@ -182,7 +665,7 @@ var ( // docker run --name node --rm -it node@sha256:710a2c192ca426e03e4f3ec1869e5c29db855eb6969b74e6c50fd270ffccd3f1 sh // npm install -g pnpm@8.5.1 // pnpm add promise@8.1.0 jquery@3.6.0 - // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Indirect: true},\n")}' | sort -u + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u pnpmV6 = pnpmNormal pnpmV6Deps = pnpmNormalDeps @@ -190,14 +673,44 @@ var ( // npm install -g pnpm@8.5.1 // pnpm add react@18.1.0 redux@4.2.0 // pnpm add -D mocha@10.0.0 - // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Indirect: true},\n")}' | sort -u + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u pnpmV6WithDev = []types.Library{ - {ID: "@babel/runtime@7.22.3", Name: "@babel/runtime", Version: "7.22.3", Indirect: true}, - {ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Indirect: true}, - {ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", Indirect: true}, - {ID: "react@18.1.0", Name: "react", Version: "18.1.0", Indirect: false}, - {ID: "redux@4.2.0", Name: "redux", Version: "4.2.0", Indirect: false}, - {ID: "regenerator-runtime@0.13.11", Name: "regenerator-runtime", Version: "0.13.11", Indirect: true}, + { + ID: "@babel/runtime@7.22.3", + Name: "@babel/runtime", + Version: "7.22.3", + Relationship: types.RelationshipIndirect, + }, + { + ID: "js-tokens@4.0.0", + Name: "js-tokens", + Version: "4.0.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "loose-envify@1.4.0", + Name: "loose-envify", + Version: "1.4.0", + Relationship: types.RelationshipIndirect, + }, + { + ID: "react@18.1.0", + Name: "react", + Version: "18.1.0", + Relationship: types.RelationshipDirect, + }, + { + ID: "redux@4.2.0", + Name: "redux", + Version: "4.2.0", + Relationship: types.RelationshipDirect, + }, + { + ID: "regenerator-runtime@0.13.11", + Name: "regenerator-runtime", + Version: "0.13.11", + Relationship: types.RelationshipIndirect, + }, } pnpmV6WithDevDeps = []types.Dependency{ { diff --git a/pkg/dependency/parser/nuget/lock/parse.go b/pkg/dependency/parser/nuget/lock/parse.go index 30205362db8a..d5bb54f0e21d 100644 --- a/pkg/dependency/parser/nuget/lock/parse.go +++ b/pkg/dependency/parser/nuget/lock/parse.go @@ -4,6 +4,7 @@ import ( "io" "github.com/liamg/jfather" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" @@ -56,10 +57,10 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, depId := packageID(packageName, packageContent.Resolved) lib := types.Library{ - ID: depId, - Name: packageName, - Version: packageContent.Resolved, - Indirect: packageContent.Type != "Direct", + ID: depId, + Name: packageName, + Version: packageContent.Resolved, + Relationship: lo.Ternary(packageContent.Type == "Direct", types.RelationshipDirect, types.RelationshipIndirect), Locations: []types.Location{ { StartLine: packageContent.StartLine, diff --git a/pkg/dependency/parser/nuget/lock/parse_testcase.go b/pkg/dependency/parser/nuget/lock/parse_testcase.go index 699db9f8c037..a499fc158e3e 100644 --- a/pkg/dependency/parser/nuget/lock/parse_testcase.go +++ b/pkg/dependency/parser/nuget/lock/parse_testcase.go @@ -12,8 +12,30 @@ var ( // dotnet restore --use-lock-file // cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"' nuGetSimple = []types.Library{ - {ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", Version: "12.0.3", Indirect: false, Locations: []types.Location{{StartLine: 5, EndLine: 10}}}, - {ID: "NuGet.Frameworks@5.7.0", Name: "NuGet.Frameworks", Version: "5.7.0", Indirect: false, Locations: []types.Location{{StartLine: 11, EndLine: 16}}}, + { + ID: "Newtonsoft.Json@12.0.3", + Name: "Newtonsoft.Json", + Version: "12.0.3", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 10, + }, + }, + }, + { + ID: "NuGet.Frameworks@5.7.0", + Name: "NuGet.Frameworks", + Version: "5.7.0", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 11, + EndLine: 16, + }, + }, + }, } nuGetSimpleDeps []types.Dependency @@ -26,19 +48,122 @@ var ( // dotnet restore --use-lock-file // cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"' nuGetSubDependencies = []types.Library{ - {ID: "Microsoft.Extensions.ApiDescription.Server@3.0.0", Name: "Microsoft.Extensions.ApiDescription.Server", Version: "3.0.0", Indirect: true, Locations: []types.Location{{StartLine: 29, EndLine: 33}}}, - {ID: "Microsoft.OpenApi@1.1.4", Name: "Microsoft.OpenApi", Version: "1.1.4", Indirect: true, Locations: []types.Location{{StartLine: 34, EndLine: 38}}}, - {ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", Version: "12.0.3", Indirect: false, Locations: []types.Location{{StartLine: 5, EndLine: 10}}}, - {ID: "NuGet.Frameworks@5.7.0", Name: "NuGet.Frameworks", Version: "5.7.0", Indirect: false, Locations: []types.Location{{StartLine: 11, EndLine: 16}}}, - {ID: "Swashbuckle.AspNetCore@5.5.1", Name: "Swashbuckle.AspNetCore", Version: "5.5.1", Indirect: false, Locations: []types.Location{{StartLine: 17, EndLine: 28}}}, - {ID: "Swashbuckle.AspNetCore.Swagger@5.5.1", Name: "Swashbuckle.AspNetCore.Swagger", Version: "5.5.1", Indirect: true, Locations: []types.Location{{StartLine: 39, EndLine: 46}}}, - {ID: "Swashbuckle.AspNetCore.SwaggerGen@5.5.1", Name: "Swashbuckle.AspNetCore.SwaggerGen", Version: "5.5.1", Indirect: true, Locations: []types.Location{{StartLine: 47, EndLine: 54}}}, - {ID: "Swashbuckle.AspNetCore.SwaggerUI@5.5.1", Name: "Swashbuckle.AspNetCore.SwaggerUI", Version: "5.5.1", Indirect: true, Locations: []types.Location{{StartLine: 55, EndLine: 59}}}, + { + ID: "Microsoft.Extensions.ApiDescription.Server@3.0.0", + Name: "Microsoft.Extensions.ApiDescription.Server", + Version: "3.0.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 29, + EndLine: 33, + }, + }, + }, + { + ID: "Microsoft.OpenApi@1.1.4", + Name: "Microsoft.OpenApi", + Version: "1.1.4", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 34, + EndLine: 38, + }, + }, + }, + { + ID: "Newtonsoft.Json@12.0.3", + Name: "Newtonsoft.Json", + Version: "12.0.3", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 10, + }, + }, + }, + { + ID: "NuGet.Frameworks@5.7.0", + Name: "NuGet.Frameworks", + Version: "5.7.0", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 11, + EndLine: 16, + }, + }, + }, + { + ID: "Swashbuckle.AspNetCore@5.5.1", + Name: "Swashbuckle.AspNetCore", + Version: "5.5.1", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 17, + EndLine: 28, + }, + }, + }, + { + ID: "Swashbuckle.AspNetCore.Swagger@5.5.1", + Name: "Swashbuckle.AspNetCore.Swagger", + Version: "5.5.1", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 39, + EndLine: 46, + }, + }, + }, + { + ID: "Swashbuckle.AspNetCore.SwaggerGen@5.5.1", + Name: "Swashbuckle.AspNetCore.SwaggerGen", + Version: "5.5.1", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 47, + EndLine: 54, + }, + }, + }, + { + ID: "Swashbuckle.AspNetCore.SwaggerUI@5.5.1", + Name: "Swashbuckle.AspNetCore.SwaggerUI", + Version: "5.5.1", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 55, + EndLine: 59, + }, + }, + }, } nuGetSubDependenciesDeps = []types.Dependency{ - {ID: "Swashbuckle.AspNetCore.Swagger@5.5.1", DependsOn: []string{"Microsoft.OpenApi@1.1.4"}}, - {ID: "Swashbuckle.AspNetCore.SwaggerGen@5.5.1", DependsOn: []string{"Swashbuckle.AspNetCore.Swagger@5.5.1"}}, - {ID: "Swashbuckle.AspNetCore@5.5.1", DependsOn: []string{"Microsoft.Extensions.ApiDescription.Server@3.0.0", "Swashbuckle.AspNetCore.Swagger@5.5.1", "Swashbuckle.AspNetCore.SwaggerGen@5.5.1", "Swashbuckle.AspNetCore.SwaggerUI@5.5.1"}}} + { + ID: "Swashbuckle.AspNetCore.Swagger@5.5.1", + DependsOn: []string{"Microsoft.OpenApi@1.1.4"}, + }, + { + ID: "Swashbuckle.AspNetCore.SwaggerGen@5.5.1", + DependsOn: []string{"Swashbuckle.AspNetCore.Swagger@5.5.1"}, + }, + { + ID: "Swashbuckle.AspNetCore@5.5.1", + DependsOn: []string{ + "Microsoft.Extensions.ApiDescription.Server@3.0.0", + "Swashbuckle.AspNetCore.Swagger@5.5.1", + "Swashbuckle.AspNetCore.SwaggerGen@5.5.1", + "Swashbuckle.AspNetCore.SwaggerUI@5.5.1", + }, + }, + } // mcr.microsoft.com/dotnet/sdk:latest // apt -y update && apt -y install jq @@ -49,8 +174,30 @@ var ( // dotnet restore --use-lock-file // cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"' nuGetLegacy = []types.Library{ - {ID: "AWSSDK.Core@3.5.1.30", Name: "AWSSDK.Core", Version: "3.5.1.30", Indirect: false, Locations: []types.Location{{StartLine: 5, EndLine: 10}}}, - {ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", Version: "12.0.3", Indirect: false, Locations: []types.Location{{StartLine: 11, EndLine: 16}}}, + { + ID: "AWSSDK.Core@3.5.1.30", + Name: "AWSSDK.Core", + Version: "3.5.1.30", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 10, + }, + }, + }, + { + ID: "Newtonsoft.Json@12.0.3", + Name: "Newtonsoft.Json", + Version: "12.0.3", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 11, + EndLine: 16, + }, + }, + }, } nuGetLegacyDeps []types.Dependency @@ -64,82 +211,817 @@ var ( // dotnet add package AWSSDK.Core // cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"' | sort -u nuGetMultiTarget = []types.Library{ - {ID: "AWSSDK.Core@3.5.1.30", Name: "AWSSDK.Core", Version: "3.5.1.30", Indirect: false, Locations: []types.Location{{StartLine: 5, EndLine: 10}, {StartLine: 33, EndLine: 38}, {StartLine: 61, EndLine: 66}, {StartLine: 89, EndLine: 94}, {StartLine: 436, EndLine: 444}}}, - {ID: "Microsoft.Bcl.AsyncInterfaces@1.1.0", Name: "Microsoft.Bcl.AsyncInterfaces", Version: "1.1.0", Indirect: true, Locations: []types.Location{{StartLine: 460, EndLine: 467}}}, - {ID: "Microsoft.CSharp@4.3.0", Name: "Microsoft.CSharp", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 138, EndLine: 147}}}, - {ID: "Microsoft.NETCore.Platforms@1.1.0", Name: "Microsoft.NETCore.Platforms", Version: "1.1.0", Indirect: true, Locations: []types.Location{{StartLine: 148, EndLine: 152}, {StartLine: 468, EndLine: 472}}}, - {ID: "Microsoft.NETCore.Targets@1.1.0", Name: "Microsoft.NETCore.Targets", Version: "1.1.0", Indirect: true, Locations: []types.Location{{StartLine: 153, EndLine: 157}}}, - {ID: "Microsoft.NETFramework.ReferenceAssemblies@1.0.0", Name: "Microsoft.NETFramework.ReferenceAssemblies", Version: "1.0.0", Indirect: false, Locations: []types.Location{{StartLine: 11, EndLine: 19}, {StartLine: 39, EndLine: 47}, {StartLine: 67, EndLine: 75}}}, - {ID: "Microsoft.NETFramework.ReferenceAssemblies.net20@1.0.0", Name: "Microsoft.NETFramework.ReferenceAssemblies.net20", Version: "1.0.0", Indirect: true, Locations: []types.Location{{StartLine: 26, EndLine: 30}, {StartLine: 54, EndLine: 58}}}, - {ID: "Microsoft.NETFramework.ReferenceAssemblies.net40@1.0.0", Name: "Microsoft.NETFramework.ReferenceAssemblies.net40", Version: "1.0.0", Indirect: true, Locations: []types.Location{{StartLine: 82, EndLine: 86}}}, - {ID: "NETStandard.Library@1.6.1", Name: "NETStandard.Library", Version: "1.6.1", Indirect: false, Locations: []types.Location{{StartLine: 95, EndLine: 125}}}, - {ID: "NETStandard.Library@2.0.3", Name: "NETStandard.Library", Version: "2.0.3", Indirect: false, Locations: []types.Location{{StartLine: 445, EndLine: 453}}}, - {ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", Version: "12.0.3", Indirect: false, Locations: []types.Location{{StartLine: 20, EndLine: 25}, {StartLine: 48, EndLine: 53}, {StartLine: 76, EndLine: 81}, {StartLine: 126, EndLine: 137}, {StartLine: 454, EndLine: 459}}}, - {ID: "System.Collections@4.3.0", Name: "System.Collections", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 158, EndLine: 167}}}, - {ID: "System.ComponentModel@4.3.0", Name: "System.ComponentModel", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 168, EndLine: 175}}}, - {ID: "System.ComponentModel.Primitives@4.3.0", Name: "System.ComponentModel.Primitives", Indirect: true, Version: "4.3.0", Locations: []types.Location{{StartLine: 176, EndLine: 185}}}, - {ID: "System.ComponentModel.TypeConverter@4.3.0", Name: "System.ComponentModel.TypeConverter", Indirect: true, Version: "4.3.0", Locations: []types.Location{{StartLine: 186, EndLine: 203}}}, - {ID: "System.Diagnostics.Debug@4.3.0", Name: "System.Diagnostics.Debug", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 204, EndLine: 213}}}, - {ID: "System.Diagnostics.Tools@4.3.0", Name: "System.Diagnostics.Tools", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 214, EndLine: 223}}}, - {ID: "System.Dynamic.Runtime@4.3.0", Name: "System.Dynamic.Runtime", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 224, EndLine: 234}}}, - {ID: "System.Globalization@4.3.0", Name: "System.Globalization", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 235, EndLine: 244}}}, - {ID: "System.IO@4.3.0", Name: "System.IO", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 245, EndLine: 256}}}, - {ID: "System.Linq@4.3.0", Name: "System.Linq", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 257, EndLine: 265}}}, - {ID: "System.Linq.Expressions@4.3.0", Name: "System.Linq.Expressions", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 266, EndLine: 274}}}, - {ID: "System.Net.Primitives@4.3.0", Name: "System.Net.Primitives", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 275, EndLine: 284}}}, - {ID: "System.ObjectModel@4.3.0", Name: "System.ObjectModel", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 285, EndLine: 292}}}, - {ID: "System.Reflection@4.3.0", Name: "System.Reflection", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 293, EndLine: 304}}}, - {ID: "System.Reflection.Extensions@4.3.0", Name: "System.Reflection.Extensions", Indirect: true, Version: "4.3.0", Locations: []types.Location{{StartLine: 305, EndLine: 315}}}, - {ID: "System.Reflection.Primitives@4.3.0", Name: "System.Reflection.Primitives", Indirect: true, Version: "4.3.0", Locations: []types.Location{{StartLine: 316, EndLine: 325}}}, - {ID: "System.Resources.ResourceManager@4.3.0", Name: "System.Resources.ResourceManager", Indirect: true, Version: "4.3.0", Locations: []types.Location{{StartLine: 326, EndLine: 337}}}, - {ID: "System.Runtime@4.3.0", Name: "System.Runtime", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 338, EndLine: 346}}}, - {ID: "System.Runtime.CompilerServices.Unsafe@4.5.2", Name: "System.Runtime.CompilerServices.Unsafe", Version: "4.5.2", Indirect: true, Locations: []types.Location{{StartLine: 473, EndLine: 477}}}, - {ID: "System.Runtime.Extensions@4.3.0", Name: "System.Runtime.Extensions", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 347, EndLine: 356}}}, - {ID: "System.Runtime.Serialization.Primitives@4.3.0", Name: "System.Runtime.Serialization.Primitives", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 357, EndLine: 364}}}, - {ID: "System.Text.Encoding@4.3.0", Name: "System.Text.Encoding", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 365, EndLine: 374}}}, - {ID: "System.Text.Encoding.Extensions@4.3.0", Name: "System.Text.Encoding.Extensions", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 375, EndLine: 385}}}, - {ID: "System.Text.RegularExpressions@4.3.0", Name: "System.Text.RegularExpressions", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 386, EndLine: 393}}}, - {ID: "System.Threading@4.3.0", Name: "System.Threading", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 394, EndLine: 402}}}, - {ID: "System.Threading.Tasks@4.3.0", Name: "System.Threading.Tasks", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 403, EndLine: 412}}}, - {ID: "System.Threading.Tasks.Extensions@4.5.2", Name: "System.Threading.Tasks.Extensions", Version: "4.5.2", Indirect: true, Locations: []types.Location{{StartLine: 478, EndLine: 485}}}, - {ID: "System.Xml.ReaderWriter@4.3.0", Name: "System.Xml.ReaderWriter", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 413, EndLine: 423}}}, - {ID: "System.Xml.XDocument@4.3.0", Name: "System.Xml.XDocument", Version: "4.3.0", Indirect: true, Locations: []types.Location{{StartLine: 424, EndLine: 433}}}, + { + ID: "AWSSDK.Core@3.5.1.30", + Name: "AWSSDK.Core", + Version: "3.5.1.30", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 10, + }, + { + StartLine: 33, + EndLine: 38, + }, + { + StartLine: 61, + EndLine: 66, + }, + { + StartLine: 89, + EndLine: 94, + }, + { + StartLine: 436, + EndLine: 444, + }, + }, + }, + { + ID: "Microsoft.Bcl.AsyncInterfaces@1.1.0", + Name: "Microsoft.Bcl.AsyncInterfaces", + Version: "1.1.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 460, + EndLine: 467, + }, + }, + }, + { + ID: "Microsoft.CSharp@4.3.0", + Name: "Microsoft.CSharp", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 138, + EndLine: 147, + }, + }, + }, + { + ID: "Microsoft.NETCore.Platforms@1.1.0", + Name: "Microsoft.NETCore.Platforms", + Version: "1.1.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 148, + EndLine: 152, + }, + { + StartLine: 468, + EndLine: 472, + }, + }, + }, + { + ID: "Microsoft.NETCore.Targets@1.1.0", + Name: "Microsoft.NETCore.Targets", + Version: "1.1.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 153, + EndLine: 157, + }, + }, + }, + { + ID: "Microsoft.NETFramework.ReferenceAssemblies@1.0.0", + Name: "Microsoft.NETFramework.ReferenceAssemblies", + Version: "1.0.0", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 11, + EndLine: 19, + }, + { + StartLine: 39, + EndLine: 47, + }, + { + StartLine: 67, + EndLine: 75, + }, + }, + }, + { + ID: "Microsoft.NETFramework.ReferenceAssemblies.net20@1.0.0", + Name: "Microsoft.NETFramework.ReferenceAssemblies.net20", + Version: "1.0.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 26, + EndLine: 30, + }, + { + StartLine: 54, + EndLine: 58, + }, + }, + }, + { + ID: "Microsoft.NETFramework.ReferenceAssemblies.net40@1.0.0", + Name: "Microsoft.NETFramework.ReferenceAssemblies.net40", + Version: "1.0.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 82, + EndLine: 86, + }, + }, + }, + { + ID: "NETStandard.Library@1.6.1", + Name: "NETStandard.Library", + Version: "1.6.1", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 95, + EndLine: 125, + }, + }, + }, + { + ID: "NETStandard.Library@2.0.3", + Name: "NETStandard.Library", + Version: "2.0.3", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 445, + EndLine: 453, + }, + }, + }, + { + ID: "Newtonsoft.Json@12.0.3", + Name: "Newtonsoft.Json", + Version: "12.0.3", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 20, + EndLine: 25, + }, + { + StartLine: 48, + EndLine: 53, + }, + { + StartLine: 76, + EndLine: 81, + }, + { + StartLine: 126, + EndLine: 137, + }, + { + StartLine: 454, + EndLine: 459, + }, + }, + }, + { + ID: "System.Collections@4.3.0", + Name: "System.Collections", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 158, + EndLine: 167, + }, + }, + }, + { + ID: "System.ComponentModel@4.3.0", + Name: "System.ComponentModel", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 168, + EndLine: 175, + }, + }, + }, + { + ID: "System.ComponentModel.Primitives@4.3.0", + Name: "System.ComponentModel.Primitives", + Relationship: types.RelationshipIndirect, + Version: "4.3.0", + Locations: []types.Location{ + { + StartLine: 176, + EndLine: 185, + }, + }, + }, + { + ID: "System.ComponentModel.TypeConverter@4.3.0", + Name: "System.ComponentModel.TypeConverter", + Relationship: types.RelationshipIndirect, + Version: "4.3.0", + Locations: []types.Location{ + { + StartLine: 186, + EndLine: 203, + }, + }, + }, + { + ID: "System.Diagnostics.Debug@4.3.0", + Name: "System.Diagnostics.Debug", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 204, + EndLine: 213, + }, + }, + }, + { + ID: "System.Diagnostics.Tools@4.3.0", + Name: "System.Diagnostics.Tools", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 214, + EndLine: 223, + }, + }, + }, + { + ID: "System.Dynamic.Runtime@4.3.0", + Name: "System.Dynamic.Runtime", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 224, + EndLine: 234, + }, + }, + }, + { + ID: "System.Globalization@4.3.0", + Name: "System.Globalization", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 235, + EndLine: 244, + }, + }, + }, + { + ID: "System.IO@4.3.0", + Name: "System.IO", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 245, + EndLine: 256, + }, + }, + }, + { + ID: "System.Linq@4.3.0", + Name: "System.Linq", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 257, + EndLine: 265, + }, + }, + }, + { + ID: "System.Linq.Expressions@4.3.0", + Name: "System.Linq.Expressions", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 266, + EndLine: 274, + }, + }, + }, + { + ID: "System.Net.Primitives@4.3.0", + Name: "System.Net.Primitives", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 275, + EndLine: 284, + }, + }, + }, + { + ID: "System.ObjectModel@4.3.0", + Name: "System.ObjectModel", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 285, + EndLine: 292, + }, + }, + }, + { + ID: "System.Reflection@4.3.0", + Name: "System.Reflection", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 293, + EndLine: 304, + }, + }, + }, + { + ID: "System.Reflection.Extensions@4.3.0", + Name: "System.Reflection.Extensions", + Relationship: types.RelationshipIndirect, + Version: "4.3.0", + Locations: []types.Location{ + { + StartLine: 305, + EndLine: 315, + }, + }, + }, + { + ID: "System.Reflection.Primitives@4.3.0", + Name: "System.Reflection.Primitives", + Relationship: types.RelationshipIndirect, + Version: "4.3.0", + Locations: []types.Location{ + { + StartLine: 316, + EndLine: 325, + }, + }, + }, + { + ID: "System.Resources.ResourceManager@4.3.0", + Name: "System.Resources.ResourceManager", + Relationship: types.RelationshipIndirect, + Version: "4.3.0", + Locations: []types.Location{ + { + StartLine: 326, + EndLine: 337, + }, + }, + }, + { + ID: "System.Runtime@4.3.0", + Name: "System.Runtime", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 338, + EndLine: 346, + }, + }, + }, + { + ID: "System.Runtime.CompilerServices.Unsafe@4.5.2", + Name: "System.Runtime.CompilerServices.Unsafe", + Version: "4.5.2", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 473, + EndLine: 477, + }, + }, + }, + { + ID: "System.Runtime.Extensions@4.3.0", + Name: "System.Runtime.Extensions", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 347, + EndLine: 356, + }, + }, + }, + { + ID: "System.Runtime.Serialization.Primitives@4.3.0", + Name: "System.Runtime.Serialization.Primitives", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 357, + EndLine: 364, + }, + }, + }, + { + ID: "System.Text.Encoding@4.3.0", + Name: "System.Text.Encoding", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 365, + EndLine: 374, + }, + }, + }, + { + ID: "System.Text.Encoding.Extensions@4.3.0", + Name: "System.Text.Encoding.Extensions", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 375, + EndLine: 385, + }, + }, + }, + { + ID: "System.Text.RegularExpressions@4.3.0", + Name: "System.Text.RegularExpressions", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 386, + EndLine: 393, + }, + }, + }, + { + ID: "System.Threading@4.3.0", + Name: "System.Threading", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 394, + EndLine: 402, + }, + }, + }, + { + ID: "System.Threading.Tasks@4.3.0", + Name: "System.Threading.Tasks", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 403, + EndLine: 412, + }, + }, + }, + { + ID: "System.Threading.Tasks.Extensions@4.5.2", + Name: "System.Threading.Tasks.Extensions", + Version: "4.5.2", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 478, + EndLine: 485, + }, + }, + }, + { + ID: "System.Xml.ReaderWriter@4.3.0", + Name: "System.Xml.ReaderWriter", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 413, + EndLine: 423, + }, + }, + }, + { + ID: "System.Xml.XDocument@4.3.0", + Name: "System.Xml.XDocument", + Version: "4.3.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 424, + EndLine: 433, + }, + }, + }, } nuGetMultiTargetDeps = []types.Dependency{ - {ID: "AWSSDK.Core@3.5.1.30", DependsOn: []string{"Microsoft.Bcl.AsyncInterfaces@1.1.0"}}, - {ID: "Microsoft.Bcl.AsyncInterfaces@1.1.0", DependsOn: []string{"System.Threading.Tasks.Extensions@4.5.2"}}, - {ID: "Microsoft.CSharp@4.3.0", DependsOn: []string{"System.Dynamic.Runtime@4.3.0", "System.Linq.Expressions@4.3.0", "System.Runtime@4.3.0"}}, - {ID: "Microsoft.NETFramework.ReferenceAssemblies@1.0.0", DependsOn: []string{"Microsoft.NETFramework.ReferenceAssemblies.net20@1.0.0", "Microsoft.NETFramework.ReferenceAssemblies.net40@1.0.0"}}, - {ID: "NETStandard.Library@1.6.1", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "System.Collections@4.3.0", "System.Diagnostics.Debug@4.3.0", "System.Diagnostics.Tools@4.3.0", "System.Globalization@4.3.0", "System.IO@4.3.0", "System.Linq.Expressions@4.3.0", "System.Linq@4.3.0", "System.Net.Primitives@4.3.0", "System.ObjectModel@4.3.0", "System.Reflection.Extensions@4.3.0", "System.Reflection.Primitives@4.3.0", "System.Reflection@4.3.0", "System.Resources.ResourceManager@4.3.0", "System.Runtime.Extensions@4.3.0", "System.Runtime@4.3.0", "System.Text.Encoding.Extensions@4.3.0", "System.Text.Encoding@4.3.0", "System.Text.RegularExpressions@4.3.0", "System.Threading.Tasks@4.3.0", "System.Threading@4.3.0", "System.Xml.ReaderWriter@4.3.0", "System.Xml.XDocument@4.3.0"}}, - {ID: "NETStandard.Library@2.0.3", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0"}}, - {ID: "Newtonsoft.Json@12.0.3", DependsOn: []string{"Microsoft.CSharp@4.3.0", "NETStandard.Library@1.6.1", "System.ComponentModel.TypeConverter@4.3.0", "System.Runtime.Serialization.Primitives@4.3.0"}}, - {ID: "System.Collections@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Runtime@4.3.0"}}, - {ID: "System.ComponentModel.Primitives@4.3.0", DependsOn: []string{"System.ComponentModel@4.3.0", "System.Resources.ResourceManager@4.3.0", "System.Runtime@4.3.0"}}, - {ID: "System.ComponentModel.TypeConverter@4.3.0", DependsOn: []string{"System.Collections@4.3.0", "System.ComponentModel.Primitives@4.3.0", "System.ComponentModel@4.3.0", "System.Globalization@4.3.0", "System.Reflection.Extensions@4.3.0", "System.Reflection.Primitives@4.3.0", "System.Reflection@4.3.0", "System.Resources.ResourceManager@4.3.0", "System.Runtime.Extensions@4.3.0", "System.Runtime@4.3.0", "System.Threading@4.3.0"}}, - {ID: "System.ComponentModel@4.3.0", DependsOn: []string{"System.Runtime@4.3.0"}}, - {ID: "System.Diagnostics.Debug@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Runtime@4.3.0"}}, - {ID: "System.Diagnostics.Tools@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Runtime@4.3.0"}}, - {ID: "System.Dynamic.Runtime@4.3.0", DependsOn: []string{"System.Linq.Expressions@4.3.0", "System.ObjectModel@4.3.0", "System.Reflection@4.3.0", "System.Runtime@4.3.0"}}, - {ID: "System.Globalization@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Runtime@4.3.0"}}, - {ID: "System.IO@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Runtime@4.3.0", "System.Text.Encoding@4.3.0", "System.Threading.Tasks@4.3.0"}}, - {ID: "System.Linq.Expressions@4.3.0", DependsOn: []string{"System.Reflection@4.3.0", "System.Runtime@4.3.0"}}, - {ID: "System.Linq@4.3.0", DependsOn: []string{"System.Collections@4.3.0", "System.Runtime@4.3.0"}}, - {ID: "System.Net.Primitives@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Runtime@4.3.0"}}, - {ID: "System.ObjectModel@4.3.0", DependsOn: []string{"System.Runtime@4.3.0"}}, - {ID: "System.Reflection.Extensions@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Reflection@4.3.0", "System.Runtime@4.3.0"}}, - {ID: "System.Reflection.Primitives@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Runtime@4.3.0"}}, - {ID: "System.Reflection@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.IO@4.3.0", "System.Reflection.Primitives@4.3.0", "System.Runtime@4.3.0"}}, - {ID: "System.Resources.ResourceManager@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Globalization@4.3.0", "System.Reflection@4.3.0", "System.Runtime@4.3.0"}}, - {ID: "System.Runtime.Extensions@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Runtime@4.3.0"}}, - {ID: "System.Runtime.Serialization.Primitives@4.3.0", DependsOn: []string{"System.Runtime@4.3.0"}}, - {ID: "System.Runtime@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0"}}, - {ID: "System.Text.Encoding.Extensions@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Runtime@4.3.0", "System.Text.Encoding@4.3.0"}}, - {ID: "System.Text.Encoding@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Runtime@4.3.0"}}, - {ID: "System.Text.RegularExpressions@4.3.0", DependsOn: []string{"System.Runtime@4.3.0"}}, - {ID: "System.Threading.Tasks.Extensions@4.5.2", DependsOn: []string{"System.Runtime.CompilerServices.Unsafe@4.5.2"}}, - {ID: "System.Threading.Tasks@4.3.0", DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0", "Microsoft.NETCore.Targets@1.1.0", "System.Runtime@4.3.0"}}, - {ID: "System.Threading@4.3.0", DependsOn: []string{"System.Runtime@4.3.0", "System.Threading.Tasks@4.3.0"}}, - {ID: "System.Xml.ReaderWriter@4.3.0", DependsOn: []string{"System.IO@4.3.0", "System.Runtime@4.3.0", "System.Text.Encoding@4.3.0", "System.Threading.Tasks@4.3.0"}}, - {ID: "System.Xml.XDocument@4.3.0", DependsOn: []string{"System.IO@4.3.0", "System.Runtime@4.3.0", "System.Xml.ReaderWriter@4.3.0"}}, + { + ID: "AWSSDK.Core@3.5.1.30", + DependsOn: []string{"Microsoft.Bcl.AsyncInterfaces@1.1.0"}, + }, + { + ID: "Microsoft.Bcl.AsyncInterfaces@1.1.0", + DependsOn: []string{"System.Threading.Tasks.Extensions@4.5.2"}, + }, + { + ID: "Microsoft.CSharp@4.3.0", + DependsOn: []string{ + "System.Dynamic.Runtime@4.3.0", + "System.Linq.Expressions@4.3.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "Microsoft.NETFramework.ReferenceAssemblies@1.0.0", + DependsOn: []string{ + "Microsoft.NETFramework.ReferenceAssemblies.net20@1.0.0", + "Microsoft.NETFramework.ReferenceAssemblies.net40@1.0.0", + }, + }, + { + ID: "NETStandard.Library@1.6.1", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "System.Collections@4.3.0", + "System.Diagnostics.Debug@4.3.0", + "System.Diagnostics.Tools@4.3.0", + "System.Globalization@4.3.0", + "System.IO@4.3.0", + "System.Linq.Expressions@4.3.0", + "System.Linq@4.3.0", + "System.Net.Primitives@4.3.0", + "System.ObjectModel@4.3.0", + "System.Reflection.Extensions@4.3.0", + "System.Reflection.Primitives@4.3.0", + "System.Reflection@4.3.0", + "System.Resources.ResourceManager@4.3.0", + "System.Runtime.Extensions@4.3.0", + "System.Runtime@4.3.0", + "System.Text.Encoding.Extensions@4.3.0", + "System.Text.Encoding@4.3.0", + "System.Text.RegularExpressions@4.3.0", + "System.Threading.Tasks@4.3.0", + "System.Threading@4.3.0", + "System.Xml.ReaderWriter@4.3.0", + "System.Xml.XDocument@4.3.0", + }, + }, + { + ID: "NETStandard.Library@2.0.3", + DependsOn: []string{"Microsoft.NETCore.Platforms@1.1.0"}, + }, + { + ID: "Newtonsoft.Json@12.0.3", + DependsOn: []string{ + "Microsoft.CSharp@4.3.0", + "NETStandard.Library@1.6.1", + "System.ComponentModel.TypeConverter@4.3.0", + "System.Runtime.Serialization.Primitives@4.3.0", + }, + }, + { + ID: "System.Collections@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.ComponentModel.Primitives@4.3.0", + DependsOn: []string{ + "System.ComponentModel@4.3.0", + "System.Resources.ResourceManager@4.3.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.ComponentModel.TypeConverter@4.3.0", + DependsOn: []string{ + "System.Collections@4.3.0", + "System.ComponentModel.Primitives@4.3.0", + "System.ComponentModel@4.3.0", + "System.Globalization@4.3.0", + "System.Reflection.Extensions@4.3.0", + "System.Reflection.Primitives@4.3.0", + "System.Reflection@4.3.0", + "System.Resources.ResourceManager@4.3.0", + "System.Runtime.Extensions@4.3.0", + "System.Runtime@4.3.0", + "System.Threading@4.3.0", + }, + }, + { + ID: "System.ComponentModel@4.3.0", + DependsOn: []string{"System.Runtime@4.3.0"}, + }, + { + ID: "System.Diagnostics.Debug@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.Diagnostics.Tools@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.Dynamic.Runtime@4.3.0", + DependsOn: []string{ + "System.Linq.Expressions@4.3.0", + "System.ObjectModel@4.3.0", + "System.Reflection@4.3.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.Globalization@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.IO@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Runtime@4.3.0", + "System.Text.Encoding@4.3.0", + "System.Threading.Tasks@4.3.0", + }, + }, + { + ID: "System.Linq.Expressions@4.3.0", + DependsOn: []string{ + "System.Reflection@4.3.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.Linq@4.3.0", + DependsOn: []string{ + "System.Collections@4.3.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.Net.Primitives@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.ObjectModel@4.3.0", + DependsOn: []string{"System.Runtime@4.3.0"}, + }, + { + ID: "System.Reflection.Extensions@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Reflection@4.3.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.Reflection.Primitives@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.Reflection@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.IO@4.3.0", + "System.Reflection.Primitives@4.3.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.Resources.ResourceManager@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Globalization@4.3.0", + "System.Reflection@4.3.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.Runtime.Extensions@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.Runtime.Serialization.Primitives@4.3.0", + DependsOn: []string{"System.Runtime@4.3.0"}, + }, + { + ID: "System.Runtime@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + }, + }, + { + ID: "System.Text.Encoding.Extensions@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Runtime@4.3.0", + "System.Text.Encoding@4.3.0", + }, + }, + { + ID: "System.Text.Encoding@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.Text.RegularExpressions@4.3.0", + DependsOn: []string{"System.Runtime@4.3.0"}, + }, + { + ID: "System.Threading.Tasks.Extensions@4.5.2", + DependsOn: []string{"System.Runtime.CompilerServices.Unsafe@4.5.2"}, + }, + { + ID: "System.Threading.Tasks@4.3.0", + DependsOn: []string{ + "Microsoft.NETCore.Platforms@1.1.0", + "Microsoft.NETCore.Targets@1.1.0", + "System.Runtime@4.3.0", + }, + }, + { + ID: "System.Threading@4.3.0", + DependsOn: []string{ + "System.Runtime@4.3.0", + "System.Threading.Tasks@4.3.0", + }, + }, + { + ID: "System.Xml.ReaderWriter@4.3.0", + DependsOn: []string{ + "System.IO@4.3.0", + "System.Runtime@4.3.0", + "System.Text.Encoding@4.3.0", + "System.Threading.Tasks@4.3.0", + }, + }, + { + ID: "System.Xml.XDocument@4.3.0", + DependsOn: []string{ + "System.IO@4.3.0", + "System.Runtime@4.3.0", + "System.Xml.ReaderWriter@4.3.0", + }, + }, } ) diff --git a/pkg/dependency/parser/php/composer/parse.go b/pkg/dependency/parser/php/composer/parse.go index a41f998a5eb1..49b73c7994c8 100644 --- a/pkg/dependency/parser/php/composer/parse.go +++ b/pkg/dependency/parser/php/composer/parse.go @@ -52,11 +52,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, foundDeps := make(map[string][]string) for _, pkg := range lockFile.Packages { lib := types.Library{ - ID: dependency.ID(ftypes.Composer, pkg.Name, pkg.Version), - Name: pkg.Name, - Version: pkg.Version, - Indirect: false, // composer.lock file doesn't have info about Direct/Indirect deps. Will think that all dependencies are Direct - License: strings.Join(pkg.License, ", "), + ID: dependency.ID(ftypes.Composer, pkg.Name, pkg.Version), + Name: pkg.Name, + Version: pkg.Version, + Relationship: types.RelationshipUnknown, // composer.lock file doesn't have info about direct/indirect dependencies + License: strings.Join(pkg.License, ", "), Locations: []types.Location{ { StartLine: pkg.StartLine, diff --git a/pkg/dependency/parser/ruby/bundler/parse.go b/pkg/dependency/parser/ruby/bundler/parse.go index e8cd538e0da6..6c59eeca2d49 100644 --- a/pkg/dependency/parser/ruby/bundler/parse.go +++ b/pkg/dependency/parser/ruby/bundler/parse.go @@ -50,10 +50,10 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, name := s[0] pkgID = packageID(name, version) libs[name] = types.Library{ - ID: pkgID, - Name: name, - Version: version, - Indirect: true, + ID: pkgID, + Name: name, + Version: version, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: lineNum, @@ -86,7 +86,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // Identify which are direct dependencies for _, d := range directDeps { if l, ok := libs[d]; ok { - l.Indirect = false + l.Relationship = types.RelationshipDirect libs[d] = l } } diff --git a/pkg/dependency/parser/ruby/bundler/parse_test.go b/pkg/dependency/parser/ruby/bundler/parse_test.go index 6b2b27dd5e3a..f172225e6f02 100644 --- a/pkg/dependency/parser/ruby/bundler/parse_test.go +++ b/pkg/dependency/parser/ruby/bundler/parse_test.go @@ -15,50 +15,88 @@ import ( var ( NormalLibs = []types.Library{ { - ID: "coderay@1.1.2", - Name: "coderay", - Version: "1.1.2", - Indirect: true, - Locations: []types.Location{{StartLine: 4, EndLine: 4}}, + ID: "coderay@1.1.2", + Name: "coderay", + Version: "1.1.2", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, }, { - ID: "concurrent-ruby@1.1.5", - Name: "concurrent-ruby", - Version: "1.1.5", - Indirect: true, - Locations: []types.Location{{StartLine: 5, EndLine: 5}}, + ID: "concurrent-ruby@1.1.5", + Name: "concurrent-ruby", + Version: "1.1.5", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, }, { - ID: "dotenv@2.7.2", - Name: "dotenv", - Version: "2.7.2", - Locations: []types.Location{{StartLine: 6, EndLine: 6}}, + ID: "dotenv@2.7.2", + Name: "dotenv", + Version: "2.7.2", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 6, + EndLine: 6, + }, + }, }, { - ID: "faker@1.9.3", - Name: "faker", - Version: "1.9.3", - Locations: []types.Location{{StartLine: 7, EndLine: 7}}, + ID: "faker@1.9.3", + Name: "faker", + Version: "1.9.3", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 7, + EndLine: 7, + }, + }, }, { - ID: "i18n@1.6.0", - Name: "i18n", - Version: "1.6.0", - Indirect: true, - Locations: []types.Location{{StartLine: 9, EndLine: 9}}, + ID: "i18n@1.6.0", + Name: "i18n", + Version: "1.6.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 9, + EndLine: 9, + }, + }, }, { - ID: "method_source@0.9.2", - Name: "method_source", - Version: "0.9.2", - Indirect: true, - Locations: []types.Location{{StartLine: 11, EndLine: 11}}, + ID: "method_source@0.9.2", + Name: "method_source", + Version: "0.9.2", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 11, + EndLine: 11, + }, + }, }, { - ID: "pry@0.12.2", - Name: "pry", - Version: "0.12.2", - Locations: []types.Location{{StartLine: 12, EndLine: 12}}, + ID: "pry@0.12.2", + Name: "pry", + Version: "0.12.2", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 12, + EndLine: 12, + }, + }, }, } NormalDeps = []types.Dependency{ @@ -80,56 +118,100 @@ var ( } Bundler2Libs = []types.Library{ { - ID: "coderay@1.1.3", - Name: "coderay", - Version: "1.1.3", - Indirect: true, - Locations: []types.Location{{StartLine: 4, EndLine: 4}}, + ID: "coderay@1.1.3", + Name: "coderay", + Version: "1.1.3", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, }, { - ID: "concurrent-ruby@1.1.10", - Name: "concurrent-ruby", - Version: "1.1.10", - Indirect: true, - Locations: []types.Location{{StartLine: 5, EndLine: 5}}, + ID: "concurrent-ruby@1.1.10", + Name: "concurrent-ruby", + Version: "1.1.10", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, }, { - ID: "dotenv@2.7.6", - Name: "dotenv", - Version: "2.7.6", - Locations: []types.Location{{StartLine: 6, EndLine: 6}}, + ID: "dotenv@2.7.6", + Name: "dotenv", + Version: "2.7.6", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 6, + EndLine: 6, + }, + }, }, { - ID: "faker@2.21.0", - Name: "faker", - Version: "2.21.0", - Locations: []types.Location{{StartLine: 7, EndLine: 7}}, + ID: "faker@2.21.0", + Name: "faker", + Version: "2.21.0", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 7, + EndLine: 7, + }, + }, }, { - ID: "i18n@1.10.0", - Name: "i18n", - Version: "1.10.0", - Indirect: true, - Locations: []types.Location{{StartLine: 9, EndLine: 9}}, + ID: "i18n@1.10.0", + Name: "i18n", + Version: "1.10.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 9, + EndLine: 9, + }, + }, }, { - ID: "json@2.6.2", - Name: "json", - Version: "2.6.2", - Locations: []types.Location{{StartLine: 11, EndLine: 11}}, + ID: "json@2.6.2", + Name: "json", + Version: "2.6.2", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 11, + EndLine: 11, + }, + }, }, { - ID: "method_source@1.0.0", - Name: "method_source", - Version: "1.0.0", - Indirect: true, - Locations: []types.Location{{StartLine: 12, EndLine: 12}}, + ID: "method_source@1.0.0", + Name: "method_source", + Version: "1.0.0", + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 12, + EndLine: 12, + }, + }, }, { - ID: "pry@0.14.1", - Name: "pry", - Version: "0.14.1", - Locations: []types.Location{{StartLine: 13, EndLine: 13}}, + ID: "pry@0.14.1", + Name: "pry", + Version: "0.14.1", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 13, + EndLine: 13, + }, + }, }, } Bundler2Deps = []types.Dependency{ diff --git a/pkg/dependency/parser/rust/binary/parse.go b/pkg/dependency/parser/rust/binary/parse.go index 5ddb2cf20be6..793b626aa8d2 100644 --- a/pkg/dependency/parser/rust/binary/parse.go +++ b/pkg/dependency/parser/rust/binary/parse.go @@ -3,6 +3,7 @@ package binary import ( rustaudit "github.com/microsoft/go-rustaudit" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" @@ -51,10 +52,10 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, } pkgID := packageID(pkg.Name, pkg.Version) libs = append(libs, types.Library{ - ID: pkgID, - Name: pkg.Name, - Version: pkg.Version, - Indirect: !pkg.Root, + ID: pkgID, + Name: pkg.Name, + Version: pkg.Version, + Relationship: lo.Ternary(pkg.Root, types.RelationshipRoot, types.RelationshipUnknown), // TODO: Determine the direct dependencies by checking the dependencies of the root crate }) var childDeps []string diff --git a/pkg/dependency/parser/rust/binary/parse_test.go b/pkg/dependency/parser/rust/binary/parse_test.go index 63b0a3a705d1..8275a7ea2b72 100644 --- a/pkg/dependency/parser/rust/binary/parse_test.go +++ b/pkg/dependency/parser/rust/binary/parse_test.go @@ -16,16 +16,16 @@ import ( var ( libs = []types.Library{ { - ID: "crate_with_features@0.1.0", - Name: "crate_with_features", - Version: "0.1.0", - Indirect: false, + ID: "crate_with_features@0.1.0", + Name: "crate_with_features", + Version: "0.1.0", + Relationship: types.RelationshipRoot, }, { - ID: "library_crate@0.1.0", - Name: "library_crate", - Version: "0.1.0", - Indirect: true, + ID: "library_crate@0.1.0", + Name: "library_crate", + Version: "0.1.0", + Relationship: types.RelationshipUnknown, }, } diff --git a/pkg/dependency/types/types.go b/pkg/dependency/types/types.go index b55d2c7df6ee..be7022fc301a 100644 --- a/pkg/dependency/types/types.go +++ b/pkg/dependency/types/types.go @@ -1,6 +1,10 @@ package types import ( + "encoding/json" + + "golang.org/x/xerrors" + xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -8,8 +12,8 @@ type Library struct { ID string `json:",omitempty"` Name string Version string - Dev bool - Indirect bool `json:",omitempty"` + Dev bool `json:",omitempty"` + Relationship Relationship `json:",omitempty"` License string `json:",omitempty"` ExternalReferences []ExternalRef `json:",omitempty"` Locations Locations `json:",omitempty"` @@ -20,9 +24,17 @@ type Libraries []Library func (libs Libraries) Len() int { return len(libs) } func (libs Libraries) Less(i, j int) bool { - if libs[i].ID != libs[j].ID { // ID could be empty + switch { + case libs[i].Relationship != libs[j].Relationship: + if libs[i].Relationship == RelationshipUnknown { + return false + } else if libs[j].Relationship == RelationshipUnknown { + return true + } + return libs[i].Relationship < libs[j].Relationship + case libs[i].ID != libs[j].ID: // ID could be empty return libs[i].ID < libs[j].ID - } else if libs[i].Name != libs[j].Name { // Name could be the same + case libs[i].Name != libs[j].Name: // Name could be the same return libs[i].Name < libs[j].Name } return libs[i].Version < libs[j].Version @@ -72,3 +84,44 @@ const ( RefVCS RefType = "vcs" RefOther RefType = "other" ) + +type Relationship int + +const ( + RelationshipUnknown Relationship = iota + RelationshipRoot + RelationshipDirect + RelationshipIndirect +) + +var relationshipNames = [...]string{ + "unknown", + "root", + "direct", + "indirect", +} + +func (r Relationship) String() string { + if r <= RelationshipUnknown || int(r) >= len(relationshipNames) { + return "unknown" + } + return relationshipNames[r] +} + +func (r Relationship) MarshalJSON() ([]byte, error) { + return json.Marshal(r.String()) +} + +func (r *Relationship) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + for i, name := range relationshipNames { + if s == name { + *r = Relationship(i) + return nil + } + } + return xerrors.Errorf("invalid relationship (%s)", s) +} diff --git a/pkg/fanal/analyzer/analyzer_test.go b/pkg/fanal/analyzer/analyzer_test.go index 8fee82acf600..df1398155b2c 100644 --- a/pkg/fanal/analyzer/analyzer_test.go +++ b/pkg/fanal/analyzer/analyzer_test.go @@ -380,10 +380,11 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) { FilePath: "/app/Gemfile.lock", Libraries: types.Packages{ { - ID: "actioncable@5.2.3", - Name: "actioncable", - Version: "5.2.3", - Indirect: false, + ID: "actioncable@5.2.3", + Name: "actioncable", + Version: "5.2.3", + Indirect: false, + Relationship: types.RelationshipDirect, DependsOn: []string{ "actionpack@5.2.3", }, @@ -395,10 +396,11 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) { }, }, { - ID: "actionpack@5.2.3", - Name: "actionpack", - Version: "5.2.3", - Indirect: true, + ID: "actionpack@5.2.3", + Name: "actionpack", + Version: "5.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 6, @@ -441,10 +443,11 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) { FilePath: "/app/Gemfile-dev.lock", Libraries: types.Packages{ { - ID: "actioncable@5.2.3", - Name: "actioncable", - Version: "5.2.3", - Indirect: false, + ID: "actioncable@5.2.3", + Name: "actioncable", + Version: "5.2.3", + Indirect: false, + Relationship: types.RelationshipDirect, DependsOn: []string{ "actionpack@5.2.3", }, @@ -456,10 +459,11 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) { }, }, { - ID: "actionpack@5.2.3", - Name: "actionpack", - Version: "5.2.3", - Indirect: true, + ID: "actionpack@5.2.3", + Name: "actionpack", + Version: "5.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 6, diff --git a/pkg/fanal/analyzer/language/analyze.go b/pkg/fanal/analyzer/language/analyze.go index 8a156bece710..84b0261f86b7 100644 --- a/pkg/fanal/analyzer/language/analyze.go +++ b/pkg/fanal/analyzer/language/analyze.go @@ -117,16 +117,17 @@ func toApplication(fileType types.LangType, filePath, libFilePath string, r xio. } newPkg := types.Package{ - ID: lib.ID, - Name: lib.Name, - Version: lib.Version, - Dev: lib.Dev, - FilePath: libPath, - Indirect: lib.Indirect, - Licenses: licenses, - DependsOn: deps[lib.ID], - Locations: locs, - Digest: d, + ID: lib.ID, + Name: lib.Name, + Version: lib.Version, + Dev: lib.Dev, + FilePath: libPath, + Indirect: isIndirect(lib.Relationship), // For backward compatibility + Relationship: lib.Relationship, + Licenses: licenses, + DependsOn: deps[lib.ID], + Locations: locs, + Digest: d, } pkgs = append(pkgs, newPkg) } @@ -149,3 +150,12 @@ func calculateDigest(r xio.ReadSeekerAt) (digest.Digest, error) { return digest.CalcSHA1(r) } + +func isIndirect(rel types.Relationship) bool { + switch rel { + case types.RelationshipIndirect: + return true + default: + return false + } +} diff --git a/pkg/fanal/analyzer/language/c/conan/conan_test.go b/pkg/fanal/analyzer/language/c/conan/conan_test.go index 60f1e2a78ef5..622632c39c1f 100644 --- a/pkg/fanal/analyzer/language/c/conan/conan_test.go +++ b/pkg/fanal/analyzer/language/c/conan/conan_test.go @@ -29,9 +29,10 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) { FilePath: "conan.lock", Libraries: types.Packages{ { - ID: "openssl/3.0.5", - Name: "openssl", - Version: "3.0.5", + ID: "openssl/3.0.5", + Name: "openssl", + Version: "3.0.5", + Relationship: types.RelationshipDirect, DependsOn: []string{ "zlib/1.2.12", }, @@ -43,10 +44,11 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "zlib/1.2.12", - Name: "zlib", - Version: "1.2.12", - Indirect: true, + ID: "zlib/1.2.12", + Name: "zlib", + Version: "1.2.12", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 22, @@ -79,6 +81,7 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) { DependsOn: []string{ "zlib/1.2.12", }, + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 12, @@ -93,7 +96,8 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) { Licenses: []string{ "Zlib", }, - Indirect: true, + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 22, diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go b/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go index d8ecad82fb3f..2c57e1b3e75a 100644 --- a/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go @@ -34,29 +34,33 @@ func Test_pubSpecLockAnalyzer_Analyze(t *testing.T) { FilePath: "pubspec.lock", Libraries: types.Packages{ { - ID: "collection@1.17.0", - Name: "collection", - Version: "1.17.0", - Indirect: true, + ID: "collection@1.17.0", + Name: "collection", + Version: "1.17.0", + Indirect: true, + Relationship: types.RelationshipIndirect, }, { - ID: "crypto@3.0.3", - Name: "crypto", - Version: "3.0.3", + ID: "crypto@3.0.3", + Name: "crypto", + Version: "3.0.3", + Relationship: types.RelationshipDirect, DependsOn: []string{ "typed_data@1.3.2", }, }, { - ID: "meta@1.11.0", - Name: "meta", - Version: "1.11.0", + ID: "meta@1.11.0", + Name: "meta", + Version: "1.11.0", + Relationship: types.RelationshipDirect, }, { - ID: "typed_data@1.3.2", - Name: "typed_data", - Version: "1.3.2", - Indirect: true, + ID: "typed_data@1.3.2", + Name: "typed_data", + Version: "1.3.2", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "collection@1.17.0", }, @@ -78,26 +82,30 @@ func Test_pubSpecLockAnalyzer_Analyze(t *testing.T) { FilePath: "pubspec.lock", Libraries: types.Packages{ { - ID: "collection@1.17.0", - Name: "collection", - Version: "1.17.0", - Indirect: true, + ID: "collection@1.17.0", + Name: "collection", + Version: "1.17.0", + Indirect: true, + Relationship: types.RelationshipIndirect, }, { - ID: "crypto@3.0.3", - Name: "crypto", - Version: "3.0.3", + ID: "crypto@3.0.3", + Name: "crypto", + Version: "3.0.3", + Relationship: types.RelationshipDirect, }, { - ID: "meta@1.11.0", - Name: "meta", - Version: "1.11.0", + ID: "meta@1.11.0", + Name: "meta", + Version: "1.11.0", + Relationship: types.RelationshipDirect, }, { - ID: "typed_data@1.3.2", - Name: "typed_data", - Version: "1.3.2", - Indirect: true, + ID: "typed_data@1.3.2", + Name: "typed_data", + Version: "1.3.2", + Indirect: true, + Relationship: types.RelationshipIndirect, }, }, }, diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go index 3db74b377fc3..f0494b69d97d 100644 --- a/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go @@ -57,9 +57,10 @@ func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { FilePath: "packages.lock.json", Libraries: types.Packages{ { - ID: "Newtonsoft.Json@12.0.3", - Name: "Newtonsoft.Json", - Version: "12.0.3", + ID: "Newtonsoft.Json@12.0.3", + Name: "Newtonsoft.Json", + Version: "12.0.3", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 5, @@ -69,9 +70,10 @@ func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { Licenses: []string{"MIT"}, }, { - ID: "NuGet.Frameworks@5.7.0", - Name: "NuGet.Frameworks", - Version: "5.7.0", + ID: "NuGet.Frameworks@5.7.0", + Name: "NuGet.Frameworks", + Version: "5.7.0", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 11, @@ -98,9 +100,10 @@ func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { FilePath: "packages.lock.json", Libraries: types.Packages{ { - ID: "Newtonsoft.Json@12.0.3", - Name: "Newtonsoft.Json", - Version: "12.0.3", + ID: "Newtonsoft.Json@12.0.3", + Name: "Newtonsoft.Json", + Version: "12.0.3", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 5, @@ -110,9 +113,10 @@ func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { Licenses: []string{"MIT"}, }, { - ID: "NuGet.Frameworks@5.7.0", - Name: "NuGet.Frameworks", - Version: "5.7.0", + ID: "NuGet.Frameworks@5.7.0", + Name: "NuGet.Frameworks", + Version: "5.7.0", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 11, @@ -139,9 +143,10 @@ func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { FilePath: "packages.lock.json", Libraries: types.Packages{ { - ID: "Newtonsoft.Json@12.0.3", - Name: "Newtonsoft.Json", - Version: "12.0.3", + ID: "Newtonsoft.Json@12.0.3", + Name: "Newtonsoft.Json", + Version: "12.0.3", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 5, @@ -150,9 +155,10 @@ func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "NuGet.Frameworks@5.7.0", - Name: "NuGet.Frameworks", - Version: "5.7.0", + ID: "NuGet.Frameworks@5.7.0", + Name: "NuGet.Frameworks", + Version: "5.7.0", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 11, diff --git a/pkg/fanal/analyzer/language/golang/binary/binary_test.go b/pkg/fanal/analyzer/language/golang/binary/binary_test.go index 28a391da96d9..839084be36bd 100644 --- a/pkg/fanal/analyzer/language/golang/binary/binary_test.go +++ b/pkg/fanal/analyzer/language/golang/binary/binary_test.go @@ -29,6 +29,16 @@ func Test_gobinaryLibraryAnalyzer_Analyze(t *testing.T) { Type: types.GoBinary, FilePath: "testdata/executable_gobinary", Libraries: types.Packages{ + { + Name: "github.com/aquasecurity/test", + Version: "", + Relationship: types.RelationshipRoot, + }, + { + Name: "stdlib", + Version: "1.15.2", + Relationship: types.RelationshipDirect, + }, { Name: "github.com/aquasecurity/go-pep440-version", Version: "v0.0.0-20210121094942-22b2f8951d46", @@ -37,18 +47,10 @@ func Test_gobinaryLibraryAnalyzer_Analyze(t *testing.T) { Name: "github.com/aquasecurity/go-version", Version: "v0.0.0-20210121072130-637058cfe492", }, - { - Name: "github.com/aquasecurity/test", - Version: "", - }, { Name: "golang.org/x/xerrors", Version: "v0.0.0-20200804184101-5ec99f83aff1", }, - { - Name: "stdlib", - Version: "1.15.2", - }, }, }, }, diff --git a/pkg/fanal/analyzer/language/golang/mod/mod.go b/pkg/fanal/analyzer/language/golang/mod/mod.go index cc9b1b439a95..bc2c564429ab 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod.go @@ -205,7 +205,7 @@ func (a *gomodAnalyzer) collectDeps(modDir, pkgID string) (godeptypes.Dependency // Filter out indirect dependencies dependsOn := lo.FilterMap(libs, func(lib godeptypes.Library, index int) (string, bool) { - return lib.Name, !lib.Indirect + return lib.Name, lib.Relationship == types.RelationshipDirect }) return godeptypes.Dependency{ @@ -233,7 +233,7 @@ func parse(fsys fs.FS, path string, parser godeptypes.Parser) (*types.Applicatio func lessThanGo117(gomod *types.Application) bool { for _, lib := range gomod.Libraries { // The indirect field is populated only in Go 1.17+ - if lib.Indirect { + if lib.Relationship == types.RelationshipIndirect { return false } } @@ -259,6 +259,7 @@ func mergeGoSum(gomod, gosum *types.Application) { // This dependency doesn't exist in go.mod, so it must be an indirect dependency. lib.Indirect = true + lib.Relationship = types.RelationshipIndirect uniq[lib.Name] = lib } diff --git a/pkg/fanal/analyzer/language/golang/mod/mod_test.go b/pkg/fanal/analyzer/language/golang/mod/mod_test.go index 25137172cbc2..d9a5254e1953 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod_test.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod_test.go @@ -33,9 +33,10 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { FilePath: "go.mod", Libraries: types.Packages{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20220406074731-71021a481237", + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20220406074731-71021a481237", + Relationship: types.RelationshipDirect, Licenses: []string{ "MIT", }, @@ -44,10 +45,11 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", - Name: "golang.org/x/xerrors", - Version: "0.0.0-20200804184101-5ec99f83aff1", - Indirect: true, + ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20200804184101-5ec99f83aff1", + Relationship: types.RelationshipIndirect, + Indirect: true, }, }, }, @@ -66,9 +68,10 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { FilePath: "go.mod", Libraries: types.Packages{ { - ID: "github.com/sad/sad@v0.0.1", - Name: "github.com/sad/sad", - Version: "0.0.1", + ID: "github.com/sad/sad@v0.0.1", + Name: "github.com/sad/sad", + Version: "0.0.1", + Relationship: types.RelationshipDirect, }, }, }, @@ -88,18 +91,20 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { FilePath: "go.mod", Libraries: types.Packages{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20230219131432-590b1dfb6edd", + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20230219131432-590b1dfb6edd", + Relationship: types.RelationshipDirect, DependsOn: []string{ "github.com/BurntSushi/toml@v0.3.1", }, }, { - ID: "github.com/BurntSushi/toml@v0.3.1", - Name: "github.com/BurntSushi/toml", - Version: "0.3.1", - Indirect: true, + ID: "github.com/BurntSushi/toml@v0.3.1", + Name: "github.com/BurntSushi/toml", + Version: "0.3.1", + Relationship: types.RelationshipIndirect, + Indirect: true, Licenses: []string{ "MIT", }, @@ -121,10 +126,11 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { FilePath: "go.mod", Libraries: types.Packages{ { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20230219131432-590b1dfb6edd", - DependsOn: []string{}, + ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd", + Name: "github.com/aquasecurity/go-dep-parser", + Version: "0.0.0-20230219131432-590b1dfb6edd", + Relationship: types.RelationshipDirect, + DependsOn: []string{}, }, }, }, diff --git a/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go b/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go index b1868fecb936..0f5c67d9049c 100644 --- a/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go +++ b/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go @@ -30,10 +30,10 @@ func Test_gradleLockAnalyzer_Analyze(t *testing.T) { FilePath: "gradle.lockfile", Libraries: types.Packages{ { - ID: "junit:junit:4.13", - Name: "junit:junit", - Version: "4.13", - Indirect: true, + ID: "junit:junit:4.13", + Name: "junit:junit", + Version: "4.13", + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 4, @@ -48,10 +48,10 @@ func Test_gradleLockAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "org.hamcrest:hamcrest-core:1.3", - Name: "org.hamcrest:hamcrest-core", - Version: "1.3", - Indirect: true, + ID: "org.hamcrest:hamcrest-core:1.3", + Name: "org.hamcrest:hamcrest-core", + Version: "1.3", + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 5, @@ -74,10 +74,10 @@ func Test_gradleLockAnalyzer_Analyze(t *testing.T) { FilePath: "gradle.lockfile", Libraries: types.Packages{ { - ID: "junit:junit:4.13", - Name: "junit:junit", - Version: "4.13", - Indirect: true, + ID: "junit:junit:4.13", + Name: "junit:junit", + Version: "4.13", + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 4, @@ -86,10 +86,10 @@ func Test_gradleLockAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "org.hamcrest:hamcrest-core:1.3", - Name: "org.hamcrest:hamcrest-core", - Version: "1.3", - Indirect: true, + ID: "org.hamcrest:hamcrest-core:1.3", + Name: "org.hamcrest:hamcrest-core", + Version: "1.3", + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 5, diff --git a/pkg/fanal/analyzer/language/java/pom/pom_test.go b/pkg/fanal/analyzer/language/java/pom/pom_test.go index 6df1ea2274fc..3ea44231e7a5 100644 --- a/pkg/fanal/analyzer/language/java/pom/pom_test.go +++ b/pkg/fanal/analyzer/language/java/pom/pom_test.go @@ -30,9 +30,20 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { FilePath: "testdata/happy/pom.xml", Libraries: types.Packages{ { - ID: "com.example:example-api:2.0.0", - Name: "com.example:example-api", - Version: "2.0.0", + ID: "com.example:example:1.0.0", + Name: "com.example:example", + Version: "1.0.0", + Licenses: []string{"Apache-2.0"}, + Relationship: types.RelationshipRoot, + DependsOn: []string{ + "com.example:example-api:2.0.0", + }, + }, + { + ID: "com.example:example-api:2.0.0", + Name: "com.example:example-api", + Version: "2.0.0", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 28, @@ -40,15 +51,6 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { }, }, }, - { - ID: "com.example:example:1.0.0", - Name: "com.example:example", - Version: "1.0.0", - Licenses: []string{"Apache-2.0"}, - DependsOn: []string{ - "com.example:example-api:2.0.0", - }, - }, }, }, }, @@ -65,9 +67,20 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { FilePath: "pom.xml", Libraries: types.Packages{ { - ID: "com.example:example-api:2.0.0", - Name: "com.example:example-api", - Version: "2.0.0", + ID: "com.example:example:1.0.0", + Name: "com.example:example", + Version: "1.0.0", + Relationship: types.RelationshipRoot, + Licenses: []string{"Apache-2.0"}, + DependsOn: []string{ + "com.example:example-api:2.0.0", + }, + }, + { + ID: "com.example:example-api:2.0.0", + Name: "com.example:example-api", + Version: "2.0.0", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 28, @@ -75,15 +88,6 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { }, }, }, - { - ID: "com.example:example:1.0.0", - Name: "com.example:example", - Version: "1.0.0", - Licenses: []string{"Apache-2.0"}, - DependsOn: []string{ - "com.example:example-api:2.0.0", - }, - }, }, }, }, @@ -99,9 +103,21 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { FilePath: "testdata/mark-as-dev/src/it/example/pom.xml", Libraries: types.Packages{ { - ID: "com.example:example-api:@example.version@", - Name: "com.example:example-api", - Version: "@example.version@", + ID: "com.example:example:1.0.0", + Name: "com.example:example", + Version: "1.0.0", + Licenses: []string{"Apache-2.0"}, + Relationship: types.RelationshipRoot, + DependsOn: []string{ + "com.example:example-api:@example.version@", + }, + Dev: true, + }, + { + ID: "com.example:example-api:@example.version@", + Name: "com.example:example-api", + Version: "@example.version@", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 28, @@ -110,16 +126,6 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { }, Dev: true, }, - { - ID: "com.example:example:1.0.0", - Name: "com.example:example", - Version: "1.0.0", - Licenses: []string{"Apache-2.0"}, - DependsOn: []string{ - "com.example:example-api:@example.version@", - }, - Dev: true, - }, }, }, }, @@ -135,10 +141,11 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { FilePath: "testdata/requirements/pom.xml", Libraries: types.Packages{ { - ID: "com.example:example:2.0.0", - Name: "com.example:example", - Version: "2.0.0", - Licenses: []string{"Apache-2.0"}, + ID: "com.example:example:2.0.0", + Name: "com.example:example", + Version: "2.0.0", + Licenses: []string{"Apache-2.0"}, + Relationship: types.RelationshipRoot, }, }, }, diff --git a/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go b/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go index 9c1cc51a55d8..82130279836d 100644 --- a/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go +++ b/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go @@ -38,7 +38,6 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { ID: "@babel/parser@7.23.6", Name: "@babel/parser", Version: "7.23.6", - Indirect: true, Licenses: []string{"MIT"}, Locations: []types.Location{ { @@ -48,11 +47,10 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "ansi-colors@3.2.3", - Name: "ansi-colors", - Version: "3.2.3", - Dev: true, - Indirect: true, + ID: "ansi-colors@3.2.3", + Name: "ansi-colors", + Version: "3.2.3", + Dev: true, Locations: []types.Location{ { StartLine: 11, @@ -61,10 +59,9 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "array-flatten@1.1.1", - Name: "array-flatten", - Version: "1.1.1", - Indirect: true, + ID: "array-flatten@1.1.1", + Name: "array-flatten", + Version: "1.1.1", Locations: []types.Location{ { StartLine: 17, @@ -76,7 +73,6 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { ID: "body-parser@1.18.3", Name: "body-parser", Version: "1.18.3", - Indirect: true, DependsOn: []string{"debug@2.6.9"}, Licenses: []string{"MIT"}, Locations: []types.Location{ @@ -90,7 +86,6 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { ID: "debug@2.6.9", Name: "debug", Version: "2.6.9", - Indirect: true, DependsOn: []string{"ms@2.0.0"}, Licenses: []string{"MIT"}, Locations: []types.Location{ @@ -108,7 +103,6 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { ID: "express@4.16.4", Name: "express", Version: "4.16.4", - Indirect: true, DependsOn: []string{"debug@2.6.9"}, Licenses: []string{"MIT"}, Locations: []types.Location{ @@ -122,7 +116,6 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { ID: "ms@2.0.0", Name: "ms", Version: "2.0.0", - Indirect: true, Licenses: []string{"MIT"}, Locations: []types.Location{ { @@ -139,7 +132,6 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { ID: "ms@2.1.1", Name: "ms", Version: "2.1.1", - Indirect: true, Licenses: []string{"MIT"}, Locations: []types.Location{ { @@ -163,10 +155,9 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { FilePath: "package-lock.json", Libraries: types.Packages{ { - ID: "ms@2.1.1", - Name: "ms", - Version: "2.1.1", - Indirect: true, + ID: "ms@2.1.1", + Name: "ms", + Version: "2.1.1", Locations: []types.Location{ { StartLine: 6, diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go index 3351de538b3b..85200cccc2e7 100644 --- a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go @@ -29,9 +29,10 @@ func Test_pnpmPkgLibraryAnalyzer_Analyze(t *testing.T) { FilePath: "testdata/pnpm-lock.yaml", Libraries: types.Packages{ { - ID: "lodash@4.17.21", - Name: "lodash", - Version: "4.17.21", + ID: "lodash@4.17.21", + Name: "lodash", + Version: "4.17.21", + Relationship: types.RelationshipDirect, }, }, }, diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go index 3d654662078b..feabf4d6abc1 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go @@ -222,6 +222,7 @@ func (a yarnAnalyzer) walkDependencies(libs []types.Package, pkgIDs map[string]t // Mark as a direct dependency pkg.Indirect = false + pkg.Relationship = types.RelationshipDirect pkg.Dev = dev pkgs[pkg.ID] = pkg @@ -247,6 +248,7 @@ func (a yarnAnalyzer) walkIndirectDependencies(pkg types.Package, pkgIDs, deps m } dep.Indirect = true + dep.Relationship = types.RelationshipIndirect dep.Dev = pkg.Dev deps[dep.ID] = dep a.walkIndirectDependencies(dep, pkgIDs, deps) diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go index 57d8ca835d65..6815dd6b4127 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go @@ -28,9 +28,10 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { FilePath: "yarn.lock", Libraries: types.Packages{ { - ID: "js-tokens@2.0.0", - Name: "js-tokens", - Version: "2.0.0", + ID: "js-tokens@2.0.0", + Name: "js-tokens", + Version: "2.0.0", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 5, @@ -39,10 +40,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "js-tokens@4.0.0", - Name: "js-tokens", - Version: "4.0.0", - Indirect: true, + ID: "js-tokens@4.0.0", + Name: "js-tokens", + Version: "4.0.0", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 10, @@ -51,10 +53,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "loose-envify@1.4.0", - Name: "loose-envify", - Version: "1.4.0", - Indirect: true, + ID: "loose-envify@1.4.0", + Name: "loose-envify", + Version: "1.4.0", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 15, @@ -66,10 +69,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "object-assign@4.1.1", - Name: "object-assign", - Version: "4.1.1", - Indirect: true, + ID: "object-assign@4.1.1", + Name: "object-assign", + Version: "4.1.1", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 22, @@ -78,10 +82,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "prop-types@15.7.2", - Name: "prop-types", - Version: "15.7.2", - Dev: true, + ID: "prop-types@15.7.2", + Name: "prop-types", + Version: "15.7.2", + Dev: true, + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 27, @@ -95,11 +100,12 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "react-is@16.13.1", - Name: "react-is", - Version: "16.13.1", - Dev: true, - Indirect: true, + ID: "react-is@16.13.1", + Name: "react-is", + Version: "16.13.1", + Dev: true, + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 36, @@ -108,9 +114,10 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "scheduler@0.13.6", - Name: "scheduler", - Version: "0.13.6", + ID: "scheduler@0.13.6", + Name: "scheduler", + Version: "0.13.6", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 41, @@ -137,9 +144,10 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { FilePath: "foo/yarn.lock", Libraries: types.Packages{ { - ID: "hoek@6.1.3", - Name: "hoek", - Version: "6.1.3", + ID: "hoek@6.1.3", + Name: "hoek", + Version: "6.1.3", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 5, @@ -301,10 +309,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { FilePath: "yarn.lock", Libraries: []types.Package{ { - ID: "is-callable@1.2.7", - Name: "is-callable", - Version: "1.2.7", - Licenses: []string{"MIT"}, + ID: "is-callable@1.2.7", + Name: "is-callable", + Version: "1.2.7", + Relationship: types.RelationshipDirect, + Licenses: []string{"MIT"}, Locations: []types.Location{ { StartLine: 8, @@ -313,11 +322,12 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "is-number@6.0.0", - Name: "is-number", - Version: "6.0.0", - Licenses: []string{"MIT"}, - Indirect: true, + ID: "is-number@6.0.0", + Name: "is-number", + Version: "6.0.0", + Licenses: []string{"MIT"}, + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 15, @@ -326,11 +336,12 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "is-odd@3.0.1", - Name: "is-odd", - Version: "3.0.1", - Licenses: []string{"MIT"}, - DependsOn: []string{"is-number@6.0.0"}, + ID: "is-odd@3.0.1", + Name: "is-odd", + Version: "3.0.1", + Licenses: []string{"MIT"}, + Relationship: types.RelationshipDirect, + DependsOn: []string{"is-number@6.0.0"}, Locations: []types.Location{ { StartLine: 22, @@ -353,11 +364,12 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { FilePath: "yarn.lock", Libraries: types.Packages{ { - ID: "foo-json@0.8.33", - Name: "@types/jsonstream", - Version: "0.8.33", - Indirect: false, - Dev: true, + ID: "foo-json@0.8.33", + Name: "@types/jsonstream", + Version: "0.8.33", + Indirect: false, + Relationship: types.RelationshipDirect, + Dev: true, Locations: []types.Location{ { StartLine: 19, @@ -369,11 +381,12 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "@types/node@20.10.5", - Name: "@types/node", - Version: "20.10.5", - Indirect: true, - Dev: true, + ID: "@types/node@20.10.5", + Name: "@types/node", + Version: "20.10.5", + Indirect: true, + Relationship: types.RelationshipIndirect, + Dev: true, Locations: []types.Location{ { StartLine: 5, @@ -385,11 +398,12 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "foo-uuid@9.0.7", - Name: "@types/uuid", - Version: "9.0.7", - Indirect: false, - Dev: true, + ID: "foo-uuid@9.0.7", + Name: "@types/uuid", + Version: "9.0.7", + Indirect: false, + Relationship: types.RelationshipDirect, + Dev: true, Locations: []types.Location{ { StartLine: 31, @@ -398,10 +412,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "foo-debug@4.3.4", - Name: "debug", - Version: "4.3.4", - Indirect: false, + ID: "foo-debug@4.3.4", + Name: "debug", + Version: "4.3.4", + Indirect: false, + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 12, @@ -413,10 +428,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "ms@2.1.2", - Name: "ms", - Version: "2.1.2", - Indirect: true, + ID: "ms@2.1.2", + Name: "ms", + Version: "2.1.2", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 36, @@ -425,10 +441,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "foo-ms@2.1.3", - Name: "ms", - Version: "2.1.3", - Indirect: false, + ID: "foo-ms@2.1.3", + Name: "ms", + Version: "2.1.3", + Indirect: false, + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 26, @@ -437,11 +454,12 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "undici-types@5.26.5", - Name: "undici-types", - Version: "5.26.5", - Indirect: true, - Dev: true, + ID: "undici-types@5.26.5", + Name: "undici-types", + Version: "5.26.5", + Indirect: true, + Relationship: types.RelationshipIndirect, + Dev: true, Locations: []types.Location{ { StartLine: 41, @@ -464,10 +482,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { FilePath: "yarn.lock", Libraries: types.Packages{ { - ID: "is-number@6.0.0", - Name: "is-number", - Version: "6.0.0", - Indirect: true, + ID: "is-number@6.0.0", + Name: "is-number", + Version: "6.0.0", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 16, @@ -476,9 +495,10 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "is-number@7.0.0", - Name: "is-number", - Version: "7.0.0", + ID: "is-number@7.0.0", + Name: "is-number", + Version: "7.0.0", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 23, @@ -487,10 +507,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "is-odd@3.0.1", - Name: "is-odd", - Version: "3.0.1", - DependsOn: []string{"is-number@6.0.0"}, + ID: "is-odd@3.0.1", + Name: "is-odd", + Version: "3.0.1", + Relationship: types.RelationshipDirect, + DependsOn: []string{"is-number@6.0.0"}, Locations: []types.Location{ { StartLine: 30, @@ -499,10 +520,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "js-tokens@4.0.0", - Name: "js-tokens", - Version: "4.0.0", - Indirect: true, + ID: "js-tokens@4.0.0", + Name: "js-tokens", + Version: "4.0.0", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 39, @@ -511,9 +533,10 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "js-tokens@8.0.1", - Name: "js-tokens", - Version: "8.0.1", + ID: "js-tokens@8.0.1", + Name: "js-tokens", + Version: "8.0.1", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 46, @@ -522,11 +545,12 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "loose-envify@1.4.0", - Name: "loose-envify", - Version: "1.4.0", - Indirect: true, - DependsOn: []string{"js-tokens@4.0.0"}, + ID: "loose-envify@1.4.0", + Name: "loose-envify", + Version: "1.4.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"js-tokens@4.0.0"}, Locations: []types.Location{ { StartLine: 53, @@ -535,11 +559,12 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "object-assign@4.1.1", - Name: "object-assign", - Version: "4.1.1", - Indirect: true, - Dev: true, + ID: "object-assign@4.1.1", + Name: "object-assign", + Version: "4.1.1", + Indirect: true, + Relationship: types.RelationshipIndirect, + Dev: true, Locations: []types.Location{ { StartLine: 64, @@ -548,10 +573,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "prettier@2.8.8", - Name: "prettier", - Version: "2.8.8", - Dev: true, + ID: "prettier@2.8.8", + Name: "prettier", + Version: "2.8.8", + Relationship: types.RelationshipDirect, + Dev: true, Locations: []types.Location{ { StartLine: 87, @@ -560,10 +586,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "prop-types@15.8.1", - Name: "prop-types", - Version: "15.8.1", - Dev: true, + ID: "prop-types@15.8.1", + Name: "prop-types", + Version: "15.8.1", + Relationship: types.RelationshipDirect, + Dev: true, Locations: []types.Location{ { StartLine: 96, @@ -577,11 +604,12 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "react-is@16.13.1", - Name: "react-is", - Version: "16.13.1", - Dev: true, - Indirect: true, + ID: "react-is@16.13.1", + Name: "react-is", + Version: "16.13.1", + Indirect: true, + Relationship: types.RelationshipIndirect, + Dev: true, Locations: []types.Location{ { StartLine: 107, @@ -590,10 +618,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "scheduler@0.23.0", - Name: "scheduler", - Version: "0.23.0", - DependsOn: []string{"loose-envify@1.4.0"}, + ID: "scheduler@0.23.0", + Name: "scheduler", + Version: "0.23.0", + Relationship: types.RelationshipDirect, + DependsOn: []string{"loose-envify@1.4.0"}, Locations: []types.Location{ { StartLine: 114, @@ -620,10 +649,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { FilePath: "yarn.lock", Libraries: []types.Package{ { - ID: "@babel/parser@7.22.7", - Name: "@babel/parser", - Version: "7.22.7", - Indirect: true, + ID: "@babel/parser@7.22.7", + Name: "@babel/parser", + Version: "7.22.7", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 5, @@ -633,10 +663,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { Licenses: []string{"MIT"}, }, { - ID: "@vue/compiler-sfc@2.7.14", - Name: "@vue/compiler-sfc", - Version: "2.7.14", - Indirect: false, + ID: "@vue/compiler-sfc@2.7.14", + Name: "@vue/compiler-sfc", + Version: "2.7.14", + Indirect: false, + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 10, @@ -651,10 +682,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "nanoid@3.3.6", - Name: "nanoid", - Version: "3.3.6", - Indirect: true, + ID: "nanoid@3.3.6", + Name: "nanoid", + Version: "3.3.6", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 19, @@ -664,10 +696,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { Licenses: []string{"MIT"}, }, { - ID: "picocolors@1.0.0", - Name: "picocolors", - Version: "1.0.0", - Indirect: true, + ID: "picocolors@1.0.0", + Name: "picocolors", + Version: "1.0.0", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 24, @@ -677,10 +710,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { Licenses: []string{"ISC"}, }, { - ID: "postcss@8.4.27", - Name: "postcss", - Version: "8.4.27", - Indirect: true, + ID: "postcss@8.4.27", + Name: "postcss", + Version: "8.4.27", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 29, @@ -695,10 +729,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "source-map@0.6.1", - Name: "source-map", - Version: "0.6.1", - Indirect: true, + ID: "source-map@0.6.1", + Name: "source-map", + Version: "0.6.1", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 43, @@ -708,10 +743,11 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { Licenses: []string{"BSD-3-Clause"}, }, { - ID: "source-map-js@1.0.2", - Name: "source-map-js", - Version: "1.0.2", - Indirect: true, + ID: "source-map-js@1.0.2", + Name: "source-map-js", + Version: "1.0.2", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 38, diff --git a/pkg/fanal/analyzer/language/php/composer/composer.go b/pkg/fanal/analyzer/language/php/composer/composer.go index 6fffecf05a50..06f8e7487c62 100644 --- a/pkg/fanal/analyzer/language/php/composer/composer.go +++ b/pkg/fanal/analyzer/language/php/composer/composer.go @@ -118,8 +118,11 @@ func (a composerAnalyzer) mergeComposerJson(fsys fs.FS, dir string, app *types.A for i, lib := range app.Libraries { // Identify the direct/transitive dependencies - if _, ok := p[lib.Name]; !ok { + if _, ok := p[lib.Name]; ok { + app.Libraries[i].Relationship = types.RelationshipDirect + } else { app.Libraries[i].Indirect = true + app.Libraries[i].Relationship = types.RelationshipIndirect } } diff --git a/pkg/fanal/analyzer/language/php/composer/composer_test.go b/pkg/fanal/analyzer/language/php/composer/composer_test.go index e420375dbaea..61cc3af8bc50 100644 --- a/pkg/fanal/analyzer/language/php/composer/composer_test.go +++ b/pkg/fanal/analyzer/language/php/composer/composer_test.go @@ -28,11 +28,12 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { FilePath: "composer.lock", Libraries: types.Packages{ { - ID: "pear/log@1.13.3", - Name: "pear/log", - Version: "1.13.3", - Indirect: false, - Licenses: []string{"MIT"}, + ID: "pear/log@1.13.3", + Name: "pear/log", + Version: "1.13.3", + Indirect: false, + Relationship: types.RelationshipDirect, + Licenses: []string{"MIT"}, Locations: []types.Location{ { StartLine: 9, @@ -42,11 +43,12 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { DependsOn: []string{"pear/pear_exception@v1.0.2"}, }, { - ID: "pear/pear_exception@v1.0.2", - Name: "pear/pear_exception", - Version: "v1.0.2", - Indirect: true, - Licenses: []string{"BSD-2-Clause"}, + ID: "pear/pear_exception@v1.0.2", + Name: "pear/pear_exception", + Version: "v1.0.2", + Indirect: true, + Relationship: types.RelationshipIndirect, + Licenses: []string{"BSD-2-Clause"}, Locations: []types.Location{ { StartLine: 69, @@ -69,11 +71,12 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { FilePath: "composer.lock", Libraries: types.Packages{ { - ID: "pear/log@1.13.3", - Name: "pear/log", - Version: "1.13.3", - Indirect: false, - Licenses: []string{"MIT"}, + ID: "pear/log@1.13.3", + Name: "pear/log", + Version: "1.13.3", + Indirect: false, + Relationship: types.RelationshipUnknown, + Licenses: []string{"MIT"}, Locations: []types.Location{ { StartLine: 9, @@ -83,11 +86,12 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { DependsOn: []string{"pear/pear_exception@v1.0.2"}, }, { - ID: "pear/pear_exception@v1.0.2", - Name: "pear/pear_exception", - Version: "v1.0.2", - Indirect: false, - Licenses: []string{"BSD-2-Clause"}, + ID: "pear/pear_exception@v1.0.2", + Name: "pear/pear_exception", + Version: "v1.0.2", + Indirect: false, + Relationship: types.RelationshipUnknown, + Licenses: []string{"BSD-2-Clause"}, Locations: []types.Location{ { StartLine: 69, @@ -110,11 +114,12 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { FilePath: "composer.lock", Libraries: types.Packages{ { - ID: "pear/log@1.13.3", - Name: "pear/log", - Version: "1.13.3", - Indirect: false, - Licenses: []string{"MIT"}, + ID: "pear/log@1.13.3", + Name: "pear/log", + Version: "1.13.3", + Indirect: false, + Relationship: types.RelationshipUnknown, + Licenses: []string{"MIT"}, Locations: []types.Location{ { StartLine: 9, @@ -124,11 +129,12 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { DependsOn: []string{"pear/pear_exception@v1.0.2"}, }, { - ID: "pear/pear_exception@v1.0.2", - Name: "pear/pear_exception", - Version: "v1.0.2", - Indirect: false, - Licenses: []string{"BSD-2-Clause"}, + ID: "pear/pear_exception@v1.0.2", + Name: "pear/pear_exception", + Version: "v1.0.2", + Indirect: false, + Relationship: types.RelationshipUnknown, + Licenses: []string{"BSD-2-Clause"}, Locations: []types.Location{ { StartLine: 69, diff --git a/pkg/fanal/analyzer/language/python/poetry/poetry.go b/pkg/fanal/analyzer/language/python/poetry/poetry.go index 8a68a61439c3..bff6ae68e129 100644 --- a/pkg/fanal/analyzer/language/python/poetry/poetry.go +++ b/pkg/fanal/analyzer/language/python/poetry/poetry.go @@ -105,8 +105,11 @@ func (a poetryAnalyzer) mergePyProject(fsys fs.FS, dir string, app *types.Applic for i, lib := range app.Libraries { // Identify the direct/transitive dependencies - if _, ok := p[lib.Name]; !ok { + if _, ok := p[lib.Name]; ok { + app.Libraries[i].Relationship = types.RelationshipDirect + } else { app.Libraries[i].Indirect = true + app.Libraries[i].Relationship = types.RelationshipIndirect } } diff --git a/pkg/fanal/analyzer/language/python/poetry/poetry_test.go b/pkg/fanal/analyzer/language/python/poetry/poetry_test.go index f4becb554cec..49cc06c420b6 100644 --- a/pkg/fanal/analyzer/language/python/poetry/poetry_test.go +++ b/pkg/fanal/analyzer/language/python/poetry/poetry_test.go @@ -28,27 +28,31 @@ func Test_poetryLibraryAnalyzer_Analyze(t *testing.T) { FilePath: "poetry.lock", Libraries: types.Packages{ { - ID: "certifi@2022.12.7", - Name: "certifi", - Version: "2022.12.7", - Indirect: true, + ID: "certifi@2022.12.7", + Name: "certifi", + Version: "2022.12.7", + Indirect: true, + Relationship: types.RelationshipIndirect, }, { - ID: "charset-normalizer@2.1.1", - Name: "charset-normalizer", - Version: "2.1.1", - Indirect: true, + ID: "charset-normalizer@2.1.1", + Name: "charset-normalizer", + Version: "2.1.1", + Indirect: true, + Relationship: types.RelationshipIndirect, }, { - ID: "click@7.1.2", - Name: "click", - Version: "7.1.2", - Indirect: true, + ID: "click@7.1.2", + Name: "click", + Version: "7.1.2", + Indirect: true, + Relationship: types.RelationshipIndirect, }, { - ID: "flask@1.1.4", - Name: "flask", - Version: "1.1.4", + ID: "flask@1.1.4", + Name: "flask", + Version: "1.1.4", + Relationship: types.RelationshipDirect, DependsOn: []string{ "click@7.1.2", "itsdangerous@1.1.0", @@ -57,36 +61,41 @@ func Test_poetryLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "idna@3.4", - Name: "idna", - Version: "3.4", - Indirect: true, + ID: "idna@3.4", + Name: "idna", + Version: "3.4", + Indirect: true, + Relationship: types.RelationshipIndirect, }, { - ID: "itsdangerous@1.1.0", - Name: "itsdangerous", - Version: "1.1.0", - Indirect: true, + ID: "itsdangerous@1.1.0", + Name: "itsdangerous", + Version: "1.1.0", + Indirect: true, + Relationship: types.RelationshipIndirect, }, { - ID: "jinja2@2.11.3", - Name: "jinja2", - Version: "2.11.3", - Indirect: true, + ID: "jinja2@2.11.3", + Name: "jinja2", + Version: "2.11.3", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "markupsafe@2.1.2", }, }, { - ID: "markupsafe@2.1.2", - Name: "markupsafe", - Version: "2.1.2", - Indirect: true, + ID: "markupsafe@2.1.2", + Name: "markupsafe", + Version: "2.1.2", + Indirect: true, + Relationship: types.RelationshipIndirect, }, { - ID: "requests@2.28.1", - Name: "requests", - Version: "2.28.1", + ID: "requests@2.28.1", + Name: "requests", + Version: "2.28.1", + Relationship: types.RelationshipDirect, DependsOn: []string{ "certifi@2022.12.7", "charset-normalizer@2.1.1", @@ -95,16 +104,18 @@ func Test_poetryLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "urllib3@1.26.14", - Name: "urllib3", - Version: "1.26.14", - Indirect: true, + ID: "urllib3@1.26.14", + Name: "urllib3", + Version: "1.26.14", + Indirect: true, + Relationship: types.RelationshipIndirect, }, { - ID: "werkzeug@1.0.1", - Name: "werkzeug", - Version: "1.0.1", - Indirect: true, + ID: "werkzeug@1.0.1", + Name: "werkzeug", + Version: "1.0.1", + Indirect: true, + Relationship: types.RelationshipIndirect, }, }, }, diff --git a/pkg/fanal/analyzer/language/rust/binary/binary_test.go b/pkg/fanal/analyzer/language/rust/binary/binary_test.go index 19b339670559..b2a87e52d63a 100644 --- a/pkg/fanal/analyzer/language/rust/binary/binary_test.go +++ b/pkg/fanal/analyzer/language/rust/binary/binary_test.go @@ -30,16 +30,17 @@ func Test_rustBinaryLibraryAnalyzer_Analyze(t *testing.T) { FilePath: "testdata/executable_rust", Libraries: types.Packages{ { - ID: "crate_with_features@0.1.0", - Name: "crate_with_features", - Version: "0.1.0", - DependsOn: []string{"library_crate@0.1.0"}, + ID: "crate_with_features@0.1.0", + Name: "crate_with_features", + Version: "0.1.0", + Relationship: types.RelationshipRoot, + DependsOn: []string{"library_crate@0.1.0"}, }, { - ID: "library_crate@0.1.0", - Name: "library_crate", - Version: "0.1.0", - Indirect: true, + ID: "library_crate@0.1.0", + Name: "library_crate", + Version: "0.1.0", + Relationship: types.RelationshipUnknown, }, }, }, diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo.go b/pkg/fanal/analyzer/language/rust/cargo/cargo.go index ba0654a4942f..e4a6bcc4736b 100644 --- a/pkg/fanal/analyzer/language/rust/cargo/cargo.go +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo.go @@ -136,6 +136,7 @@ func (a cargoAnalyzer) removeDevDependencies(fsys fs.FS, dir string, app *types. } else if match { // Mark as a direct dependency pkg.Indirect = false + pkg.Relationship = types.RelationshipDirect pkgs[pkg.ID] = pkg break } @@ -224,6 +225,7 @@ func (a cargoAnalyzer) walkIndirectDependencies(pkg types.Package, pkgIDs, deps } dep.Indirect = true + dep.Relationship = types.RelationshipIndirect deps[dep.ID] = dep a.walkIndirectDependencies(dep, pkgIDs, deps) } diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go b/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go index ef699f4eff72..c5fb27278069 100644 --- a/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go @@ -29,10 +29,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { FilePath: "Cargo.lock", Libraries: types.Packages{ { - ID: "aho-corasick@0.7.20", - Name: "aho-corasick", - Version: "0.7.20", - Indirect: true, + ID: "aho-corasick@0.7.20", + Name: "aho-corasick", + Version: "0.7.20", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 4, @@ -42,10 +43,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { DependsOn: []string{"memchr@2.5.0"}, }, { - ID: "libc@0.2.140", - Name: "libc", - Version: "0.2.140", - Indirect: true, + ID: "libc@0.2.140", + Name: "libc", + Version: "0.2.140", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 22, @@ -54,10 +56,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "memchr@1.0.2", - Name: "memchr", - Version: "1.0.2", - Indirect: false, + ID: "memchr@1.0.2", + Name: "memchr", + Version: "1.0.2", + Indirect: false, + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 28, @@ -67,10 +70,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { DependsOn: []string{"libc@0.2.140"}, }, { - ID: "memchr@2.5.0", - Name: "memchr", - Version: "2.5.0", - Indirect: true, + ID: "memchr@2.5.0", + Name: "memchr", + Version: "2.5.0", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 37, @@ -79,10 +83,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "regex@1.7.3", - Name: "regex", - Version: "1.7.3", - Indirect: false, + ID: "regex@1.7.3", + Name: "regex", + Version: "1.7.3", + Indirect: false, + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 43, @@ -96,10 +101,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "regex-syntax@0.5.6", - Name: "regex-syntax", - Version: "0.5.6", - Indirect: false, + ID: "regex-syntax@0.5.6", + Name: "regex-syntax", + Version: "0.5.6", + Indirect: false, + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 54, @@ -109,10 +115,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { DependsOn: []string{"ucd-util@0.1.10"}, }, { - ID: "regex-syntax@0.6.29", - Name: "regex-syntax", - Version: "0.6.29", - Indirect: true, + ID: "regex-syntax@0.6.29", + Name: "regex-syntax", + Version: "0.6.29", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 63, @@ -121,10 +128,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "ucd-util@0.1.10", - Name: "ucd-util", - Version: "0.1.10", - Indirect: true, + ID: "ucd-util@0.1.10", + Name: "ucd-util", + Version: "0.1.10", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 69, @@ -147,10 +155,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { FilePath: "Cargo.lock", Libraries: types.Packages{ { - ID: "memchr@2.5.0", - Name: "memchr", - Version: "2.5.0", - Indirect: false, + ID: "memchr@2.5.0", + Name: "memchr", + Version: "2.5.0", + Indirect: false, + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 11, @@ -173,10 +182,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { FilePath: "Cargo.lock", Libraries: types.Packages{ { - ID: "aho-corasick@0.7.20", - Name: "aho-corasick", - Version: "0.7.20", - Indirect: false, + ID: "aho-corasick@0.7.20", + Name: "aho-corasick", + Version: "0.7.20", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 4, @@ -186,10 +196,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { DependsOn: []string{"memchr@2.5.0"}, }, { - ID: "app@0.1.0", - Name: "app", - Version: "0.1.0", - Indirect: false, + ID: "app@0.1.0", + Name: "app", + Version: "0.1.0", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 13, @@ -203,10 +214,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "libc@0.2.140", - Name: "libc", - Version: "0.2.140", - Indirect: false, + ID: "libc@0.2.140", + Name: "libc", + Version: "0.2.140", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 22, @@ -215,10 +227,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "memchr@1.0.2", - Name: "memchr", - Version: "1.0.2", - Indirect: false, + ID: "memchr@1.0.2", + Name: "memchr", + Version: "1.0.2", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 28, @@ -228,10 +241,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { DependsOn: []string{"libc@0.2.140"}, }, { - ID: "memchr@2.5.0", - Name: "memchr", - Version: "2.5.0", - Indirect: false, + ID: "memchr@2.5.0", + Name: "memchr", + Version: "2.5.0", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 37, @@ -240,10 +254,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "regex@1.7.3", - Name: "regex", - Version: "1.7.3", - Indirect: false, + ID: "regex@1.7.3", + Name: "regex", + Version: "1.7.3", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 43, @@ -257,10 +272,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "regex-syntax@0.5.6", - Name: "regex-syntax", - Version: "0.5.6", - Indirect: false, + ID: "regex-syntax@0.5.6", + Name: "regex-syntax", + Version: "0.5.6", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 54, @@ -270,10 +286,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { DependsOn: []string{"ucd-util@0.1.10"}, }, { - ID: "regex-syntax@0.6.29", - Name: "regex-syntax", - Version: "0.6.29", - Indirect: false, + ID: "regex-syntax@0.6.29", + Name: "regex-syntax", + Version: "0.6.29", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 63, @@ -282,10 +299,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "ucd-util@0.1.10", - Name: "ucd-util", - Version: "0.1.10", - Indirect: false, + ID: "ucd-util@0.1.10", + Name: "ucd-util", + Version: "0.1.10", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 69, @@ -294,10 +312,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "winapi@0.3.9", - Name: "winapi", - Version: "0.3.9", - Indirect: false, + ID: "winapi@0.3.9", + Name: "winapi", + Version: "0.3.9", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 75, @@ -310,10 +329,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "winapi-i686-pc-windows-gnu@0.4.0", - Name: "winapi-i686-pc-windows-gnu", - Version: "0.4.0", - Indirect: false, + ID: "winapi-i686-pc-windows-gnu@0.4.0", + Name: "winapi-i686-pc-windows-gnu", + Version: "0.4.0", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 85, @@ -322,10 +342,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "winapi-x86_64-pc-windows-gnu@0.4.0", - Name: "winapi-x86_64-pc-windows-gnu", - Version: "0.4.0", - Indirect: false, + ID: "winapi-x86_64-pc-windows-gnu@0.4.0", + Name: "winapi-x86_64-pc-windows-gnu", + Version: "0.4.0", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 91, @@ -348,10 +369,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { FilePath: "Cargo.lock", Libraries: types.Packages{ { - ID: "app@0.1.0", - Name: "app", - Version: "0.1.0", - Indirect: false, + ID: "app@0.1.0", + Name: "app", + Version: "0.1.0", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 5, @@ -361,10 +383,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { DependsOn: []string{"memchr@2.5.0"}, }, { - ID: "memchr@2.5.0", - Name: "memchr", - Version: "2.5.0", - Indirect: false, + ID: "memchr@2.5.0", + Name: "memchr", + Version: "2.5.0", + Indirect: false, + Relationship: types.RelationshipUnknown, Locations: []types.Location{ { StartLine: 12, @@ -392,10 +415,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { FilePath: "Cargo.lock", Libraries: types.Packages{ { - ID: "aho-corasick@1.1.2", - Name: "aho-corasick", - Version: "1.1.2", - Indirect: true, + ID: "aho-corasick@1.1.2", + Name: "aho-corasick", + Version: "1.1.2", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 5, @@ -405,10 +429,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { DependsOn: []string{"memchr@2.6.4"}, }, { - ID: "gdb-command@0.7.6", - Name: "gdb-command", - Version: "0.7.6", - Indirect: false, + ID: "gdb-command@0.7.6", + Name: "gdb-command", + Version: "0.7.6", + Indirect: false, + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 14, @@ -421,10 +446,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "libc@0.2.150", - Name: "libc", - Version: "0.2.150", - Indirect: true, + ID: "libc@0.2.150", + Name: "libc", + Version: "0.2.150", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 24, @@ -433,10 +459,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "memchr@2.6.4", - Name: "memchr", - Version: "2.6.4", - Indirect: true, + ID: "memchr@2.6.4", + Name: "memchr", + Version: "2.6.4", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 44, @@ -445,9 +472,10 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "regex@1.10.2", - Name: "regex", - Version: "1.10.2", + ID: "regex@1.10.2", + Name: "regex", + Version: "1.10.2", + Relationship: types.RelationshipDirect, Locations: []types.Location{ { StartLine: 50, @@ -462,10 +490,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "regex-automata@0.4.3", - Name: "regex-automata", - Version: "0.4.3", - Indirect: true, + ID: "regex-automata@0.4.3", + Name: "regex-automata", + Version: "0.4.3", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 62, @@ -479,10 +508,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "regex-syntax@0.8.2", - Name: "regex-syntax", - Version: "0.8.2", - Indirect: true, + ID: "regex-syntax@0.8.2", + Name: "regex-syntax", + Version: "0.8.2", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 73, @@ -491,10 +521,11 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "wait-timeout@0.2.0", - Name: "wait-timeout", - Version: "0.2.0", - Indirect: true, + ID: "wait-timeout@0.2.0", + Name: "wait-timeout", + Version: "0.2.0", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { StartLine: 79, diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go index a99823dde2c0..d741b78e4c9f 100644 --- a/pkg/fanal/artifact/image/image_test.go +++ b/pkg/fanal/artifact/image/image_test.go @@ -910,10 +910,11 @@ func TestArtifact_Inspect(t *testing.T) { FilePath: "ruby-app/Gemfile.lock", Libraries: types.Packages{ { - ID: "actioncable@5.2.3", - Name: "actioncable", - Version: "5.2.3", - Indirect: true, + ID: "actioncable@5.2.3", + Name: "actioncable", + Version: "5.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "actionpack@5.2.3", "nio4r@2.3.1", @@ -927,10 +928,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "actionmailer@5.2.3", - Name: "actionmailer", - Version: "5.2.3", - Indirect: true, + ID: "actionmailer@5.2.3", + Name: "actionmailer", + Version: "5.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "actionpack@5.2.3", "actionview@5.2.3", @@ -946,10 +948,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "actionpack@5.2.3", - Name: "actionpack", - Version: "5.2.3", - Indirect: true, + ID: "actionpack@5.2.3", + Name: "actionpack", + Version: "5.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "actionview@5.2.3", "activesupport@5.2.3", @@ -966,10 +969,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "actionview@5.2.3", - Name: "actionview", - Version: "5.2.3", - Indirect: true, + ID: "actionview@5.2.3", + Name: "actionview", + Version: "5.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "activesupport@5.2.3", "builder@3.2.3", @@ -985,10 +989,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "activejob@5.2.3", - Name: "activejob", - Version: "5.2.3", - Indirect: true, + ID: "activejob@5.2.3", + Name: "activejob", + Version: "5.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "activesupport@5.2.3", "globalid@0.4.2", @@ -1001,11 +1006,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "activemodel@5.2.3", - Name: "activemodel", - Version: "5.2.3", - Indirect: true, - DependsOn: []string{"activesupport@5.2.3"}, + ID: "activemodel@5.2.3", + Name: "activemodel", + Version: "5.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"activesupport@5.2.3"}, Locations: []types.Location{ { StartLine: 30, @@ -1014,10 +1020,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "activerecord@5.2.3", - Name: "activerecord", - Version: "5.2.3", - Indirect: true, + ID: "activerecord@5.2.3", + Name: "activerecord", + Version: "5.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "activemodel@5.2.3", "activesupport@5.2.3", @@ -1031,10 +1038,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "activestorage@5.2.3", - Name: "activestorage", - Version: "5.2.3", - Indirect: true, + ID: "activestorage@5.2.3", + Name: "activestorage", + Version: "5.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "actionpack@5.2.3", "activerecord@5.2.3", @@ -1048,10 +1056,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "activesupport@5.2.3", - Name: "activesupport", - Version: "5.2.3", - Indirect: true, + ID: "activesupport@5.2.3", + Name: "activesupport", + Version: "5.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "concurrent-ruby@1.1.5", "i18n@1.6.0", @@ -1066,11 +1075,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "arel@9.0.0", - Name: "arel", - Version: "9.0.0", - Indirect: true, - DependsOn: []string(nil), + ID: "arel@9.0.0", + Name: "arel", + Version: "9.0.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 45, @@ -1079,11 +1089,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "ast@2.4.0", - Name: "ast", - Version: "2.4.0", - Indirect: true, - DependsOn: []string(nil), + ID: "ast@2.4.0", + Name: "ast", + Version: "2.4.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 46, @@ -1092,11 +1103,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "builder@3.2.3", - Name: "builder", - Version: "3.2.3", - Indirect: true, - DependsOn: []string(nil), + ID: "builder@3.2.3", + Name: "builder", + Version: "3.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 47, @@ -1105,11 +1117,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "coderay@1.1.2", - Name: "coderay", - Version: "1.1.2", - Indirect: true, - DependsOn: []string(nil), + ID: "coderay@1.1.2", + Name: "coderay", + Version: "1.1.2", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 48, @@ -1118,11 +1131,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "concurrent-ruby@1.1.5", - Name: "concurrent-ruby", - Version: "1.1.5", - Indirect: true, - DependsOn: []string(nil), + ID: "concurrent-ruby@1.1.5", + Name: "concurrent-ruby", + Version: "1.1.5", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 49, @@ -1131,11 +1145,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "crass@1.0.4", - Name: "crass", - Version: "1.0.4", - Indirect: true, - DependsOn: []string(nil), + ID: "crass@1.0.4", + Name: "crass", + Version: "1.0.4", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 50, @@ -1144,11 +1159,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "dotenv@2.7.2", - Name: "dotenv", - Version: "2.7.2", - Indirect: false, - DependsOn: []string(nil), + ID: "dotenv@2.7.2", + Name: "dotenv", + Version: "2.7.2", + Indirect: false, + Relationship: types.RelationshipDirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 51, @@ -1157,11 +1173,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "erubi@1.8.0", - Name: "erubi", - Version: "1.8.0", - Indirect: true, - DependsOn: []string(nil), + ID: "erubi@1.8.0", + Name: "erubi", + Version: "1.8.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 52, @@ -1170,11 +1187,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "faker@1.9.3", - Name: "faker", - Version: "1.9.3", - Indirect: false, - DependsOn: []string{"i18n@1.6.0"}, + ID: "faker@1.9.3", + Name: "faker", + Version: "1.9.3", + Indirect: false, + Relationship: types.RelationshipDirect, + DependsOn: []string{"i18n@1.6.0"}, Locations: []types.Location{ { StartLine: 53, @@ -1183,11 +1201,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "globalid@0.4.2", - Name: "globalid", - Version: "0.4.2", - Indirect: true, - DependsOn: []string{"activesupport@5.2.3"}, + ID: "globalid@0.4.2", + Name: "globalid", + Version: "0.4.2", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"activesupport@5.2.3"}, Locations: []types.Location{ { StartLine: 55, @@ -1196,11 +1215,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "i18n@1.6.0", - Name: "i18n", - Version: "1.6.0", - Indirect: true, - DependsOn: []string{"concurrent-ruby@1.1.5"}, + ID: "i18n@1.6.0", + Name: "i18n", + Version: "1.6.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"concurrent-ruby@1.1.5"}, Locations: []types.Location{ { StartLine: 57, @@ -1209,11 +1229,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "jaro_winkler@1.5.2", - Name: "jaro_winkler", - Version: "1.5.2", - Indirect: true, - DependsOn: []string(nil), + ID: "jaro_winkler@1.5.2", + Name: "jaro_winkler", + Version: "1.5.2", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 59, @@ -1222,11 +1243,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "json@2.2.0", - Name: "json", - Version: "2.2.0", - Indirect: false, - DependsOn: []string(nil), + ID: "json@2.2.0", + Name: "json", + Version: "2.2.0", + Indirect: false, + Relationship: types.RelationshipDirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 60, @@ -1235,10 +1257,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "loofah@2.2.3", - Name: "loofah", - Version: "2.2.3", - Indirect: true, + ID: "loofah@2.2.3", + Name: "loofah", + Version: "2.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "crass@1.0.4", "nokogiri@1.10.3", @@ -1251,11 +1274,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "mail@2.7.1", - Name: "mail", - Version: "2.7.1", - Indirect: true, - DependsOn: []string{"mini_mime@1.0.1"}, + ID: "mail@2.7.1", + Name: "mail", + Version: "2.7.1", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"mini_mime@1.0.1"}, Locations: []types.Location{ { StartLine: 64, @@ -1264,11 +1288,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "marcel@0.3.3", - Name: "marcel", - Version: "0.3.3", - Indirect: true, - DependsOn: []string{"mimemagic@0.3.3"}, + ID: "marcel@0.3.3", + Name: "marcel", + Version: "0.3.3", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"mimemagic@0.3.3"}, Locations: []types.Location{ { StartLine: 66, @@ -1277,11 +1302,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "method_source@0.9.2", - Name: "method_source", - Version: "0.9.2", - Indirect: true, - DependsOn: []string(nil), + ID: "method_source@0.9.2", + Name: "method_source", + Version: "0.9.2", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 68, @@ -1290,11 +1316,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "mimemagic@0.3.3", - Name: "mimemagic", - Version: "0.3.3", - Indirect: true, - DependsOn: []string(nil), + ID: "mimemagic@0.3.3", + Name: "mimemagic", + Version: "0.3.3", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 69, @@ -1303,11 +1330,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "mini_mime@1.0.1", - Name: "mini_mime", - Version: "1.0.1", - Indirect: true, - DependsOn: []string(nil), + ID: "mini_mime@1.0.1", + Name: "mini_mime", + Version: "1.0.1", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 70, @@ -1316,11 +1344,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "mini_portile2@2.4.0", - Name: "mini_portile2", - Version: "2.4.0", - Indirect: true, - DependsOn: []string(nil), + ID: "mini_portile2@2.4.0", + Name: "mini_portile2", + Version: "2.4.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 71, @@ -1329,11 +1358,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "minitest@5.11.3", - Name: "minitest", - Version: "5.11.3", - Indirect: true, - DependsOn: []string(nil), + ID: "minitest@5.11.3", + Name: "minitest", + Version: "5.11.3", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 72, @@ -1342,11 +1372,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "nio4r@2.3.1", - Name: "nio4r", - Version: "2.3.1", - Indirect: true, - DependsOn: []string(nil), + ID: "nio4r@2.3.1", + Name: "nio4r", + Version: "2.3.1", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 73, @@ -1355,11 +1386,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "nokogiri@1.10.3", - Name: "nokogiri", - Version: "1.10.3", - Indirect: true, - DependsOn: []string{"mini_portile2@2.4.0"}, + ID: "nokogiri@1.10.3", + Name: "nokogiri", + Version: "1.10.3", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"mini_portile2@2.4.0"}, Locations: []types.Location{ { StartLine: 74, @@ -1368,11 +1400,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "parallel@1.17.0", - Name: "parallel", - Version: "1.17.0", - Indirect: true, - DependsOn: []string(nil), + ID: "parallel@1.17.0", + Name: "parallel", + Version: "1.17.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 76, @@ -1381,11 +1414,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "parser@2.6.3.0", - Name: "parser", - Version: "2.6.3.0", - Indirect: true, - DependsOn: []string{"ast@2.4.0"}, + ID: "parser@2.6.3.0", + Name: "parser", + Version: "2.6.3.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"ast@2.4.0"}, Locations: []types.Location{ { StartLine: 77, @@ -1394,10 +1428,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "pry@0.12.2", - Name: "pry", - Version: "0.12.2", - Indirect: false, + ID: "pry@0.12.2", + Name: "pry", + Version: "0.12.2", + Indirect: false, + Relationship: types.RelationshipDirect, DependsOn: []string{ "coderay@1.1.2", "method_source@0.9.2", @@ -1410,11 +1445,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "psych@3.1.0", - Name: "psych", - Version: "3.1.0", - Indirect: true, - DependsOn: []string(nil), + ID: "psych@3.1.0", + Name: "psych", + Version: "3.1.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 82, @@ -1423,11 +1459,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "rack@2.0.7", - Name: "rack", - Version: "2.0.7", - Indirect: true, - DependsOn: []string(nil), + ID: "rack@2.0.7", + Name: "rack", + Version: "2.0.7", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 83, @@ -1436,11 +1473,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "rack-test@1.1.0", - Name: "rack-test", - Version: "1.1.0", - Indirect: true, - DependsOn: []string{"rack@2.0.7"}, + ID: "rack-test@1.1.0", + Name: "rack-test", + Version: "1.1.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"rack@2.0.7"}, Locations: []types.Location{ { StartLine: 84, @@ -1449,10 +1487,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "rails@5.2.0", - Name: "rails", - Version: "5.2.0", - Indirect: false, + ID: "rails@5.2.0", + Name: "rails", + Version: "5.2.0", + Indirect: false, + Relationship: types.RelationshipDirect, DependsOn: []string{ "actioncable@5.2.3", "actionmailer@5.2.3", @@ -1474,10 +1513,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "rails-dom-testing@2.0.3", - Name: "rails-dom-testing", - Version: "2.0.3", - Indirect: true, + ID: "rails-dom-testing@2.0.3", + Name: "rails-dom-testing", + Version: "2.0.3", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "activesupport@5.2.3", "nokogiri@1.10.3", @@ -1490,11 +1530,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "rails-html-sanitizer@1.0.3", - Name: "rails-html-sanitizer", - Version: "1.0.3", - Indirect: true, - DependsOn: []string{"loofah@2.2.3"}, + ID: "rails-html-sanitizer@1.0.3", + Name: "rails-html-sanitizer", + Version: "1.0.3", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"loofah@2.2.3"}, Locations: []types.Location{ { StartLine: 102, @@ -1503,10 +1544,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "railties@5.2.3", - Name: "railties", - Version: "5.2.3", - Indirect: true, + ID: "railties@5.2.3", + Name: "railties", + Version: "5.2.3", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "actionpack@5.2.3", "activesupport@5.2.3", @@ -1522,11 +1564,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "rainbow@3.0.0", - Name: "rainbow", - Version: "3.0.0", - Indirect: true, - DependsOn: []string(nil), + ID: "rainbow@3.0.0", + Name: "rainbow", + Version: "3.0.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 110, @@ -1535,11 +1578,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "rake@12.3.2", - Name: "rake", - Version: "12.3.2", - Indirect: true, - DependsOn: []string(nil), + ID: "rake@12.3.2", + Name: "rake", + Version: "12.3.2", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 111, @@ -1548,10 +1592,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "rubocop@0.67.2", - Name: "rubocop", - Version: "0.67.2", - Indirect: false, + ID: "rubocop@0.67.2", + Name: "rubocop", + Version: "0.67.2", + Indirect: false, + Relationship: types.RelationshipDirect, DependsOn: []string{ "jaro_winkler@1.5.2", "parallel@1.17.0", @@ -1569,11 +1614,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "ruby-progressbar@1.10.0", - Name: "ruby-progressbar", - Version: "1.10.0", - Indirect: true, - DependsOn: []string(nil), + ID: "ruby-progressbar@1.10.0", + Name: "ruby-progressbar", + Version: "1.10.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 120, @@ -1582,10 +1628,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "sprockets@3.7.2", - Name: "sprockets", - Version: "3.7.2", - Indirect: true, + ID: "sprockets@3.7.2", + Name: "sprockets", + Version: "3.7.2", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "concurrent-ruby@1.1.5", "rack@2.0.7", @@ -1598,10 +1645,11 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "sprockets-rails@3.2.1", - Name: "sprockets-rails", - Version: "3.2.1", - Indirect: true, + ID: "sprockets-rails@3.2.1", + Name: "sprockets-rails", + Version: "3.2.1", + Indirect: true, + Relationship: types.RelationshipIndirect, DependsOn: []string{ "actionpack@5.2.3", "activesupport@5.2.3", @@ -1615,11 +1663,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "thor@0.20.3", - Name: "thor", - Version: "0.20.3", - Indirect: true, - DependsOn: []string(nil), + ID: "thor@0.20.3", + Name: "thor", + Version: "0.20.3", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 128, @@ -1628,11 +1677,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "thread_safe@0.3.6", - Name: "thread_safe", - Version: "0.3.6", - Indirect: true, - DependsOn: []string(nil), + ID: "thread_safe@0.3.6", + Name: "thread_safe", + Version: "0.3.6", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 129, @@ -1641,11 +1691,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "tzinfo@1.2.5", - Name: "tzinfo", - Version: "1.2.5", - Indirect: true, - DependsOn: []string{"thread_safe@0.3.6"}, + ID: "tzinfo@1.2.5", + Name: "tzinfo", + Version: "1.2.5", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"thread_safe@0.3.6"}, Locations: []types.Location{ { StartLine: 130, @@ -1654,11 +1705,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "unicode-display_width@1.5.0", - Name: "unicode-display_width", - Version: "1.5.0", - Indirect: true, - DependsOn: []string(nil), + ID: "unicode-display_width@1.5.0", + Name: "unicode-display_width", + Version: "1.5.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 132, @@ -1667,11 +1719,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "websocket-driver@0.7.0", - Name: "websocket-driver", - Version: "0.7.0", - Indirect: true, - DependsOn: []string{"websocket-extensions@0.1.3"}, + ID: "websocket-driver@0.7.0", + Name: "websocket-driver", + Version: "0.7.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"websocket-extensions@0.1.3"}, Locations: []types.Location{ { StartLine: 133, @@ -1680,11 +1733,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - ID: "websocket-extensions@0.1.3", - Name: "websocket-extensions", - Version: "0.1.3", - Indirect: true, - DependsOn: []string(nil), + ID: "websocket-extensions@0.1.3", + Name: "websocket-extensions", + Version: "0.1.3", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string(nil), Locations: []types.Location{ { StartLine: 135, diff --git a/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden b/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden index fdd72301280c..0b85bf333243 100644 --- a/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden +++ b/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden @@ -11,6 +11,7 @@ }, "Version": "5.2.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "actionpack@5.2.3", "nio4r@2.3.1", @@ -32,6 +33,7 @@ }, "Version": "5.2.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "actionpack@5.2.3", "actionview@5.2.3", @@ -55,6 +57,7 @@ }, "Version": "5.2.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "actionview@5.2.3", "activesupport@5.2.3", @@ -79,6 +82,7 @@ }, "Version": "5.2.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "activesupport@5.2.3", "builder@3.2.3", @@ -102,6 +106,7 @@ }, "Version": "5.2.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "activesupport@5.2.3", "globalid@0.4.2" @@ -122,6 +127,7 @@ }, "Version": "5.2.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "activesupport@5.2.3" ], @@ -141,6 +147,7 @@ }, "Version": "5.2.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "activemodel@5.2.3", "activesupport@5.2.3", @@ -162,6 +169,7 @@ }, "Version": "5.2.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "actionpack@5.2.3", "activerecord@5.2.3", @@ -183,6 +191,7 @@ }, "Version": "5.2.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "concurrent-ruby@1.1.5", "i18n@1.6.0", @@ -205,6 +214,7 @@ }, "Version": "9.0.0", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -221,6 +231,7 @@ }, "Version": "2.4.0", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -237,6 +248,7 @@ }, "Version": "3.2.3", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -253,6 +265,7 @@ }, "Version": "1.1.2", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -269,6 +282,7 @@ }, "Version": "1.1.5", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -285,6 +299,7 @@ }, "Version": "1.0.4", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -300,6 +315,7 @@ "PURL": "pkg:gem/dotenv@2.7.2" }, "Version": "2.7.2", + "Relationship": "direct", "Layer": {}, "Locations": [ { @@ -316,6 +332,7 @@ }, "Version": "1.8.0", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -331,6 +348,7 @@ "PURL": "pkg:gem/faker@1.9.3" }, "Version": "1.9.3", + "Relationship": "direct", "DependsOn": [ "i18n@1.6.0" ], @@ -350,6 +368,7 @@ }, "Version": "0.4.2", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "activesupport@5.2.3" ], @@ -369,6 +388,7 @@ }, "Version": "1.6.0", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "concurrent-ruby@1.1.5" ], @@ -388,6 +408,7 @@ }, "Version": "1.5.2", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -403,6 +424,7 @@ "PURL": "pkg:gem/json@2.2.0" }, "Version": "2.2.0", + "Relationship": "direct", "Layer": {}, "Locations": [ { @@ -419,6 +441,7 @@ }, "Version": "2.2.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "crass@1.0.4", "nokogiri@1.10.3" @@ -439,6 +462,7 @@ }, "Version": "2.7.1", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "mini_mime@1.0.1" ], @@ -458,6 +482,7 @@ }, "Version": "0.3.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "mimemagic@0.3.3" ], @@ -477,6 +502,7 @@ }, "Version": "0.9.2", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -493,6 +519,7 @@ }, "Version": "0.3.3", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -509,6 +536,7 @@ }, "Version": "1.0.1", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -525,6 +553,7 @@ }, "Version": "2.4.0", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -541,6 +570,7 @@ }, "Version": "5.11.3", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -557,6 +587,7 @@ }, "Version": "2.3.1", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -573,6 +604,7 @@ }, "Version": "1.10.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "mini_portile2@2.4.0" ], @@ -592,6 +624,7 @@ }, "Version": "1.17.0", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -608,6 +641,7 @@ }, "Version": "2.6.3.0", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "ast@2.4.0" ], @@ -626,6 +660,7 @@ "PURL": "pkg:gem/pry@0.12.2" }, "Version": "0.12.2", + "Relationship": "direct", "DependsOn": [ "coderay@1.1.2", "method_source@0.9.2" @@ -646,6 +681,7 @@ }, "Version": "3.1.0", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -662,6 +698,7 @@ }, "Version": "2.0.7", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -678,6 +715,7 @@ }, "Version": "1.1.0", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "rack@2.0.7" ], @@ -696,6 +734,7 @@ "PURL": "pkg:gem/rails@5.2.0" }, "Version": "5.2.0", + "Relationship": "direct", "DependsOn": [ "actioncable@5.2.3", "actionmailer@5.2.3", @@ -725,6 +764,7 @@ }, "Version": "2.0.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "activesupport@5.2.3", "nokogiri@1.10.3" @@ -745,6 +785,7 @@ }, "Version": "1.0.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "loofah@2.2.3" ], @@ -764,6 +805,7 @@ }, "Version": "5.2.3", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "actionpack@5.2.3", "activesupport@5.2.3", @@ -787,6 +829,7 @@ }, "Version": "3.0.0", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -803,6 +846,7 @@ }, "Version": "12.3.2", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -818,6 +862,7 @@ "PURL": "pkg:gem/rubocop@0.67.2" }, "Version": "0.67.2", + "Relationship": "direct", "DependsOn": [ "jaro_winkler@1.5.2", "parallel@1.17.0", @@ -843,6 +888,7 @@ }, "Version": "1.10.0", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -859,6 +905,7 @@ }, "Version": "3.7.2", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "concurrent-ruby@1.1.5", "rack@2.0.7" @@ -879,6 +926,7 @@ }, "Version": "3.2.1", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "actionpack@5.2.3", "activesupport@5.2.3", @@ -900,6 +948,7 @@ }, "Version": "0.20.3", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -916,6 +965,7 @@ }, "Version": "0.3.6", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -932,6 +982,7 @@ }, "Version": "1.2.5", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "thread_safe@0.3.6" ], @@ -951,6 +1002,7 @@ }, "Version": "1.5.0", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -967,6 +1019,7 @@ }, "Version": "0.7.0", "Indirect": true, + "Relationship": "indirect", "DependsOn": [ "websocket-extensions@0.1.3" ], @@ -986,6 +1039,7 @@ }, "Version": "0.1.3", "Indirect": true, + "Relationship": "indirect", "Layer": {}, "Locations": [ { @@ -2627,7 +2681,6 @@ "PURL": "pkg:npm/asap@2.0.6" }, "Version": "2.0.6", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -2643,7 +2696,6 @@ "PURL": "pkg:npm/jquery@3.3.9" }, "Version": "3.3.9", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -2659,7 +2711,6 @@ "PURL": "pkg:npm/js-tokens@4.0.0" }, "Version": "4.0.0", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -2675,7 +2726,6 @@ "PURL": "pkg:npm/lodash@4.17.4" }, "Version": "4.17.4", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -2691,7 +2741,6 @@ "PURL": "pkg:npm/loose-envify@1.4.0" }, "Version": "1.4.0", - "Indirect": true, "DependsOn": [ "js-tokens@4.0.0" ], @@ -2710,7 +2759,6 @@ "PURL": "pkg:npm/object-assign@4.1.1" }, "Version": "4.1.1", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -2726,7 +2774,6 @@ "PURL": "pkg:npm/promise@8.0.3" }, "Version": "8.0.3", - "Indirect": true, "DependsOn": [ "asap@2.0.6" ], @@ -2745,7 +2792,6 @@ "PURL": "pkg:npm/prop-types@15.7.2" }, "Version": "15.7.2", - "Indirect": true, "DependsOn": [ "loose-envify@1.4.0", "object-assign@4.1.1", @@ -2766,7 +2812,6 @@ "PURL": "pkg:npm/react@16.8.6" }, "Version": "16.8.6", - "Indirect": true, "DependsOn": [ "loose-envify@1.4.0", "object-assign@4.1.1", @@ -2788,7 +2833,6 @@ "PURL": "pkg:npm/react-is@16.8.6" }, "Version": "16.8.6", - "Indirect": true, "Layer": {}, "Locations": [ { @@ -2804,7 +2848,6 @@ "PURL": "pkg:npm/redux@4.0.1" }, "Version": "4.0.1", - "Indirect": true, "DependsOn": [ "loose-envify@1.4.0", "symbol-observable@1.2.0" @@ -2824,7 +2867,6 @@ "PURL": "pkg:npm/scheduler@0.13.6" }, "Version": "0.13.6", - "Indirect": true, "DependsOn": [ "loose-envify@1.4.0", "object-assign@4.1.1" @@ -2844,7 +2886,6 @@ "PURL": "pkg:npm/symbol-observable@1.2.0" }, "Version": "1.2.0", - "Indirect": true, "Layer": {}, "Locations": [ { diff --git a/pkg/fanal/types/artifact.go b/pkg/fanal/types/artifact.go index 86870d52ee40..1bc2a0a9575e 100644 --- a/pkg/fanal/types/artifact.go +++ b/pkg/fanal/types/artifact.go @@ -9,6 +9,7 @@ import ( "github.com/package-url/packageurl-go" "github.com/samber/lo" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/digest" "github.com/aquasecurity/trivy/pkg/sbom/core" ) @@ -65,6 +66,16 @@ type Layer struct { CreatedBy string `json:",omitempty"` } +// TODO: merge pkg/dependency/types/types.go into this file +type Relationship = godeptypes.Relationship + +const ( + RelationshipUnknown = godeptypes.RelationshipUnknown + RelationshipRoot = godeptypes.RelationshipRoot + RelationshipDirect = godeptypes.RelationshipDirect + RelationshipIndirect = godeptypes.RelationshipIndirect +) + type Package struct { ID string `json:",omitempty"` Name string `json:",omitempty"` @@ -83,7 +94,9 @@ type Package struct { Modularitylabel string `json:",omitempty"` // only for Red Hat based distributions BuildInfo *BuildInfo `json:",omitempty"` // only for Red Hat - Indirect bool `json:",omitempty"` // this package is direct dependency of the project or not + + Indirect bool `json:",omitempty"` // Deprecated: Use relationship. Kept for backward compatibility. + Relationship Relationship `json:",omitempty"` // Dependencies of this package // Note: it may have interdependencies, which may lead to infinite loops. diff --git a/pkg/report/github/github.go b/pkg/report/github/github.go index 5de466ad6beb..8a1ef95c74e1 100644 --- a/pkg/report/github/github.go +++ b/pkg/report/github/github.go @@ -176,7 +176,7 @@ func getMetadata(report types.Report) Metadata { } func getPkgRelationshipType(pkg ftypes.Package) string { - if pkg.Indirect { + if pkg.Relationship == ftypes.RelationshipIndirect { return IndirectRelationship } return DirectRelationship diff --git a/pkg/report/github/github_test.go b/pkg/report/github/github_test.go index 596fd8dd3862..655a24a7a3b8 100644 --- a/pkg/report/github/github_test.go +++ b/pkg/report/github/github_test.go @@ -39,17 +39,19 @@ func TestWriter_Write(t *testing.T) { Type: "yarn", Packages: []ftypes.Package{ { - Name: "@xtuc/ieee754", - Version: "1.2.0", + Name: "@xtuc/ieee754", + Version: "1.2.0", + Relationship: ftypes.RelationshipDirect, }, { - Name: "@xtuc/long", - Version: "4.2.2", + Name: "@xtuc/long", + Version: "4.2.2", + Relationship: ftypes.RelationshipDirect, }, { - Name: "@xtuc/binaryen", - Version: "1.37.33", - Indirect: true, + Name: "@xtuc/binaryen", + Version: "1.37.33", + Relationship: ftypes.RelationshipIndirect, }, }, Vulnerabilities: []types.DetectedVulnerability{ diff --git a/pkg/report/table/vulnerability.go b/pkg/report/table/vulnerability.go index 1984838d3709..a54809bc80da 100644 --- a/pkg/report/table/vulnerability.go +++ b/pkg/report/table/vulnerability.go @@ -259,7 +259,7 @@ func (r *vulnerabilityRenderer) printf(format string, args ...interface{}) { func addParents(topItem treeprint.Tree, pkg ftypes.Package, parentMap map[string]ftypes.Packages, ancestors map[string][]string, seen map[string]struct{}, depth int) { - if !pkg.Indirect { + if pkg.Relationship == ftypes.RelationshipDirect { return } @@ -270,7 +270,7 @@ func addParents(topItem treeprint.Tree, pkg ftypes.Package, parentMap map[string } seen[parent.ID] = struct{}{} // to avoid infinite loops - if depth == 1 && !parent.Indirect { + if depth == 1 && parent.Relationship == ftypes.RelationshipDirect { topItem.AddBranch(parent.ID) } else { // We omit intermediate dependencies and show only direct dependencies @@ -311,12 +311,11 @@ func findAncestor(pkgID string, parentMap map[string]ftypes.Packages, seen map[s continue } switch { - case !parent.Indirect: + case parent.Relationship == ftypes.RelationshipDirect: ancestors[parent.ID] = struct{}{} case len(parentMap[parent.ID]) == 0: - // Direct dependencies cannot be identified in some package managers like "package-lock.json" v1, - // then the "Indirect" field can be always true. We try to guess direct dependencies in this case. - // A dependency with no parents must be a direct dependency. + // Some package managers, such as "package-lock.json" v1, can retrieve package dependencies but not relationships. + // We try to guess direct dependencies in this case. A dependency with no parents must be a direct dependency. // // e.g. // -> styled-components diff --git a/pkg/report/table/vulnerability_test.go b/pkg/report/table/vulnerability_test.go index d941edc796f1..b7fbc5bce1a5 100644 --- a/pkg/report/table/vulnerability_test.go +++ b/pkg/report/table/vulnerability_test.go @@ -171,38 +171,40 @@ Total: 1 (MEDIUM: 0, HIGH: 1) Type: "npm", Packages: []ftypes.Package{ { - ID: "node-fetch@1.7.3", - Name: "node-fetch", - Version: "1.7.3", - Indirect: true, + ID: "node-fetch@1.7.3", + Name: "node-fetch", + Version: "1.7.3", + Relationship: ftypes.RelationshipIndirect, }, { - ID: "isomorphic-fetch@2.2.1", - Name: "isomorphic-fetch", - Version: "2.2.1", - Indirect: true, + ID: "isomorphic-fetch@2.2.1", + Name: "isomorphic-fetch", + Version: "2.2.1", + Relationship: ftypes.RelationshipIndirect, DependsOn: []string{ "node-fetch@1.7.3", }, }, { - ID: "fbjs@0.8.18", - Name: "fbjs", - Version: "0.8.18", - Indirect: true, + ID: "fbjs@0.8.18", + Name: "fbjs", + Version: "0.8.18", + Relationship: ftypes.RelationshipIndirect, DependsOn: []string{ "isomorphic-fetch@2.2.1", }, }, { - ID: "sanitize-html@1.20.0", - Name: "sanitize-html", - Version: "1.20.0", + ID: "sanitize-html@1.20.0", + Name: "sanitize-html", + Version: "1.20.0", + Relationship: ftypes.RelationshipDirect, }, { - ID: "styled-components@3.1.3", - Name: "styled-components", - Version: "3.1.3", + ID: "styled-components@3.1.3", + Name: "styled-components", + Version: "3.1.3", + Relationship: ftypes.RelationshipDirect, DependsOn: []string{ "fbjs@0.8.18", }, @@ -260,41 +262,41 @@ package-lock.json `, }, { - name: "happy path with vulnerability origin graph without direct dependency info", + name: "happy path with vulnerability origin graph with unknown relationships", result: types.Result{ Target: "package-lock.json", Class: types.ClassLangPkg, Type: "npm", Packages: []ftypes.Package{ { - ID: "node-fetch@1.7.3", - Name: "node-fetch", - Version: "1.7.3", - Indirect: true, + ID: "node-fetch@1.7.3", + Name: "node-fetch", + Version: "1.7.3", + Relationship: ftypes.RelationshipUnknown, }, { - ID: "isomorphic-fetch@2.2.1", - Name: "isomorphic-fetch", - Version: "2.2.1", - Indirect: true, + ID: "isomorphic-fetch@2.2.1", + Name: "isomorphic-fetch", + Version: "2.2.1", + Relationship: ftypes.RelationshipUnknown, DependsOn: []string{ "node-fetch@1.7.3", }, }, { - ID: "fbjs@0.8.18", - Name: "fbjs", - Version: "0.8.18", - Indirect: true, + ID: "fbjs@0.8.18", + Name: "fbjs", + Version: "0.8.18", + Relationship: ftypes.RelationshipUnknown, DependsOn: []string{ "isomorphic-fetch@2.2.1", }, }, { - ID: "styled-components@3.1.3", - Name: "styled-components", - Version: "3.1.3", - Indirect: true, + ID: "styled-components@3.1.3", + Name: "styled-components", + Version: "3.1.3", + Relationship: ftypes.RelationshipUnknown, DependsOn: []string{ "fbjs@0.8.18", }, diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index 5bb181992975..b6d1301d6f6e 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -170,6 +170,7 @@ func (e *Encoder) encodePackages(parent *core.Component, result types.Result) { } // Convert packages into components and add them to the BOM + parentRelationship := core.RelationshipContains components := make(map[string]*core.Component, len(result.Packages)) for i, pkg := range result.Packages { pkgID := lo.Ternary(pkg.ID == "", fmt.Sprintf("%s@%s", pkg.Name, pkg.Version), pkg.ID) @@ -186,30 +187,42 @@ func (e *Encoder) encodePackages(parent *core.Component, result types.Result) { if vv := vulns[pkgID]; vv != nil { e.bom.AddVulnerabilities(c, vv) } + + // Handle a root package + if pkg.Relationship == ftypes.RelationshipRoot { + // If the package is a root package, add a relationship between the parent and the root package + e.bom.AddRelationship(parent, c, core.RelationshipContains) + // Replace the parent with the root package + parent = c + parentRelationship = core.RelationshipDependsOn + } } - // Build a dependency graph + // Build a dependency graph between packages for _, pkg := range result.Packages { - // Skip indirect dependencies - if pkg.Indirect && len(parents[pkg.ID]) != 0 { + if pkg.Relationship == ftypes.RelationshipRoot { continue } + c := components[pkg.ID+pkg.FilePath] - directPkg := components[pkg.ID+pkg.FilePath] - e.bom.AddRelationship(parent, directPkg, core.RelationshipContains) + // Add a relationship between the parent and the package if needed + if e.belongToParent(pkg, parents) { + e.bom.AddRelationship(parent, c, parentRelationship) + } + // Add relationships between the package and its dependencies for _, dep := range pkg.DependsOn { - indirectPkg, ok := components[dep] + dependsOn, ok := components[dep] if !ok { continue } - e.bom.AddRelationship(directPkg, indirectPkg, core.RelationshipDependsOn) + e.bom.AddRelationship(c, dependsOn, core.RelationshipDependsOn) } // Components that do not have their own dependencies MUST be declared as empty elements within the graph. // TODO: Should check if the component has actually no dependencies or the dependency graph is not supported. if len(pkg.DependsOn) == 0 { - e.bom.AddRelationship(directPkg, nil, "") + e.bom.AddRelationship(c, nil, "") } } } @@ -356,6 +369,35 @@ func (*Encoder) vulnerability(vuln types.DetectedVulnerability) core.Vulnerabili } } +// belongToParent determines if a package should be directly included in the parent based on its relationship and dependencies. +func (*Encoder) belongToParent(pkg ftypes.Package, parents map[string]ftypes.Packages) bool { + // Case 1: Direct/Indirect: known , DependsOn: known + // 1-1: Only direct packages are included in the parent (RelationshipContains or RelationshipDependsOn) + // 1-2: Each direct package includes its dependent packages (RelationshipDependsOn). + // Case 2: Direct/Indirect: unknown, DependsOn: unknown (e.g., conan lockfile v2) + // All packages are included in the parent (RelationshipContains or RelationshipDependsOn). + // Case 3: Direct/Indirect: unknown, DependsOn: known (e.g., OS packages) + // All packages are included in the parent (RelationshipContains or RelationshipDependsOn). + // Case 4: Direct/Indirect: known , DependsOn: unknown (e.g., go.mod without $GOPATH) + // All packages are included in the parent (RelationshipContains or RelationshipDependsOn). + switch { + // Case 1-1: direct packages + case pkg.Relationship == ftypes.RelationshipDirect: + return true + // Case 1-2: indirect packages + case pkg.Relationship == ftypes.RelationshipIndirect && len(parents[pkg.ID]) != 0: + return false + // Case 2 & 3: + case pkg.Relationship == ftypes.RelationshipUnknown: + return true + // Case 4: + case pkg.Relationship == ftypes.RelationshipIndirect && len(parents[pkg.ID]) == 0: + return true + default: + return true + } +} + func filterProperties(props []core.Property) []core.Property { return lo.Filter(props, func(property core.Property, _ int) bool { return !(property.Value == "" || (property.Name == core.PropertySrcEpoch && property.Value == "0")) diff --git a/pkg/sbom/io/encode_test.go b/pkg/sbom/io/encode_test.go index a57bddd9983d..d45f70112592 100644 --- a/pkg/sbom/io/encode_test.go +++ b/pkg/sbom/io/encode_test.go @@ -293,6 +293,245 @@ func TestEncoder_Encode(t *testing.T) { }, }, }, + { + name: "root package", + report: types.Report{ + SchemaVersion: 2, + ArtifactName: "gobinary", + ArtifactType: ftypes.ArtifactFilesystem, + Results: []types.Result{ + { + Target: "test", + Type: ftypes.GoBinary, + Class: types.ClassLangPkg, + Packages: []ftypes.Package{ + { + ID: "github.com/org/root", + Name: "github.com/org/root", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/org", + Name: "root", + }, + }, + Relationship: ftypes.RelationshipRoot, + DependsOn: []string{ + "github.com/org/direct@v1.0.0", + }, + }, + { + ID: "github.com/org/direct@v1.0.0", + Name: "github.com/org/direct", + Version: "v1.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/org", + Name: "direct", + Version: "1.0.0", + }, + }, + Relationship: ftypes.RelationshipDirect, + DependsOn: []string{ + "github.com/org/indirect@v2.0.0", + }, + }, + { + ID: "github.com/org/indirect@v2.0.0", + Name: "github.com/org/indirect", + Version: "2.0.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/org", + Name: "indirect", + Version: "2.0.0", + }, + }, + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "stdlib@1.22.1", + Name: "stdlib", + Version: "1.22.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Name: "stdlib", + Version: "1.22.1", + }, + }, + Relationship: ftypes.RelationshipDirect, + }, + }, + }, + }, + }, + wantComponents: map[uuid.UUID]*core.Component{ + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): { + Type: core.TypeFilesystem, + Name: "gobinary", + Root: true, + Properties: []core.Property{ + { + Name: core.PropertySchemaVersion, + Value: "2", + }, + }, + PkgID: core.PkgID{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): { + Type: core.TypeApplication, + Name: "test", + Properties: []core.Property{ + { + Name: core.PropertyClass, + Value: "lang-pkgs", + }, + { + Name: core.PropertyType, + Value: "gobinary", + }, + }, + PkgID: core.PkgID{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000003"): { + Type: core.TypeLibrary, + Name: "github.com/org/root", + SrcFile: "test", + Properties: []core.Property{ + { + Name: core.PropertyPkgID, + Value: "github.com/org/root", + }, + { + Name: core.PropertyPkgType, + Value: "gobinary", + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/org", + Name: "root", + }, + BOMRef: "pkg:golang/github.com/org/root", + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000004"): { + Type: core.TypeLibrary, + Name: "github.com/org/direct", + Version: "1.0.0", + SrcFile: "test", + Properties: []core.Property{ + { + Name: core.PropertyPkgID, + Value: "github.com/org/direct@v1.0.0", + }, + { + Name: core.PropertyPkgType, + Value: "gobinary", + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/org", + Name: "direct", + Version: "1.0.0", + }, + BOMRef: "pkg:golang/github.com/org/direct@1.0.0", + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"): { + Type: core.TypeLibrary, + Name: "github.com/org/indirect", + Version: "2.0.0", + SrcFile: "test", + Properties: []core.Property{ + { + Name: core.PropertyPkgID, + Value: "github.com/org/indirect@v2.0.0", + }, + { + Name: core.PropertyPkgType, + Value: "gobinary", + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/org", + Name: "indirect", + Version: "2.0.0", + }, + BOMRef: "pkg:golang/github.com/org/indirect@2.0.0", + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000006"): { + Type: core.TypeLibrary, + Name: "stdlib", + Version: "1.22.1", + SrcFile: "test", + Properties: []core.Property{ + { + Name: core.PropertyPkgID, + Value: "stdlib@1.22.1", + }, + { + Name: core.PropertyPkgType, + Value: "gobinary", + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Name: "stdlib", + Version: "1.22.1", + }, + BOMRef: "pkg:golang/stdlib@1.22.1", + }, + }, + }, + wantRels: map[uuid.UUID][]core.Relationship{ + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"), + Type: core.RelationshipContains, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000003"), + Type: core.RelationshipContains, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000003"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000004"), + Type: core.RelationshipDependsOn, + }, + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000006"), + Type: core.RelationshipDependsOn, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000004"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"), + Type: core.RelationshipDependsOn, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"): nil, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000006"): nil, + }, + wantVulns: map[uuid.UUID][]core.Vulnerability{}, + }, { name: "invalid digest", report: types.Report{ @@ -330,7 +569,7 @@ func TestEncoder_Encode(t *testing.T) { require.Len(t, got.Components(), len(tt.wantComponents)) for id, want := range tt.wantComponents { - assert.EqualExportedValues(t, *want, *got.Components()[id]) + assert.EqualExportedValues(t, *want, *got.Components()[id], id) } assert.Equal(t, tt.wantRels, got.Relationships()) From 2d090ef2df7966ada7178b4b88179498ad7e1f2b Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Sat, 27 Apr 2024 14:00:14 +0400 Subject: [PATCH 007/352] feat(go): add main module (#6574) Signed-off-by: knqyf263 --- pkg/dependency/parser/golang/mod/parse.go | 17 ++- .../parser/golang/mod/parse_testcase.go | 110 ++++++++++++++++++ .../analyzer/language/golang/mod/mod_test.go | 20 ++++ 3 files changed, 145 insertions(+), 2 deletions(-) diff --git a/pkg/dependency/parser/golang/mod/parse.go b/pkg/dependency/parser/golang/mod/parse.go index b506128997ad..3be3bb0c2800 100644 --- a/pkg/dependency/parser/golang/mod/parse.go +++ b/pkg/dependency/parser/golang/mod/parse.go @@ -85,16 +85,29 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, skipIndirect = lessThan117(modFileParsed.Go.Version) } + // Main module + if m := modFileParsed.Module; m != nil { + ver := strings.TrimPrefix(m.Mod.Version, "v") + libs[m.Mod.Path] = types.Library{ + ID: packageID(m.Mod.Path, ver), + Name: m.Mod.Path, + Version: ver, + ExternalReferences: p.GetExternalRefs(m.Mod.Path), + Relationship: types.RelationshipRoot, + } + } + // Required modules for _, require := range modFileParsed.Require { // Skip indirect dependencies less than Go 1.17 if skipIndirect && require.Indirect { continue } + ver := strings.TrimPrefix(require.Mod.Version, "v") libs[require.Mod.Path] = types.Library{ - ID: packageID(require.Mod.Path, require.Mod.Version[1:]), + ID: packageID(require.Mod.Path, ver), Name: require.Mod.Path, - Version: require.Mod.Version[1:], + Version: ver, Relationship: lo.Ternary(require.Indirect, types.RelationshipIndirect, types.RelationshipDirect), ExternalReferences: p.GetExternalRefs(require.Mod.Path), } diff --git a/pkg/dependency/parser/golang/mod/parse_testcase.go b/pkg/dependency/parser/golang/mod/parse_testcase.go index 4edeccc2b581..dfadc32f67c3 100644 --- a/pkg/dependency/parser/golang/mod/parse_testcase.go +++ b/pkg/dependency/parser/golang/mod/parse_testcase.go @@ -5,6 +5,17 @@ import "github.com/aquasecurity/trivy/pkg/dependency/types" var ( // execute go mod tidy in normal folder GoModNormal = []types.Library{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", @@ -39,6 +50,17 @@ var ( // execute go mod tidy in replaced folder GoModReplaced = []types.Library{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", Name: "github.com/aquasecurity/go-dep-parser", @@ -61,6 +83,17 @@ var ( // execute go mod tidy in replaced folder GoModUnreplaced = []types.Library{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211110174639-8257534ffed3", Name: "github.com/aquasecurity/go-dep-parser", @@ -83,6 +116,17 @@ var ( // execute go mod tidy in replaced-with-version folder GoModReplacedWithVersion = []types.Library{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", Name: "github.com/aquasecurity/go-dep-parser", @@ -105,6 +149,17 @@ var ( // execute go mod tidy in replaced-with-version-mismatch folder GoModReplacedWithVersionMismatch = []types.Library{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", @@ -139,6 +194,17 @@ var ( // execute go mod tidy in replaced-with-local-path folder GoModReplacedWithLocalPath = []types.Library{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", @@ -167,6 +233,17 @@ var ( // execute go mod tidy in replaced-with-local-path-and-version folder GoModReplacedWithLocalPathAndVersion = []types.Library{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", @@ -195,6 +272,17 @@ var ( // execute go mod tidy in replaced-with-local-path-and-version-mismatch folder GoModReplacedWithLocalPathAndVersionMismatch = []types.Library{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", @@ -229,6 +317,17 @@ var ( // execute go mod tidy in go116 folder GoMod116 = []types.Library{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", @@ -245,6 +344,17 @@ var ( // execute go mod tidy in no-go-version folder GoModNoGoVersion = []types.Library{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", diff --git a/pkg/fanal/analyzer/language/golang/mod/mod_test.go b/pkg/fanal/analyzer/language/golang/mod/mod_test.go index d9a5254e1953..c667170af7a6 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod_test.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod_test.go @@ -32,6 +32,11 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { Type: types.GoModule, FilePath: "go.mod", Libraries: types.Packages{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", Name: "github.com/aquasecurity/go-dep-parser", @@ -67,6 +72,11 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { Type: types.GoModule, FilePath: "go.mod", Libraries: types.Packages{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + }, { ID: "github.com/sad/sad@v0.0.1", Name: "github.com/sad/sad", @@ -90,6 +100,11 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { Type: types.GoModule, FilePath: "go.mod", Libraries: types.Packages{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd", Name: "github.com/aquasecurity/go-dep-parser", @@ -125,6 +140,11 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { Type: types.GoModule, FilePath: "go.mod", Libraries: types.Packages{ + { + ID: "github.com/org/repo", + Name: "github.com/org/repo", + Relationship: types.RelationshipRoot, + }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd", Name: "github.com/aquasecurity/go-dep-parser", From 060d0bb6411b6ed5c004af80b29754c9313e20dc Mon Sep 17 00:00:00 2001 From: chenk Date: Sat, 27 Apr 2024 17:30:17 +0300 Subject: [PATCH 008/352] BREAKING: support exclude `kinds/namespaces` and include `kinds/namespaces` (#6323) Signed-off-by: chenk --- .../configuration/cli/trivy_kubernetes.md | 29 ++++--- go.mod | 22 ++--- go.sum | 40 ++++----- integration/k8s_test.go | 56 +------------ pkg/commands/app.go | 21 +++-- pkg/flag/kubernetes_flags.go | 82 +++++++++++-------- pkg/k8s/commands/cluster.go | 17 ++-- pkg/k8s/commands/namespace.go | 44 ---------- pkg/k8s/commands/namespace_test.go | 47 ----------- pkg/k8s/commands/resource.go | 73 ----------------- pkg/k8s/commands/resource_test.go | 61 -------------- pkg/k8s/commands/run.go | 44 +++------- 12 files changed, 129 insertions(+), 407 deletions(-) delete mode 100644 pkg/k8s/commands/namespace.go delete mode 100644 pkg/k8s/commands/namespace_test.go delete mode 100644 pkg/k8s/commands/resource.go delete mode 100644 pkg/k8s/commands/resource_test.go diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index a1befafeb504..7b7c91f3d88e 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -2,32 +2,33 @@ [EXPERIMENTAL] Scan kubernetes cluster +### Synopsis + +Default context in kube configuration will be used unless specified + ``` -trivy kubernetes [flags] { cluster | all | specific resources like kubectl. eg: pods, pod/NAME } +trivy kubernetes [flags] [CONTEXT] ``` ### Examples ``` # cluster scanning - $ trivy k8s --report summary cluster - - # namespace scanning: - $ trivy k8s -n kube-system --report summary all + $ trivy k8s --report summary - # resources scanning: - $ trivy k8s --report=summary deploy - $ trivy k8s --namespace=kube-system --report=summary deploy,configmaps + # cluster scanning with specific namespace: + $ trivy k8s --include-namespaces kube-system --report summary - # resource scanning: - $ trivy k8s deployment/orion + # cluster with specific context: + $ trivy k8s kind-kind --report summary + + ``` ### Options ``` - -A, --all-namespaces fetch resources from all cluster namespaces --burst int specify the maximum burst for throttle (default 10) --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend @@ -36,11 +37,12 @@ trivy kubernetes [flags] { cluster | all | specific resources like kubectl. eg: --components strings specify which components to scan (workload,infra) (default [workload,infra]) --config-data strings specify paths from which data for the Rego policies will be recursively loaded --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files - --context string specify a context to scan --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan + --exclude-kinds strings indicate the kinds exclude from scanning (example: node) + --exclude-namespaces strings indicate the namespaces excluded from scanning (example: kube-system) --exclude-nodes strings indicate the node labels that the node-collector job should exclude from scanning (example: kubernetes.io/arch:arm64,team:dev) --exclude-owned exclude resources that have an owner reference --exit-code int specify exit code when any security issues are found @@ -58,13 +60,14 @@ trivy kubernetes [flags] { cluster | all | specific resources like kubectl. eg: --ignore-unfixed display only fixed vulnerabilities --ignorefile string specify .trivyignore file (default ".trivyignore") --image-src strings image source(s) to use, in priority order (docker,containerd,podman,remote) (default [docker,containerd,podman,remote]) + --include-kinds strings indicate the kinds included in scanning (example: node) + --include-namespaces strings indicate the namespaces included in scanning (example: kube-system) --include-non-failures include successes and exceptions, available with '--scanners misconfig' --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") --k8s-version string specify k8s version to validate outdated api by it (example: 1.21.0) --kubeconfig string specify the kubeconfig file path to use --list-all-pkgs enabling the option will output all packages regardless of vulnerability --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) - -n, --namespace string specify a namespace to scan --no-progress suppress progress bar --node-collector-imageref string indicate the image reference for the node-collector scan job (default "ghcr.io/aquasecurity/node-collector:0.0.9") --node-collector-namespace string specify the namespace in which the node-collector job should be deployed (default "trivy-temp") diff --git a/go.mod b/go.mod index 463dccf98019..03b9790a1aff 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/aquasecurity/trivy-aws v0.8.0 github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 - github.com/aquasecurity/trivy-kubernetes v0.6.3 + github.com/aquasecurity/trivy-kubernetes v0.6.6 github.com/aquasecurity/trivy-policies v0.10.0 github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2/config v1.27.10 @@ -50,8 +50,8 @@ require ( github.com/go-openapi/strfmt v0.23.0 github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/golang/protobuf v1.5.3 - github.com/google/go-containerregistry v0.19.0 + github.com/golang/protobuf v1.5.4 + github.com/google/go-containerregistry v0.19.1 github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/uuid v1.6.0 github.com/google/wire v0.6.0 @@ -105,7 +105,7 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 github.com/xlab/treeprint v1.2.0 go.etcd.io/bbolt v1.3.9 - go.uber.org/zap v1.27.0 + go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/mod v0.16.0 golang.org/x/net v0.23.0 @@ -115,7 +115,7 @@ require ( golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.29.1 + k8s.io/api v0.29.3 k8s.io/utils v0.0.0-20231127182322-b307cd553661 modernc.org/sqlite v1.29.7 ) @@ -177,7 +177,7 @@ require ( github.com/antchfx/xpath v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.49.21 // indirect + github.com/aws/aws-sdk-go v1.51.16 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect @@ -411,14 +411,14 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.29.0 // indirect - k8s.io/apimachinery v0.29.1 // indirect + k8s.io/apimachinery v0.29.3 // indirect k8s.io/apiserver v0.29.0 // indirect - k8s.io/cli-runtime v0.29.0 // indirect - k8s.io/client-go v0.29.0 // indirect - k8s.io/component-base v0.29.0 // indirect + k8s.io/cli-runtime v0.29.3 // indirect + k8s.io/client-go v0.29.3 // indirect + k8s.io/component-base v0.29.3 // indirect k8s.io/klog/v2 v2.120.0 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/kubectl v0.29.0 // indirect + k8s.io/kubectl v0.29.3 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect modernc.org/libc v1.49.3 // indirect modernc.org/mathutil v1.6.0 // indirect diff --git a/go.sum b/go.sum index aa2ebaa46191..59e9518afb1e 100644 --- a/go.sum +++ b/go.sum @@ -349,8 +349,8 @@ github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTU github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= -github.com/aquasecurity/trivy-kubernetes v0.6.3 h1:Hmo0pefXRsyVYsii62WUQyt3xMHjm37ipPESeWM/LNA= -github.com/aquasecurity/trivy-kubernetes v0.6.3/go.mod h1:v6B8SO2ep718ccGbbjhpzMn6p27IijS+dMb+MeYz3jQ= +github.com/aquasecurity/trivy-kubernetes v0.6.6 h1:90Y3FH7Mrh+M06+RyLhl26HA06kWbhvTWwKWpt9jE0M= +github.com/aquasecurity/trivy-kubernetes v0.6.6/go.mod h1:+NJBTgQErUmq21Ag71q/EuXZKIP+/OJvBAR0G+YUkKo= github.com/aquasecurity/trivy-policies v0.10.0 h1:QONOsIFi6+WyB+7NGMBQeCgMFcRg6RV9dTBBpeOFDxU= github.com/aquasecurity/trivy-policies v0.10.0/go.mod h1:7WU0GTUqtQxqQ+FV3JAy7lskQQZU6lp7Mz1i8GEapFw= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= @@ -366,8 +366,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.49.21 h1:Rl8KW6HqkwzhATwvXhyr7vD4JFUMi7oXGAw9SrxxIFY= -github.com/aws/aws-sdk-go v1.49.21/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.51.16 h1:vnWKK8KjbftEkuPX8bRj3WHsLy1uhotn0eXptpvrxJI= +github.com/aws/aws-sdk-go v1.51.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= @@ -948,8 +948,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -981,8 +981,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.6.0/go.mod h1:euCCtNbZ6tKqi1E72vwDj2xZcN5ttKpZLfa/wSo5iLw= -github.com/google/go-containerregistry v0.19.0 h1:uIsMRBV7m/HDkDxE/nXMnv1q+lOOSPlQ/ywc5JbB8Ic= -github.com/google/go-containerregistry v0.19.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= +github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= +github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -2476,32 +2476,32 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= -k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= +k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw= +k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= -k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= +k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.29.0 h1:Y1xEMjJkP+BIi0GSEv1BBrf1jLU9UPfAnnGGbbDdp7o= k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM= -k8s.io/cli-runtime v0.29.0 h1:q2kC3cex4rOBLfPOnMSzV2BIrrQlx97gxHJs21KxKS4= -k8s.io/cli-runtime v0.29.0/go.mod h1:VKudXp3X7wR45L+nER85YUzOQIru28HQpXr0mTdeCrk= +k8s.io/cli-runtime v0.29.3 h1:r68rephmmytoywkw2MyJ+CxjpasJDQY7AGc3XY2iv1k= +k8s.io/cli-runtime v0.29.3/go.mod h1:aqVUsk86/RhaGJwDhHXH0jcdqBrgdF3bZWk4Z9D4mkM= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= -k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= +k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg= +k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= -k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= +k8s.io/component-base v0.29.3 h1:Oq9/nddUxlnrCuuR2K/jp6aflVvc0uDvxMzAWxnGzAo= +k8s.io/component-base v0.29.3/go.mod h1:Yuj33XXjuOk2BAaHsIGHhCKZQAgYKhqIxIjIr2UXYio= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= @@ -2514,8 +2514,8 @@ k8s.io/klog/v2 v2.120.0/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/kubectl v0.29.0 h1:Oqi48gXjikDhrBF67AYuZRTcJV4lg2l42GmvsP7FmYI= -k8s.io/kubectl v0.29.0/go.mod h1:0jMjGWIcMIQzmUaMgAzhSELv5WtHo2a8pq67DtviAJs= +k8s.io/kubectl v0.29.3 h1:RuwyyIU42MAISRIePaa8Q7A3U74Q9P4MoJbDFz9o3us= +k8s.io/kubectl v0.29.3/go.mod h1:yCxfY1dbwgVdEt2zkJ6d5NNLOhhWgTyrqACIoFhpdd4= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= diff --git a/integration/k8s_test.go b/integration/k8s_test.go index 62a0bbd2d526..498f667932c4 100644 --- a/integration/k8s_test.go +++ b/integration/k8s_test.go @@ -31,7 +31,7 @@ func TestK8s(t *testing.T) { "--cache-dir", cacheDir, "k8s", - "cluster", + "kind-kind-test", "--report", "summary", "-q", @@ -39,10 +39,6 @@ func TestK8s(t *testing.T) { "5m0s", "--format", "json", - "--components", - "workload", - "--context", - "kind-kind-test", "--output", outputFile, } @@ -79,12 +75,10 @@ func TestK8s(t *testing.T) { outputFile := filepath.Join(t.TempDir(), "output.json") osArgs := []string{ "k8s", - "cluster", + "kind-kind-test", "--format", "cyclonedx", "-q", - "--context", - "kind-kind-test", "--output", outputFile, } @@ -111,51 +105,5 @@ func TestK8s(t *testing.T) { assert.True(t, lo.SomeBy(*got.Dependencies, func(r cdx.Dependency) bool { return len(*r.Dependencies) > 0 })) - - }) - - t.Run("specific resource scan", func(t *testing.T) { - // Set up the output file - outputFile := filepath.Join(t.TempDir(), "output.json") - - osArgs := []string{ - "k8s", - "-n", - "default", - "deployments/nginx-deployment", - "-q", - "--timeout", - "5m0s", - "--format", - "json", - "--components", - "workload", - "--context", - "kind-kind-test", - "--output", - outputFile, - } - - // Run Trivy - err := execute(osArgs) - require.NoError(t, err) - - var got report.Report - f, err := os.Open(outputFile) - require.NoError(t, err) - defer f.Close() - - err = json.NewDecoder(f).Decode(&got) - require.NoError(t, err) - - // Flatten findings - results := lo.FlatMap(got.Resources, func(resource report.Resource, _ int) []types.Result { - return resource.Results - }) - - // Has vulnerabilities - assert.True(t, lo.SomeBy(results, func(r types.Result) bool { - return len(r.Vulnerabilities) > 0 - })) }) } diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 902b92b78087..05e14ea87f82 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -934,22 +934,21 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), } cmd := &cobra.Command{ - Use: "kubernetes [flags] { cluster | all | specific resources like kubectl. eg: pods, pod/NAME }", + Use: "kubernetes [flags] [CONTEXT]", Aliases: []string{"k8s"}, GroupID: groupScanning, Short: "[EXPERIMENTAL] Scan kubernetes cluster", + Long: `Default context in kube configuration will be used unless specified`, Example: ` # cluster scanning - $ trivy k8s --report summary cluster + $ trivy k8s --report summary - # namespace scanning: - $ trivy k8s -n kube-system --report summary all + # cluster scanning with specific namespace: + $ trivy k8s --include-namespaces kube-system --report summary - # resources scanning: - $ trivy k8s --report=summary deploy - $ trivy k8s --namespace=kube-system --report=summary deploy,configmaps - - # resource scanning: - $ trivy k8s deployment/orion + # cluster with specific context: + $ trivy k8s kind-kind --report summary + + `, PreRunE: func(cmd *cobra.Command, args []string) error { if err := k8sFlags.Bind(cmd); err != nil { @@ -1236,7 +1235,7 @@ func validateArgs(cmd *cobra.Command, args []string) error { return nil } - if len(args) == 0 && viper.GetString(flag.InputFlag.ConfigName) == "" { + if len(args) == 0 && viper.GetString(flag.InputFlag.ConfigName) == "" && cmd.Name() != "kubernetes" { if err := cmd.Help(); err != nil { return err } diff --git a/pkg/flag/kubernetes_flags.go b/pkg/flag/kubernetes_flags.go index a2f47ace08d9..e325eb861045 100644 --- a/pkg/flag/kubernetes_flags.go +++ b/pkg/flag/kubernetes_flags.go @@ -10,20 +10,6 @@ import ( ) var ( - ClusterContextFlag = Flag[string]{ - Name: "context", - ConfigName: "kubernetes.context", - Usage: "specify a context to scan", - Aliases: []Alias{ - {Name: "ctx"}, - }, - } - K8sNamespaceFlag = Flag[string]{ - Name: "namespace", - ConfigName: "kubernetes.namespace", - Shorthand: "n", - Usage: "specify a namespace to scan", - } KubeConfigFlag = Flag[string]{ Name: "kubeconfig", ConfigName: "kubernetes.kubeconfig", @@ -52,12 +38,6 @@ var ( ConfigName: "kubernetes.tolerations", Usage: "specify node-collector job tolerations (example: key1=value1:NoExecute,key2=value2:NoSchedule)", } - AllNamespaces = Flag[bool]{ - Name: "all-namespaces", - ConfigName: "kubernetes.all-namespaces", - Shorthand: "A", - Usage: "fetch resources from all cluster namespaces", - } NodeCollectorNamespace = Flag[string]{ Name: "node-collector-namespace", ConfigName: "kubernetes.node-collector.namespace", @@ -80,6 +60,27 @@ var ( ConfigName: "kubernetes.exclude.nodes", Usage: "indicate the node labels that the node-collector job should exclude from scanning (example: kubernetes.io/arch:arm64,team:dev)", } + + ExcludeKinds = Flag[[]string]{ + Name: "exclude-kinds", + ConfigName: "kubernetes.excludeKinds", + Usage: "indicate the kinds exclude from scanning (example: node)", + } + IncludeKinds = Flag[[]string]{ + Name: "include-kinds", + ConfigName: "kubernetes.includeKinds", + Usage: "indicate the kinds included in scanning (example: node)", + } + ExcludeNamespaces = Flag[[]string]{ + Name: "exclude-namespaces", + ConfigName: "kubernetes.excludeNamespaces", + Usage: "indicate the namespaces excluded from scanning (example: kube-system)", + } + IncludeNamespaces = Flag[[]string]{ + Name: "include-namespaces", + ConfigName: "kubernetes.includeNamespaces", + Usage: "indicate the namespaces included in scanning (example: kube-system)", + } QPS = Flag[float64]{ Name: "qps", ConfigName: "kubernetes.qps", @@ -95,49 +96,52 @@ var ( ) type K8sFlagGroup struct { - ClusterContext *Flag[string] - Namespace *Flag[string] KubeConfig *Flag[string] Components *Flag[[]string] K8sVersion *Flag[string] Tolerations *Flag[[]string] NodeCollectorImageRef *Flag[string] - AllNamespaces *Flag[bool] NodeCollectorNamespace *Flag[string] ExcludeOwned *Flag[bool] ExcludeNodes *Flag[[]string] + ExcludeKinds *Flag[[]string] + IncludeKinds *Flag[[]string] + ExcludeNamespaces *Flag[[]string] + IncludeNamespaces *Flag[[]string] QPS *Flag[float64] Burst *Flag[int] } type K8sOptions struct { - ClusterContext string - Namespace string KubeConfig string Components []string K8sVersion string Tolerations []corev1.Toleration NodeCollectorImageRef string - AllNamespaces bool NodeCollectorNamespace string ExcludeOwned bool ExcludeNodes map[string]string + ExcludeKinds []string + IncludeKinds []string + ExcludeNamespaces []string + IncludeNamespaces []string QPS float32 Burst int } func NewK8sFlagGroup() *K8sFlagGroup { return &K8sFlagGroup{ - ClusterContext: ClusterContextFlag.Clone(), - Namespace: K8sNamespaceFlag.Clone(), KubeConfig: KubeConfigFlag.Clone(), Components: ComponentsFlag.Clone(), K8sVersion: K8sVersionFlag.Clone(), Tolerations: TolerationsFlag.Clone(), - AllNamespaces: AllNamespaces.Clone(), NodeCollectorNamespace: NodeCollectorNamespace.Clone(), ExcludeOwned: ExcludeOwned.Clone(), ExcludeNodes: ExcludeNodes.Clone(), + ExcludeKinds: ExcludeKinds.Clone(), + IncludeKinds: IncludeKinds.Clone(), + ExcludeNamespaces: ExcludeNamespaces.Clone(), + IncludeNamespaces: IncludeNamespaces.Clone(), NodeCollectorImageRef: NodeCollectorImageRef.Clone(), QPS: QPS.Clone(), Burst: Burst.Clone(), @@ -150,17 +154,18 @@ func (f *K8sFlagGroup) Name() string { func (f *K8sFlagGroup) Flags() []Flagger { return []Flagger{ - f.ClusterContext, - f.Namespace, f.KubeConfig, f.Components, f.K8sVersion, f.Tolerations, - f.AllNamespaces, f.NodeCollectorNamespace, f.ExcludeOwned, f.ExcludeNodes, f.NodeCollectorImageRef, + f.ExcludeKinds, + f.IncludeKinds, + f.ExcludeNamespaces, + f.IncludeNamespaces, f.QPS, f.Burst, } @@ -185,20 +190,27 @@ func (f *K8sFlagGroup) ToOptions() (K8sOptions, error) { } exludeNodeLabels[excludeNodeParts[0]] = excludeNodeParts[1] } + if len(f.ExcludeNamespaces.Value()) > 0 && len(f.IncludeNamespaces.Value()) > 0 { + return K8sOptions{}, fmt.Errorf("include-namespaces and exclude-namespaces flags cannot be used together") + } + if len(f.ExcludeKinds.Value()) > 0 && len(f.IncludeKinds.Value()) > 0 { + return K8sOptions{}, fmt.Errorf("include-kinds and exclude-kinds flags cannot be used together") + } return K8sOptions{ - ClusterContext: f.ClusterContext.Value(), - Namespace: f.Namespace.Value(), KubeConfig: f.KubeConfig.Value(), Components: f.Components.Value(), K8sVersion: f.K8sVersion.Value(), Tolerations: tolerations, - AllNamespaces: f.AllNamespaces.Value(), NodeCollectorNamespace: f.NodeCollectorNamespace.Value(), ExcludeOwned: f.ExcludeOwned.Value(), ExcludeNodes: exludeNodeLabels, NodeCollectorImageRef: f.NodeCollectorImageRef.Value(), QPS: float32(f.QPS.Value()), + ExcludeKinds: f.ExcludeKinds.Value(), + IncludeKinds: f.IncludeKinds.Value(), + ExcludeNamespaces: f.ExcludeNamespaces.Value(), + IncludeNamespaces: f.IncludeNamespaces.Value(), Burst: f.Burst.Value(), }, nil } diff --git a/pkg/k8s/commands/cluster.go b/pkg/k8s/commands/cluster.go index b4ad9be78347..bc985bff7393 100644 --- a/pkg/k8s/commands/cluster.go +++ b/pkg/k8s/commands/cluster.go @@ -3,7 +3,6 @@ package commands import ( "context" - "go.uber.org/zap" "golang.org/x/exp/slices" "golang.org/x/xerrors" @@ -16,9 +15,6 @@ import ( // clusterRun runs scan on kubernetes cluster func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) error { - // TODO: replace with log.Logger - logger, _ := zap.NewProduction() - if err := validateReportArguments(opts); err != nil { return err } @@ -26,13 +22,20 @@ func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) err var err error switch opts.Format { case types.FormatCycloneDX: - artifacts, err = trivyk8s.New(cluster, logger.Sugar()).ListClusterBomInfo(ctx) + artifacts, err = trivyk8s.New(cluster).ListClusterBomInfo(ctx) if err != nil { return xerrors.Errorf("get k8s artifacts with node info error: %w", err) } case types.FormatJSON, types.FormatTable: + k8sOpts := []trivyk8s.K8sOption{ + trivyk8s.WithExcludeNamespaces(opts.ExcludeNamespaces), + trivyk8s.WithIncludeNamespaces(opts.IncludeNamespaces), + trivyk8s.WithExcludeKinds(opts.ExcludeKinds), + trivyk8s.WithIncludeKinds(opts.IncludeKinds), + trivyk8s.WithExcludeOwned(opts.ExcludeOwned), + } if opts.Scanners.AnyEnabled(types.MisconfigScanner) && slices.Contains(opts.Components, "infra") { - artifacts, err = trivyk8s.New(cluster, logger.Sugar(), trivyk8s.WithExcludeOwned(opts.ExcludeOwned)).ListArtifactAndNodeInfo(ctx, + artifacts, err = trivyk8s.New(cluster, k8sOpts...).ListArtifactAndNodeInfo(ctx, trivyk8s.WithScanJobNamespace(opts.NodeCollectorNamespace), trivyk8s.WithIgnoreLabels(opts.ExcludeNodes), trivyk8s.WithScanJobImageRef(opts.NodeCollectorImageRef), @@ -41,7 +44,7 @@ func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) err return xerrors.Errorf("get k8s artifacts with node info error: %w", err) } } else { - artifacts, err = trivyk8s.New(cluster, logger.Sugar()).ListArtifacts(ctx) + artifacts, err = trivyk8s.New(cluster, k8sOpts...).ListArtifacts(ctx) if err != nil { return xerrors.Errorf("get k8s artifacts error: %w", err) } diff --git a/pkg/k8s/commands/namespace.go b/pkg/k8s/commands/namespace.go deleted file mode 100644 index b6c85e8dae00..000000000000 --- a/pkg/k8s/commands/namespace.go +++ /dev/null @@ -1,44 +0,0 @@ -package commands - -import ( - "context" - - "go.uber.org/zap" - "golang.org/x/xerrors" - - "github.com/aquasecurity/trivy-kubernetes/pkg/k8s" - "github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s" - "github.com/aquasecurity/trivy/pkg/flag" -) - -// namespaceRun runs scan on kubernetes cluster -func namespaceRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) error { - // TODO: replace with slog.Logger - logger, _ := zap.NewProduction() - - if err := validateReportArguments(opts); err != nil { - return err - } - var trivyk trivyk8s.TrivyK8S - if opts.AllNamespaces { - trivyk = trivyk8s.New(cluster, logger.Sugar()).AllNamespaces() - } else { - trivyk = trivyk8s.New(cluster, logger.Sugar()).Namespace(getNamespace(opts, cluster.GetCurrentNamespace())) - } - - artifacts, err := trivyk.ListArtifacts(ctx) - if err != nil { - return xerrors.Errorf("get k8s artifacts error: %w", err) - } - - runner := newRunner(opts, cluster.GetCurrentContext()) - return runner.run(ctx, artifacts) -} - -func getNamespace(opts flag.Options, currentNamespace string) string { - if opts.K8sOptions.Namespace != "" { - return opts.K8sOptions.Namespace - } - - return currentNamespace -} diff --git a/pkg/k8s/commands/namespace_test.go b/pkg/k8s/commands/namespace_test.go deleted file mode 100644 index 661360d7007b..000000000000 --- a/pkg/k8s/commands/namespace_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package commands - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/aquasecurity/trivy/pkg/flag" -) - -func Test_getNamespace(t *testing.T) { - - tests := []struct { - name string - currentNamespace string - opts flag.Options - want string - }{ - { - name: "--namespace=custom", - currentNamespace: "default", - opts: flag.Options{ - K8sOptions: flag.K8sOptions{ - Namespace: "custom", - }, - }, - want: "custom", - }, - { - name: "no namespaces passed", - currentNamespace: "default", - opts: flag.Options{ - K8sOptions: flag.K8sOptions{ - Namespace: "", - }, - }, - want: "default", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - got := getNamespace(test.opts, test.currentNamespace) - assert.Equal(t, test.want, got) - }) - } -} diff --git a/pkg/k8s/commands/resource.go b/pkg/k8s/commands/resource.go deleted file mode 100644 index 1662fe25d4d8..000000000000 --- a/pkg/k8s/commands/resource.go +++ /dev/null @@ -1,73 +0,0 @@ -package commands - -import ( - "context" - "strings" - - "go.uber.org/zap" - "golang.org/x/xerrors" - - "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" - "github.com/aquasecurity/trivy-kubernetes/pkg/k8s" - "github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s" - "github.com/aquasecurity/trivy/pkg/flag" -) - -// resourceRun runs scan on kubernetes cluster -func resourceRun(ctx context.Context, args []string, opts flag.Options, cluster k8s.Cluster) error { - kind, name, err := extractKindAndName(args) - if err != nil { - return err - } - - runner := newRunner(opts, cluster.GetCurrentContext()) - - var trivyk trivyk8s.TrivyK8S - - // TODO: replace with slog.Logger - logger, _ := zap.NewProduction() - trivyk = trivyk8s.New(cluster, logger.Sugar(), trivyk8s.WithExcludeOwned(opts.ExcludeOwned)) - - if opts.AllNamespaces { - trivyk = trivyk.AllNamespaces() - } else { - trivyk = trivyk.Namespace(getNamespace(opts, cluster.GetCurrentNamespace())) - } - - if name == "" { // pods or configmaps etc - if err = validateReportArguments(opts); err != nil { - return err - } - - targets, err := trivyk.Resources(kind).ListArtifacts(ctx) - if err != nil { - return err - } - - return runner.run(ctx, targets) - } - - // pod/NAME or pod NAME etc - artifact, err := trivyk.GetArtifact(ctx, kind, name) - if err != nil { - return err - } - - return runner.run(ctx, []*artifacts.Artifact{artifact}) -} - -func extractKindAndName(args []string) (string, string, error) { - switch len(args) { - case 1: - s := strings.Split(args[0], "/") - if len(s) != 2 { - return args[0], "", nil - } - - return s[0], s[1], nil - case 2: - return args[0], args[1], nil - } - - return "", "", xerrors.Errorf("can't parse arguments %v. Please run `trivy k8s` for usage.", args) -} diff --git a/pkg/k8s/commands/resource_test.go b/pkg/k8s/commands/resource_test.go deleted file mode 100644 index 27ce9df5d42f..000000000000 --- a/pkg/k8s/commands/resource_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package commands - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_extractKindAndName(t *testing.T) { - tests := []struct { - name string - args []string - expectedKind string - expectedName string - expectedError string - }{ - { - name: "one argument only", - args: []string{"deploy"}, - expectedKind: "deploy", - expectedName: "", - }, - { - name: "one argument only, multiple targets", - args: []string{"deploy,configmaps"}, - expectedKind: "deploy,configmaps", - expectedName: "", - }, - { - name: "bar separated", - args: []string{"deploy/orion"}, - expectedKind: "deploy", - expectedName: "orion", - }, - { - name: "space separated", - args: []string{"deploy", "lua"}, - expectedKind: "deploy", - expectedName: "lua", - }, - { - name: "multiple arguments separated", - args: []string{"test", "test", "test"}, - expectedError: "can't parse arguments [test test test]. Please run `trivy k8s` for usage.", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - kind, name, err := extractKindAndName(test.args) - - if len(test.expectedError) > 0 { - assert.Error(t, err, test.expectedError) - return - } - - assert.Equal(t, test.expectedKind, kind) - assert.Equal(t, test.expectedName, name) - }) - } -} diff --git a/pkg/k8s/commands/run.go b/pkg/k8s/commands/run.go index 567c63fa461e..179853f99121 100644 --- a/pkg/k8s/commands/run.go +++ b/pkg/k8s/commands/run.go @@ -20,48 +20,30 @@ import ( "github.com/aquasecurity/trivy/pkg/types" ) -const ( - clusterArtifact = "cluster" - allArtifact = "all" -) - // Run runs a k8s scan func Run(ctx context.Context, args []string, opts flag.Options) error { - ctx, cancel := context.WithTimeout(ctx, opts.Timeout) - defer cancel() - - ctx = log.WithContextPrefix(ctx, "k8s") - - cluster, err := k8s.GetCluster( - k8s.WithContext(opts.K8sOptions.ClusterContext), + clusterOptions := []k8s.ClusterOption{ k8s.WithKubeConfig(opts.K8sOptions.KubeConfig), k8s.WithBurst(opts.K8sOptions.Burst), k8s.WithQPS(opts.K8sOptions.QPS), - ) + } + if len(args) > 0 { + clusterOptions = append(clusterOptions, k8s.WithContext(args[0])) + } + cluster, err := k8s.GetCluster(clusterOptions...) if err != nil { return xerrors.Errorf("failed getting k8s cluster: %w", err) } + ctx, cancel := context.WithTimeout(ctx, opts.Timeout) defer func() { + cancel() if errors.Is(err, context.DeadlineExceeded) { - log.Warn("Increase --timeout value") + log.WarnContext(ctx, "Increase --timeout value") } }() opts.K8sVersion = cluster.GetClusterVersion() - switch args[0] { - case clusterArtifact: - return clusterRun(ctx, opts, cluster) - case allArtifact: - if opts.Format == types.FormatCycloneDX { - return xerrors.Errorf("KBOM with CycloneDX format is not supported for all namespace scans") - } - return namespaceRun(ctx, opts, cluster) - default: // resourceArtifact - if opts.Format == types.FormatCycloneDX { - return xerrors.Errorf("KBOM with CycloneDX format is not supported for resource scans") - } - return resourceRun(ctx, args, opts, cluster) - } + return clusterRun(ctx, opts, cluster) } type runner struct { @@ -71,8 +53,8 @@ type runner struct { func newRunner(flagOpts flag.Options, cluster string) *runner { return &runner{ - flagOpts: flagOpts, - cluster: cluster, + flagOpts, + cluster, } } @@ -86,7 +68,7 @@ func (r *runner) run(ctx context.Context, artifacts []*k8sArtifacts.Artifact) er } defer func() { if err := runner.Close(ctx); err != nil { - log.ErrorContext(ctx, "Failed to close runner", log.Err(err)) + log.ErrorContext(ctx, "failed to close runner: %s", err) } }() From 8e6cd0e917fb54f72ca8054e2d94c3f53f764134 Mon Sep 17 00:00:00 2001 From: chenk Date: Sat, 27 Apr 2024 19:39:47 +0300 Subject: [PATCH 009/352] fix: trivy k8s avoid deleting non-default node collector namespace (#6559) Signed-off-by: chenk --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 03b9790a1aff..786c80116062 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/aquasecurity/trivy-aws v0.8.0 github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 - github.com/aquasecurity/trivy-kubernetes v0.6.6 + github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb github.com/aquasecurity/trivy-policies v0.10.0 github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2/config v1.27.10 diff --git a/go.sum b/go.sum index 59e9518afb1e..664801e3fc7f 100644 --- a/go.sum +++ b/go.sum @@ -349,8 +349,8 @@ github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTU github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= -github.com/aquasecurity/trivy-kubernetes v0.6.6 h1:90Y3FH7Mrh+M06+RyLhl26HA06kWbhvTWwKWpt9jE0M= -github.com/aquasecurity/trivy-kubernetes v0.6.6/go.mod h1:+NJBTgQErUmq21Ag71q/EuXZKIP+/OJvBAR0G+YUkKo= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb h1:U07awOdXGT8NMwTPVuXkL/cKZyvO4PuG+VX1oIvsuiQ= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb/go.mod h1:+NJBTgQErUmq21Ag71q/EuXZKIP+/OJvBAR0G+YUkKo= github.com/aquasecurity/trivy-policies v0.10.0 h1:QONOsIFi6+WyB+7NGMBQeCgMFcRg6RV9dTBBpeOFDxU= github.com/aquasecurity/trivy-policies v0.10.0/go.mod h1:7WU0GTUqtQxqQ+FV3JAy7lskQQZU6lp7Mz1i8GEapFw= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= From 916f6c66f8031bb311657944ff3ca1284169902e Mon Sep 17 00:00:00 2001 From: guangwu Date: Mon, 29 Apr 2024 14:13:03 +0800 Subject: [PATCH 010/352] fix: close plugin.yaml (#6577) Signed-off-by: guoguangwu --- pkg/plugin/plugin.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index cbff3f4d01f7..a72419aceb5a 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -354,6 +354,7 @@ func loadMetadata(dir string) (Plugin, error) { if err != nil { return Plugin{}, xerrors.Errorf("file open error: %w", err) } + defer f.Close() var plugin Plugin if err = yaml.NewDecoder(f).Decode(&plugin); err != nil { From e3bef02018208057f0d840b01f12e6867b0cc1ff Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:33:53 +0600 Subject: [PATCH 011/352] feat: add support `environment.yaml` files (#6569) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- .github/workflows/semantic-pr.yaml | 1 + docs/docs/coverage/os/conda.md | 36 ++++ docs/docs/coverage/os/index.md | 35 ++-- integration/repo_test.go | 9 + .../conda-environment-cyclonedx.json.golden | 80 ++++++++ .../repo/conda-environment/environment.yaml | 6 + mkdocs.yml | 1 + .../parser/conda/environment/parse.go | 103 ++++++++++ .../parser/conda/environment/parse_test.go | 192 ++++++++++++++++++ .../conda/environment/testdata/happy.yaml | 21 ++ .../conda/environment/testdata/invalid.yaml | 1 + pkg/detector/library/driver.go | 2 +- pkg/fanal/analyzer/all/import.go | 1 + pkg/fanal/analyzer/const.go | 3 + .../language/conda/environment/environment.go | 41 ++++ .../conda/environment/environment_test.go | 131 ++++++++++++ .../environment/testdata/environment.yaml | 9 + .../conda/environment/testdata/invalid.yaml | 1 + pkg/fanal/types/const.go | 4 + pkg/purl/purl.go | 2 +- pkg/purl/purl_test.go | 13 ++ 21 files changed, 673 insertions(+), 19 deletions(-) create mode 100644 docs/docs/coverage/os/conda.md create mode 100644 integration/testdata/conda-environment-cyclonedx.json.golden create mode 100644 integration/testdata/fixtures/repo/conda-environment/environment.yaml create mode 100644 pkg/dependency/parser/conda/environment/parse.go create mode 100644 pkg/dependency/parser/conda/environment/parse_test.go create mode 100644 pkg/dependency/parser/conda/environment/testdata/happy.yaml create mode 100644 pkg/dependency/parser/conda/environment/testdata/invalid.yaml create mode 100644 pkg/fanal/analyzer/language/conda/environment/environment.go create mode 100644 pkg/fanal/analyzer/language/conda/environment/environment_test.go create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/environment.yaml create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/invalid.yaml diff --git a/.github/workflows/semantic-pr.yaml b/.github/workflows/semantic-pr.yaml index 0b84c110abb5..f02ef758ae91 100644 --- a/.github/workflows/semantic-pr.yaml +++ b/.github/workflows/semantic-pr.yaml @@ -75,6 +75,7 @@ jobs: dart swift bitnami + conda os lang diff --git a/docs/docs/coverage/os/conda.md b/docs/docs/coverage/os/conda.md new file mode 100644 index 000000000000..79a49194fd66 --- /dev/null +++ b/docs/docs/coverage/os/conda.md @@ -0,0 +1,36 @@ +# Conda + +Trivy supports the following scanners for Conda packages. + +| Scanner | Supported | +|:-------------:|:---------:| +| SBOM | ✓ | +| Vulnerability | - | +| License | ✓[^1] | + + +## SBOM +Trivy detects packages that have been installed with `Conda`. + + +### `.json` +Trivy parses `/envs//conda-meta/.json` files to find the version and license for the dependencies installed in your env. + +### `environment.yml`[^2] +Trivy supports parsing [environment.yml][environment.yml][^2] files to find dependency list. + +!!! note + License detection is currently not supported. + +`environment.yml`[^2] files supports [version range][env-version-range]. We can't be sure about versions for these dependencies. +Therefore, you need to use `conda env export` command to get dependency list in `Conda` default format before scanning `environment.yml`[^2] file. + +!!! note + For dependencies in a non-Conda format, Trivy doesn't include a version of them. + + +[^1]: License detection is only supported for `.json` files +[^2]: Trivy supports both `yaml` and `yml` extensions. + +[environment.yml]: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#sharing-an-environment +[env-version-range]: https://docs.conda.io/projects/conda-build/en/latest/resources/package-spec.html#examples-of-package-specs diff --git a/docs/docs/coverage/os/index.md b/docs/docs/coverage/os/index.md index e04a452fc4d3..3294557d6a7c 100644 --- a/docs/docs/coverage/os/index.md +++ b/docs/docs/coverage/os/index.md @@ -9,23 +9,24 @@ Trivy supports operating systems for ## Supported OS -| OS | Supported Versions | Package Managers | -|-----------------------------------------------|-------------------------------------|------------------| -| [Alpine Linux](alpine.md) | 2.2 - 2.7, 3.0 - 3.19, edge | apk | -| [Wolfi Linux](wolfi.md) | (n/a) | apk | -| [Chainguard](chainguard.md) | (n/a) | apk | -| [Red Hat Enterprise Linux](rhel.md) | 6, 7, 8 | dnf/yum/rpm | -| [CentOS](centos.md)[^1] | 6, 7, 8 | dnf/yum/rpm | -| [AlmaLinux](alma.md) | 8, 9 | dnf/yum/rpm | -| [Rocky Linux](rocky.md) | 8, 9 | dnf/yum/rpm | -| [Oracle Linux](oracle.md) | 5, 6, 7, 8 | dnf/yum/rpm | -| [CBL-Mariner](cbl-mariner.md) | 1.0, 2.0 | dnf/yum/rpm | -| [Amazon Linux](amazon.md) | 1, 2, 2023 | dnf/yum/rpm | -| [openSUSE Leap](suse.md) | 42, 15 | zypper/rpm | -| [SUSE Enterprise Linux](suse.md) | 11, 12, 15 | zypper/rpm | -| [Photon OS](photon.md) | 1.0, 2.0, 3.0, 4.0 | tndf/yum/rpm | -| [Debian GNU/Linux](debian.md) | 7, 8, 9, 10, 11, 12 | apt/dpkg | -| [Ubuntu](ubuntu.md) | All versions supported by Canonical | apt/dpkg | +| OS | Supported Versions | Package Managers | +|--------------------------------------|-------------------------------------|------------------| +| [Alpine Linux](alpine.md) | 2.2 - 2.7, 3.0 - 3.19, edge | apk | +| [Wolfi Linux](wolfi.md) | (n/a) | apk | +| [Chainguard](chainguard.md) | (n/a) | apk | +| [Red Hat Enterprise Linux](rhel.md) | 6, 7, 8 | dnf/yum/rpm | +| [CentOS](centos.md)[^1] | 6, 7, 8 | dnf/yum/rpm | +| [AlmaLinux](alma.md) | 8, 9 | dnf/yum/rpm | +| [Rocky Linux](rocky.md) | 8, 9 | dnf/yum/rpm | +| [Oracle Linux](oracle.md) | 5, 6, 7, 8 | dnf/yum/rpm | +| [CBL-Mariner](cbl-mariner.md) | 1.0, 2.0 | dnf/yum/rpm | +| [Amazon Linux](amazon.md) | 1, 2, 2023 | dnf/yum/rpm | +| [openSUSE Leap](suse.md) | 42, 15 | zypper/rpm | +| [SUSE Enterprise Linux](suse.md) | 11, 12, 15 | zypper/rpm | +| [Photon OS](photon.md) | 1.0, 2.0, 3.0, 4.0 | tndf/yum/rpm | +| [Debian GNU/Linux](debian.md) | 7, 8, 9, 10, 11, 12 | apt/dpkg | +| [Ubuntu](ubuntu.md) | All versions supported by Canonical | apt/dpkg | +| [OSs with installed Conda](conda.md) | - | conda | ## Supported container images diff --git a/integration/repo_test.go b/integration/repo_test.go index ba11aa9ccb0f..8d787104e63f 100644 --- a/integration/repo_test.go +++ b/integration/repo_test.go @@ -341,6 +341,15 @@ func TestRepository(t *testing.T) { }, golden: "testdata/conda-cyclonedx.json.golden", }, + { + name: "conda environment.yaml generating CycloneDX SBOM", + args: args{ + command: "fs", + format: "cyclonedx", + input: "testdata/fixtures/repo/conda-environment", + }, + golden: "testdata/conda-environment-cyclonedx.json.golden", + }, { name: "pom.xml generating CycloneDX SBOM (with vulnerabilities)", args: args{ diff --git a/integration/testdata/conda-environment-cyclonedx.json.golden b/integration/testdata/conda-environment-cyclonedx.json.golden new file mode 100644 index 000000000000..e927b7594bfb --- /dev/null +++ b/integration/testdata/conda-environment-cyclonedx.json.golden @@ -0,0 +1,80 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000004", + "version": 1, + "metadata": { + "timestamp": "2021-08-25T12:20:30+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000001", + "type": "application", + "name": "testdata/fixtures/repo/conda-environment", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000002", + "type": "application", + "name": "environment.yaml", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "lang-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "conda-environment" + } + ] + }, + { + "bom-ref": "pkg:conda/bzip2@1.0.8", + "type": "library", + "name": "bzip2", + "version": "1.0.8", + "purl": "pkg:conda/bzip2@1.0.8", + "properties": [ + { + "name": "aquasecurity:trivy:PkgType", + "value": "conda-environment" + } + ] + } + ], + "dependencies": [ + { + "ref": "3ff14136-e09f-4df9-80ea-000000000001", + "dependsOn": [ + "3ff14136-e09f-4df9-80ea-000000000002" + ] + }, + { + "ref": "3ff14136-e09f-4df9-80ea-000000000002", + "dependsOn": [ + "pkg:conda/bzip2@1.0.8" + ] + }, + { + "ref": "pkg:conda/bzip2@1.0.8", + "dependsOn": [] + } + ], + "vulnerabilities": [] +} diff --git a/integration/testdata/fixtures/repo/conda-environment/environment.yaml b/integration/testdata/fixtures/repo/conda-environment/environment.yaml new file mode 100644 index 000000000000..cf47d3632faf --- /dev/null +++ b/integration/testdata/fixtures/repo/conda-environment/environment.yaml @@ -0,0 +1,6 @@ +name: test-env +channels: + - defaults +dependencies: + - bzip2=1.0.8=h998d150_5 +prefix: /opt/conda/envs/test-env diff --git a/mkdocs.yml b/mkdocs.yml index f85fd7a8f209..75aff50f9fe2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -77,6 +77,7 @@ nav: - CBL-Mariner: docs/coverage/os/cbl-mariner.md - CentOS: docs/coverage/os/centos.md - Chainguard: docs/coverage/os/chainguard.md + - Conda: docs/coverage/os/conda.md - Debian: docs/coverage/os/debian.md - Oracle Linux: docs/coverage/os/oracle.md - Photon OS: docs/coverage/os/photon.md diff --git a/pkg/dependency/parser/conda/environment/parse.go b/pkg/dependency/parser/conda/environment/parse.go new file mode 100644 index 000000000000..8a4418699f2f --- /dev/null +++ b/pkg/dependency/parser/conda/environment/parse.go @@ -0,0 +1,103 @@ +package environment + +import ( + "sort" + "strings" + "sync" + + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/go-version/pkg/version" + "github.com/aquasecurity/trivy/pkg/dependency/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +type environment struct { + Dependencies []Dependency `yaml:"dependencies"` +} + +type Dependency struct { + Value string + Line int +} + +type Parser struct { + logger *log.Logger + once sync.Once +} + +func NewParser() types.Parser { + return &Parser{ + logger: log.WithPrefix("conda"), + once: sync.Once{}, + } +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var env environment + if err := yaml.NewDecoder(r).Decode(&env); err != nil { + return nil, nil, xerrors.Errorf("unable to decode conda environment.yml file: %w", err) + } + + var libs []types.Library + for _, dep := range env.Dependencies { + lib := p.toLibrary(dep) + // Skip empty libs + if lib.Name == "" { + continue + } + libs = append(libs, lib) + } + + sort.Sort(types.Libraries(libs)) + return libs, nil, nil +} + +func (p *Parser) toLibrary(dep Dependency) types.Library { + name, ver := p.parseDependency(dep.Value) + if ver == "" { + p.once.Do(func() { + p.logger.Warn("Unable to detect the dependency versions from `environment.yml` as those versions are not pinned. Use `conda env export` to pin versions.") + }) + } + return types.Library{ + Name: name, + Version: ver, + Locations: types.Locations{ + { + StartLine: dep.Line, + EndLine: dep.Line, + }, + }, + } +} + +// parseDependency parses the dependency line and returns the name and the pinned version. +// The version range is not supported. It parses only the pinned version. +// e.g. +// - numpy 1.8.1 +// - numpy ==1.8.1 +// - numpy 1.8.1 py27_0 +// - numpy=1.8.1=py27_0 +// +// cf. https://docs.conda.io/projects/conda-build/en/latest/resources/package-spec.html#examples-of-package-specs +func (*Parser) parseDependency(line string) (string, string) { + line = strings.NewReplacer(">", " >", "<", " <", "=", " ").Replace(line) + parts := strings.Fields(line) + name := parts[0] + if len(parts) == 1 { + return name, "" + } + if _, err := version.Parse(parts[1]); err != nil { + return name, "" + } + return name, parts[1] +} + +func (d *Dependency) UnmarshalYAML(node *yaml.Node) error { + d.Value = node.Value + d.Line = node.Line + return nil +} diff --git a/pkg/dependency/parser/conda/environment/parse_test.go b/pkg/dependency/parser/conda/environment/parse_test.go new file mode 100644 index 000000000000..f68736947119 --- /dev/null +++ b/pkg/dependency/parser/conda/environment/parse_test.go @@ -0,0 +1,192 @@ +package environment_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/conda/environment" + "github.com/aquasecurity/trivy/pkg/dependency/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + input string + want []types.Library + wantErr string + }{ + { + name: "happy path", + input: "testdata/happy.yaml", + want: []types.Library{ + { + Name: "_openmp_mutex", + Locations: types.Locations{ + { + StartLine: 6, + EndLine: 6, + }, + }, + }, + { + Name: "blas", + Version: "1.0", + Locations: types.Locations{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + { + Name: "bzip2", + Version: "1.0.8", + Locations: types.Locations{ + { + StartLine: 19, + EndLine: 19, + }, + }, + }, + { + Name: "ca-certificates", + Version: "2024.2", + Locations: types.Locations{ + { + StartLine: 7, + EndLine: 7, + }, + }, + }, + { + Name: "ld_impl_linux-aarch64", + Locations: types.Locations{ + { + StartLine: 8, + EndLine: 8, + }, + }, + }, + { + Name: "libblas", + Locations: types.Locations{ + { + StartLine: 9, + EndLine: 9, + }, + }, + }, + { + Name: "libcblas", + Locations: types.Locations{ + { + StartLine: 10, + EndLine: 10, + }, + }, + }, + { + Name: "libexpat", + Version: "2.6.2", + Locations: types.Locations{ + { + StartLine: 11, + EndLine: 11, + }, + }, + }, + { + Name: "libffi", + Version: "3.4.2", + Locations: types.Locations{ + { + StartLine: 12, + EndLine: 12, + }, + }, + }, + { + Name: "libgcc-ng", + Locations: types.Locations{ + { + StartLine: 13, + EndLine: 13, + }, + }, + }, + { + Name: "libgfortran-ng", + Locations: types.Locations{ + { + StartLine: 14, + EndLine: 14, + }, + }, + }, + { + Name: "libgfortran5", + Locations: types.Locations{ + { + StartLine: 15, + EndLine: 15, + }, + }, + }, + { + Name: "libgomp", + Version: "13.2.0", + Locations: types.Locations{ + { + StartLine: 16, + EndLine: 16, + }, + }, + }, + { + Name: "liblapack", + Locations: types.Locations{ + { + StartLine: 17, + EndLine: 17, + }, + }, + }, + { + Name: "libnsl", + Version: "2.0.1", + Locations: types.Locations{ + { + StartLine: 18, + EndLine: 18, + }, + }, + }, + }, + }, + { + name: "invalid_json", + input: "testdata/invalid.yaml", + wantErr: "unable to decode conda environment.yml file", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.input) + require.NoError(t, err) + defer f.Close() + + got, _, err := environment.NewParser().Parse(f) + + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/dependency/parser/conda/environment/testdata/happy.yaml b/pkg/dependency/parser/conda/environment/testdata/happy.yaml new file mode 100644 index 000000000000..f36e8bf990eb --- /dev/null +++ b/pkg/dependency/parser/conda/environment/testdata/happy.yaml @@ -0,0 +1,21 @@ +name: test-env +channels: + - defaults +dependencies: + - blas=1.0=openblas + - _openmp_mutex + - ca-certificates=2024.2 + - ld_impl_linux-aarch64=2.40.* + - libblas>=3.9 + - libcblas<=3.9.0=22_linuxaarch64_openblas + - libexpat==2.6.2 + - libffi==3.4.2=h3557bc0_5 + - libgcc-ng 13.2|13.3 + - libgfortran-ng >13.2.0,<=13.3 + - libgfortran5 =>13.2.0,<13.3|13.4 + - libgomp 13.2.0 hf8544c7_5 + - liblapack=3.9.*=22_linuxaarch64_openblas + - libnsl=2.0.1=h31becfc_0 + - bzip2=1.0.8=h998d150_5 + +prefix: /opt/conda/envs/test-env diff --git a/pkg/dependency/parser/conda/environment/testdata/invalid.yaml b/pkg/dependency/parser/conda/environment/testdata/invalid.yaml new file mode 100644 index 000000000000..e466dcbd8e8f --- /dev/null +++ b/pkg/dependency/parser/conda/environment/testdata/invalid.yaml @@ -0,0 +1 @@ +invalid \ No newline at end of file diff --git a/pkg/detector/library/driver.go b/pkg/detector/library/driver.go index e94f2b4db89f..64bd140ed57e 100644 --- a/pkg/detector/library/driver.go +++ b/pkg/detector/library/driver.go @@ -72,7 +72,7 @@ func NewDriver(libType ftypes.LangType) (Driver, bool) { // https://guides.cocoapods.org/making/making-a-cocoapod.html#cocoapods-versioning-specifics ecosystem = vulnerability.Cocoapods comparer = rubygems.Comparer{} - case ftypes.CondaPkg: + case ftypes.CondaPkg, ftypes.CondaEnv: log.Warn("Conda package is supported for SBOM, not for vulnerability scanning") return Driver{}, false case ftypes.Bitnami: diff --git a/pkg/fanal/analyzer/all/import.go b/pkg/fanal/analyzer/all/import.go index 443ebc6bdfb8..acd0b4cc70e2 100644 --- a/pkg/fanal/analyzer/all/import.go +++ b/pkg/fanal/analyzer/all/import.go @@ -8,6 +8,7 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/imgconf/dockerfile" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/imgconf/secret" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/c/conan" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/conda/environment" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/conda/meta" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dart/pub" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/deps" diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 29ed8027118f..ef20482a782c 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -69,6 +69,7 @@ const ( // Conda TypeCondaPkg Type = "conda-pkg" + TypeCondaEnv Type = "conda-environment" // Python TypePythonPkg Type = "python-pkg" @@ -177,6 +178,7 @@ var ( TypeDotNetCore, TypePackagesProps, TypeCondaPkg, + TypeCondaEnv, TypePythonPkg, TypePip, TypePipenv, @@ -208,6 +210,7 @@ var ( TypeSwift, TypePubSpecLock, TypeMixLock, + TypeCondaEnv, } // TypeIndividualPkgs has all analyzers for individual packages diff --git a/pkg/fanal/analyzer/language/conda/environment/environment.go b/pkg/fanal/analyzer/language/conda/environment/environment.go new file mode 100644 index 000000000000..ee4dfbd7de88 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/environment.go @@ -0,0 +1,41 @@ +package environment + +import ( + "context" + "os" + "path/filepath" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/conda/environment" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&environmentAnalyzer{}) +} + +const version = 1 + +type environmentAnalyzer struct{} + +func (a environmentAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + res, err := language.Analyze(types.CondaEnv, input.FilePath, input.Content, environment.NewParser()) + if err != nil { + return nil, xerrors.Errorf("unable to parse environment.yaml: %w", err) + } + return res, nil +} +func (a environmentAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.Base(filePath) == types.CondaEnvYml || filepath.Base(filePath) == types.CondaEnvYaml +} + +func (a environmentAnalyzer) Type() analyzer.Type { + return analyzer.TypeCondaEnv +} + +func (a environmentAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/conda/environment/environment_test.go b/pkg/fanal/analyzer/language/conda/environment/environment_test.go new file mode 100644 index 000000000000..d511ac3e50a1 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/environment_test.go @@ -0,0 +1,131 @@ +package environment + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "os" + "testing" +) + +func Test_environmentAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/environment.yaml", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.CondaEnv, + FilePath: "testdata/environment.yaml", + Libraries: types.Packages{ + { + Name: "_libgcc_mutex", + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + { + Name: "_openmp_mutex", + Version: "5.1", + Locations: []types.Location{ + { + StartLine: 6, + EndLine: 6, + }, + }, + }, + { + Name: "blas", + Version: "1.0", + Locations: []types.Location{ + { + StartLine: 7, + EndLine: 7, + }, + }, + }, + { + Name: "bzip2", + Version: "1.0.8", + Locations: []types.Location{ + { + StartLine: 8, + EndLine: 8, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "invalid", + inputFile: "testdata/invalid.yaml", + wantErr: "unable to parse environment.yaml", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + + a := environmentAnalyzer{} + ctx := context.Background() + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} + +func Test_environmentAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path `yaml`", + filePath: "foo/environment.yaml", + want: true, + }, + { + name: "happy path `yml`", + filePath: "bar/environment.yaml", + want: true, + }, + { + name: "sad path `json` ", + filePath: "environment.json", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := environmentAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/environment.yaml b/pkg/fanal/analyzer/language/conda/environment/testdata/environment.yaml new file mode 100644 index 000000000000..62cd7ff599bd --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/environment.yaml @@ -0,0 +1,9 @@ +name: test-env +channels: + - defaults +dependencies: + - _libgcc_mutex + - _openmp_mutex=5.1 + - blas=1.0=openblas + - bzip2=1.0.8=h998d150_5 +prefix: /opt/conda/envs/test-env diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/invalid.yaml b/pkg/fanal/analyzer/language/conda/environment/testdata/invalid.yaml new file mode 100644 index 000000000000..9977a2836c1a --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/invalid.yaml @@ -0,0 +1 @@ +invalid diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index b46b36a8d425..56f56036a590 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -55,6 +55,7 @@ const ( Pipenv LangType = "pipenv" Poetry LangType = "poetry" CondaPkg LangType = "conda-pkg" + CondaEnv LangType = "conda-environment" PythonPkg LangType = "python-pkg" NodePkg LangType = "node-pkg" Yarn LangType = "yarn" @@ -139,4 +140,7 @@ const ( PubSpecLock = "pubspec.lock" MixLock = "mix.lock" + + CondaEnvYaml = "environment.yaml" + CondaEnvYml = "environment.yml" ) diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index 59a4b99df30c..608c7b0b4029 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -432,7 +432,7 @@ func purlType(t ftypes.TargetType) string { return packageurl.TypeGem case ftypes.NuGet, ftypes.DotNetCore, ftypes.PackagesProps: return packageurl.TypeNuget - case ftypes.CondaPkg: + case ftypes.CondaPkg, ftypes.CondaEnv: return packageurl.TypeConda case ftypes.PythonPkg, ftypes.Pip, ftypes.Pipenv, ftypes.Poetry: return packageurl.TypePyPi diff --git a/pkg/purl/purl_test.go b/pkg/purl/purl_test.go index d22e010d8b22..646930ee5b76 100644 --- a/pkg/purl/purl_test.go +++ b/pkg/purl/purl_test.go @@ -131,6 +131,19 @@ func TestNewPackageURL(t *testing.T) { Version: "0.4.1", }, }, + { + name: "conda environment.yaml", + typ: ftypes.CondaEnv, + pkg: ftypes.Package{ + Name: "blas", + Version: "1.0", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeConda, + Name: "blas", + Version: "1.0", + }, + }, { name: "composer package", typ: ftypes.Composer, From 29b8faf5faaa02e463cbb54465563b40d5667bf4 Mon Sep 17 00:00:00 2001 From: Damian E Date: Mon, 29 Apr 2024 12:37:25 +0200 Subject: [PATCH 012/352] feat(vuln): Handle scanning conan v2.x lockfiles (#6357) Co-authored-by: Teppei Fukuda --- docs/docs/coverage/language/c.md | 16 +-- pkg/dependency/parser/c/conan/parse.go | 99 ++++++++++++++----- pkg/dependency/parser/c/conan/parse_test.go | 37 ++++++- .../testdata/{empty.lock => empty_v1.lock} | 0 .../{happy.lock => happy_v1_case1.lock} | 0 .../{happy2.lock => happy_v1_case2.lock} | 0 .../parser/c/conan/testdata/happy_v2.lock | 12 +++ .../conan/testdata/{sad.lock => sad_v1.lock} | 0 8 files changed, 132 insertions(+), 32 deletions(-) rename pkg/dependency/parser/c/conan/testdata/{empty.lock => empty_v1.lock} (100%) rename pkg/dependency/parser/c/conan/testdata/{happy.lock => happy_v1_case1.lock} (100%) rename pkg/dependency/parser/c/conan/testdata/{happy2.lock => happy_v1_case2.lock} (100%) create mode 100644 pkg/dependency/parser/c/conan/testdata/happy_v2.lock rename pkg/dependency/parser/c/conan/testdata/{sad.lock => sad_v1.lock} (100%) diff --git a/docs/docs/coverage/language/c.md b/docs/docs/coverage/language/c.md index 0ae219d8370b..276340a806bd 100644 --- a/docs/docs/coverage/language/c.md +++ b/docs/docs/coverage/language/c.md @@ -1,6 +1,6 @@ # C/C++ -Trivy supports [Conan][conan] C/C++ Package Manager. +Trivy supports Conan C/C++ Package Manager ([v1][conanV1] and [v2][conanV2] with limitations). The following scanners are supported. @@ -10,21 +10,25 @@ The following scanners are supported. The following table provides an outline of the features Trivy offers. -| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | -|-----------------|----------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| -| Conan | conan.lock[^2] | ✓ | Excluded | ✓ | ✓ | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|-----------------------|----------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| +| Conan (lockfile v1) | conan.lock[^2] | ✓ | Excluded | ✓ | ✓ | +| Conan (lockfile v2) | conan.lock[^2] | ✓ [^3] | Excluded | - | ✓ | ## Conan In order to detect dependencies, Trivy searches for `conan.lock`[^1]. +[conanV1]: https://docs.conan.io/1/index.html +[conanV2]: https://docs.conan.io/2/ + ### Licenses The Conan lock file doesn't contain any license information. To obtain licenses we parse the `conanfile.py` files from the [conan cache directory][conan-cache-dir]. To correctly detection licenses, ensure that the cache directory contains all dependencies used. -[conan]: https://docs.conan.io/1/index.html [conan-cache-dir]: https://docs.conan.io/1/mastering/custom_cache.html [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies [^1]: The local cache should contain the dependencies used. See [licenses](#licenses). -[^2]: `conan.lock` is default name. To scan a custom filename use [file-patterns](../../configuration/skipping.md#file-patterns). \ No newline at end of file +[^2]: `conan.lock` is default name. To scan a custom filename use [file-patterns](../../configuration/skipping.md#file-patterns). +[^3]: For `conan.lock` in version 2, indirect dependencies are included in analysis but not flagged explicitly in dependency tree diff --git a/pkg/dependency/parser/c/conan/parse.go b/pkg/dependency/parser/c/conan/parse.go index f5417afd007f..06c583b47282 100644 --- a/pkg/dependency/parser/c/conan/parse.go +++ b/pkg/dependency/parser/c/conan/parse.go @@ -18,6 +18,7 @@ import ( type LockFile struct { GraphLock GraphLock `json:"graph_lock"` + Requires Requires `json:"requires"` } type GraphLock struct { @@ -31,6 +32,14 @@ type Node struct { EndLine int } +type Require struct { + Dependency string + StartLine int + EndLine int +} + +type Requires []Require + type Parser struct { logger *log.Logger } @@ -41,17 +50,9 @@ func NewParser() types.Parser { } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { - var lock LockFile - input, err := io.ReadAll(r) - if err != nil { - return nil, nil, xerrors.Errorf("failed to read canon lock file: %w", err) - } - if err := jfather.Unmarshal(input, &lock); err != nil { - return nil, nil, xerrors.Errorf("failed to decode canon lock file: %w", err) - } - - // Get a list of direct dependencies +func (p *Parser) parseV1(lock LockFile) ([]types.Library, []types.Dependency, error) { + var libs []types.Library + var deps []types.Dependency var directDeps []string if root, ok := lock.GraphLock.Nodes["0"]; ok { directDeps = root.Requires @@ -63,7 +64,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, if node.Ref == "" { continue } - lib, err := parseRef(node) + lib, err := toLibrary(node.Ref, node.StartLine, node.EndLine) if err != nil { p.logger.Debug("Parse ref error", log.Err(err)) continue @@ -77,8 +78,6 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, } // Parse dependency graph - var libs []types.Library - var deps []types.Dependency for i, node := range lock.GraphLock.Nodes { lib, ok := parsed[i] if !ok { @@ -103,25 +102,70 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return libs, deps, nil } -func parseRef(node Node) (types.Library, error) { +func (p *Parser) parseV2(lock LockFile) ([]types.Library, []types.Dependency, error) { + var libs []types.Library + + for _, req := range lock.Requires { + lib, err := toLibrary(req.Dependency, req.StartLine, req.EndLine) + if err != nil { + p.logger.Debug("Creating library entry from requirement failed", err) + continue + } + + libs = append(libs, lib) + } + return libs, []types.Dependency{}, nil +} + +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var lock LockFile + + input, err := io.ReadAll(r) + if err != nil { + return nil, nil, xerrors.Errorf("failed to read conan lock file: %w", err) + } + if err := jfather.Unmarshal(input, &lock); err != nil { + return nil, nil, xerrors.Errorf("failed to decode conan lock file: %w", err) + } + + // try to parse requirements as conan v1.x + if lock.GraphLock.Nodes != nil { + p.logger.Debug("Handling conan lockfile as v1.x") + return p.parseV1(lock) + } else { + // try to parse requirements as conan v2.x + p.logger.Debug("Handling conan lockfile as v2.x") + return p.parseV2(lock) + } +} + +func parsePackage(text string) (string, string, error) { // full ref format: package/version@user/channel#rrev:package_id#prev // various examples: // 'pkga/0.1@user/testing' // 'pkgb/0.1.0' // 'pkgc/system' // 'pkgd/0.1.0#7dcb50c43a5a50d984c2e8fa5898bf18' - ss := strings.Split(strings.Split(strings.Split(node.Ref, "@")[0], "#")[0], "/") + ss := strings.Split(strings.Split(strings.Split(text, "@")[0], "#")[0], "/") if len(ss) != 2 { - return types.Library{}, xerrors.Errorf("Unable to determine conan dependency: %q", node.Ref) + return "", "", xerrors.Errorf("Unable to determine conan dependency: %q", text) + } + return ss[0], ss[1], nil +} + +func toLibrary(pkg string, startLine, endLine int) (types.Library, error) { + name, version, err := parsePackage(pkg) + if err != nil { + return types.Library{}, err } return types.Library{ - ID: dependency.ID(ftypes.Conan, ss[0], ss[1]), - Name: ss[0], - Version: ss[1], + ID: dependency.ID(ftypes.Conan, name, version), + Name: name, + Version: version, Locations: []types.Location{ { - StartLine: node.StartLine, - EndLine: node.EndLine, + StartLine: startLine, + EndLine: endLine, }, }, }, nil @@ -137,3 +181,14 @@ func (n *Node) UnmarshalJSONWithMetadata(node jfather.Node) error { n.EndLine = node.Range().End.Line return nil } + +func (r *Require) UnmarshalJSONWithMetadata(node jfather.Node) error { + var dep string + if err := node.Decode(&dep); err != nil { + return err + } + r.Dependency = dep + r.StartLine = node.Range().Start.Line + r.EndLine = node.Range().End.Line + return nil +} diff --git a/pkg/dependency/parser/c/conan/parse_test.go b/pkg/dependency/parser/c/conan/parse_test.go index 7502fde76271..abbf9f921a18 100644 --- a/pkg/dependency/parser/c/conan/parse_test.go +++ b/pkg/dependency/parser/c/conan/parse_test.go @@ -22,7 +22,7 @@ func TestParse(t *testing.T) { }{ { name: "happy path", - inputFile: "testdata/happy.lock", + inputFile: "testdata/happy_v1_case1.lock", wantLibs: []types.Library{ { ID: "pkga/0.0.1", @@ -72,7 +72,7 @@ func TestParse(t *testing.T) { }, { name: "happy path. lock file with revisions support", - inputFile: "testdata/happy2.lock", + inputFile: "testdata/happy_v1_case2.lock", wantLibs: []types.Library{ { ID: "openssl/3.0.3", @@ -108,13 +108,42 @@ func TestParse(t *testing.T) { }, }, }, + { + name: "happy path conan v2", + inputFile: "testdata/happy_v2.lock", + wantLibs: []types.Library{ + { + ID: "matrix/1.3", + Name: "matrix", + Version: "1.3", + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + { + ID: "sound32/1.0", + Name: "sound32", + Version: "1.0", + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, + }, + wantDeps: []types.Dependency{}, + }, { name: "happy path. lock file without dependencies", - inputFile: "testdata/empty.lock", + inputFile: "testdata/empty_v1.lock", }, { name: "sad path. wrong ref format", - inputFile: "testdata/sad.lock", + inputFile: "testdata/sad_v1.lock", }, } diff --git a/pkg/dependency/parser/c/conan/testdata/empty.lock b/pkg/dependency/parser/c/conan/testdata/empty_v1.lock similarity index 100% rename from pkg/dependency/parser/c/conan/testdata/empty.lock rename to pkg/dependency/parser/c/conan/testdata/empty_v1.lock diff --git a/pkg/dependency/parser/c/conan/testdata/happy.lock b/pkg/dependency/parser/c/conan/testdata/happy_v1_case1.lock similarity index 100% rename from pkg/dependency/parser/c/conan/testdata/happy.lock rename to pkg/dependency/parser/c/conan/testdata/happy_v1_case1.lock diff --git a/pkg/dependency/parser/c/conan/testdata/happy2.lock b/pkg/dependency/parser/c/conan/testdata/happy_v1_case2.lock similarity index 100% rename from pkg/dependency/parser/c/conan/testdata/happy2.lock rename to pkg/dependency/parser/c/conan/testdata/happy_v1_case2.lock diff --git a/pkg/dependency/parser/c/conan/testdata/happy_v2.lock b/pkg/dependency/parser/c/conan/testdata/happy_v2.lock new file mode 100644 index 000000000000..f4103baae1d0 --- /dev/null +++ b/pkg/dependency/parser/c/conan/testdata/happy_v2.lock @@ -0,0 +1,12 @@ +{ + "version": "0.5", + "requires": [ + "sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675278904.0791488", + "matrix/1.3#905c3f0babc520684c84127378fefdd0%1675278900.0103245" + ], + "build_requires": [ + "automake/1.16.5#058bda3e21c36c9aa8425daf3c1faf50%1701120593.68", + "autoconf/2.71#00a1e46d8ba5baaf7f10d64c1a6a0342%1709043523.063" + ], + "python_requires": [] +} \ No newline at end of file diff --git a/pkg/dependency/parser/c/conan/testdata/sad.lock b/pkg/dependency/parser/c/conan/testdata/sad_v1.lock similarity index 100% rename from pkg/dependency/parser/c/conan/testdata/sad.lock rename to pkg/dependency/parser/c/conan/testdata/sad_v1.lock From a5d485cf8a36f3f50b9fe4c3b58641eb364b8ed8 Mon Sep 17 00:00:00 2001 From: Jean-Yves LENHOF <36410287+jylenhof@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:32:14 +0200 Subject: [PATCH 013/352] docs: add asdf and mise installation method (#6063) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- docs/getting-started/installation.md | 78 +++++++++++++++++++++------- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 47aa0f21b1ff..92daf75e36c4 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -85,31 +85,29 @@ References: Nix package manager for Linux and MacOS. === "Command line" - -`nix-env --install -A nixpkgs.trivy` + `nix-env --install -A nixpkgs.trivy` === "Configuration" - -```nix - # your other config ... - environment.systemPackages = with pkgs; [ - # your other packages ... - trivy - ]; -``` + ```nix + # your other config ... + environment.systemPackages = with pkgs; [ + # your other packages ... + trivy + ]; + ``` === "Home Manager" - -```nix - # your other config ... - home.packages = with pkgs; [ - # your other packages ... - trivy - ]; -``` + ```nix + # your other config ... + home.packages = with pkgs; [ + # your other packages ... + trivy + ]; + ``` References: -- + +- https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/admin/trivy/default.nix ### FreeBSD (Official) @@ -119,6 +117,48 @@ References: pkg install trivy ``` +### asdf/mise (Community) + +[asdf](https://github.com/asdf-vm/asdf) and [mise](https://github.com/jdx/mise) are quite similar tools you can use to install trivy. +See their respective documentation for more information of how to install them and use them: + +- [asdf](https://asdf-vm.com/guide/getting-started.html) +- [mise](https://mise.jdx.dev/getting-started.html) + +The plugin used by both tools is developped [here](https://github.com/zufardhiyaulhaq/asdf-trivy) + + +=== "asdf" + A basic global installation is shown below, for specific version or/and local version to a directory see "asdf" documentation. + + ```shell + # Install plugin + asdf plugin add trivy https://github.com/zufardhiyaulhaq/asdf-trivy.git + + # Install latest version + asdf install trivy latest + + # Set a version globally (on your ~/.tool-versions file) + asdf global trivy latest + + # Now trivy commands are available + trivy --version + ``` + +=== "mise" + A basic global installation is shown below, for specific version or/and local version to a directory see "mise" documentation. + + ``` shell + # Install plugin and install latest version + mise install trivy@latest + + # Set a version globally (on your ~/.tool-versions file) + mise use -g trivy@latest + + # Now trivy commands are available + trivy --version + ``` + ## Install from GitHub Release (Official) ### Download Binary From f0961d54f6d68324003419f65042d15d5435d28b Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 2 May 2024 09:07:49 +0400 Subject: [PATCH 014/352] feat: respect custom exit code from plugin (#6584) Signed-off-by: knqyf263 --- cmd/trivy/main.go | 6 ++++++ pkg/cloud/aws/commands/run.go | 4 ++-- pkg/commands/app.go | 2 ++ pkg/commands/artifact/run.go | 5 +---- pkg/commands/convert/run.go | 5 +---- pkg/commands/operation/operation.go | 15 +++++++-------- pkg/k8s/commands/run.go | 4 +--- pkg/plugin/plugin.go | 9 +++++++-- pkg/plugin/plugin_test.go | 2 +- pkg/types/error.go | 13 +++++++++++++ 10 files changed, 41 insertions(+), 24 deletions(-) create mode 100644 pkg/types/error.go diff --git a/cmd/trivy/main.go b/cmd/trivy/main.go index e3118ae8e97f..dbff5fa54ab1 100644 --- a/cmd/trivy/main.go +++ b/cmd/trivy/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "os" "golang.org/x/xerrors" @@ -9,12 +10,17 @@ import ( "github.com/aquasecurity/trivy/pkg/commands" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/plugin" + "github.com/aquasecurity/trivy/pkg/types" _ "modernc.org/sqlite" // sqlite driver for RPM DB and Java DB ) func main() { if err := run(); err != nil { + var exitError *types.ExitError + if errors.As(err, &exitError) { + os.Exit(exitError.Code) + } log.Fatal("Fatal error", log.Err(err)) } } diff --git a/pkg/cloud/aws/commands/run.go b/pkg/cloud/aws/commands/run.go index 58744e752c79..374abbd91289 100644 --- a/pkg/cloud/aws/commands/run.go +++ b/pkg/cloud/aws/commands/run.go @@ -18,6 +18,7 @@ import ( "github.com/aquasecurity/trivy/pkg/commands/operation" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" ) var allSupportedServicesFunc = awsScanner.AllSupportedServices @@ -170,6 +171,5 @@ func Run(ctx context.Context, opt flag.Options) error { return xerrors.Errorf("unable to write results: %w", err) } - operation.Exit(opt, r.Failed()) - return nil + return operation.Exit(opt, r.Failed(), types.Metadata{}) } diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 05e14ea87f82..af2902a14e0d 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -130,6 +130,8 @@ func loadPluginCommands() []*cobra.Command { return nil }, DisableFlagParsing: true, + SilenceUsage: true, + SilenceErrors: true, } commands = append(commands, cmd) } diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index f9e06a30f0a1..fbfe257312ba 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -452,10 +452,7 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err return xerrors.Errorf("report error: %w", err) } - operation.ExitOnEOL(opts, report.Metadata) - operation.Exit(opts, report.Results.Failed()) - - return nil + return operation.Exit(opts, report.Results.Failed(), report.Metadata) } func disabledAnalyzers(opts flag.Options) []analyzer.Type { diff --git a/pkg/commands/convert/run.go b/pkg/commands/convert/run.go index 34e799f7a061..428d6b5b0b4b 100644 --- a/pkg/commands/convert/run.go +++ b/pkg/commands/convert/run.go @@ -44,8 +44,5 @@ func Run(ctx context.Context, opts flag.Options) (err error) { return xerrors.Errorf("unable to write results: %w", err) } - operation.ExitOnEOL(opts, r.Metadata) - operation.Exit(opts, r.Results.Failed()) - - return nil + return operation.Exit(opts, r.Results.Failed(), r.Metadata) } diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index 2b4e2a7f5ffa..e97a9bd8f1ee 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -204,16 +204,15 @@ func GetTLSConfig(caCertPath, certPath, keyPath string) (*x509.CertPool, tls.Cer return caCertPool, cert, nil } -func Exit(opts flag.Options, failedResults bool) { - if opts.ExitCode != 0 && failedResults { - os.Exit(opts.ExitCode) - } -} - -func ExitOnEOL(opts flag.Options, m types.Metadata) { +func Exit(opts flag.Options, failedResults bool, m types.Metadata) error { if opts.ExitOnEOL != 0 && m.OS != nil && m.OS.Eosl { log.Error("Detected EOL OS", log.String("family", string(m.OS.Family)), log.String("version", m.OS.Name)) - os.Exit(opts.ExitOnEOL) + return &types.ExitError{Code: opts.ExitOnEOL} } + + if opts.ExitCode != 0 && failedResults { + return &types.ExitError{Code: opts.ExitCode} + } + return nil } diff --git a/pkg/k8s/commands/run.go b/pkg/k8s/commands/run.go index 179853f99121..01ebf1db645f 100644 --- a/pkg/k8s/commands/run.go +++ b/pkg/k8s/commands/run.go @@ -122,9 +122,7 @@ func (r *runner) run(ctx context.Context, artifacts []*k8sArtifacts.Artifact) er return xerrors.Errorf("unable to write results: %w", err) } - operation.Exit(r.flagOpts, rpt.Failed()) - - return nil + return operation.Exit(r.flagOpts, rpt.Failed(), types.Metadata{}) } // Full-cluster scanning with '--format table' without explicit '--report all' is not allowed so that it won't mess up user's terminal. diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index a72419aceb5a..11e46a4488a0 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -2,6 +2,7 @@ package plugin import ( "context" + "errors" "fmt" "io" "os" @@ -15,6 +16,7 @@ import ( "github.com/aquasecurity/trivy/pkg/downloader" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) @@ -111,8 +113,11 @@ func (p Plugin) Run(ctx context.Context, opts RunOptions) error { // out if the error was from not being able to execute the plugin or // an error set by the plugin itself. if err = cmd.Run(); err != nil { - if _, ok := err.(*exec.ExitError); !ok { - return xerrors.Errorf("exit: %w", err) + var execError *exec.ExitError + if errors.As(err, &execError) { + return &types.ExitError{ + Code: execError.ExitCode(), + } } return xerrors.Errorf("plugin exec: %w", err) } diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go index f9ee7aac2b89..d3f5aa1a0fec 100644 --- a/pkg/plugin/plugin_test.go +++ b/pkg/plugin/plugin_test.go @@ -140,7 +140,7 @@ func TestPlugin_Run(t *testing.T) { GOOS: "linux", GOARCH: "amd64", }, - wantErr: "plugin exec: exit status 1", + wantErr: "exit status 1", }, } for _, tt := range tests { diff --git a/pkg/types/error.go b/pkg/types/error.go new file mode 100644 index 000000000000..5a1614e07dd0 --- /dev/null +++ b/pkg/types/error.go @@ -0,0 +1,13 @@ +package types + +import ( + "fmt" +) + +type ExitError struct { + Code int +} + +func (e *ExitError) Error() string { + return fmt.Sprintf("exit status %d", e.Code) +} From 419e3d2023aa190ff62c3952219053a9bca066bb Mon Sep 17 00:00:00 2001 From: Oscar Alberto Tovar Date: Thu, 2 May 2024 01:33:13 -0400 Subject: [PATCH 015/352] feat(go): parse main mod version from build info settings (#6564) Co-authored-by: Teppei Fukuda --- docs/docs/coverage/language/golang.md | 8 +- pkg/dependency/parser/golang/binary/parse.go | 76 ++++++++++- .../parser/golang/binary/parse_test.go | 121 ++++++++++++++++++ .../testdata/main-version-via-ldflags.elf | Bin 0 -> 1347317 bytes 4 files changed, 199 insertions(+), 6 deletions(-) create mode 100755 pkg/dependency/parser/golang/binary/testdata/main-version-via-ldflags.elf diff --git a/docs/docs/coverage/language/golang.md b/docs/docs/coverage/language/golang.md index 4127d288a8b8..892746ecef54 100644 --- a/docs/docs/coverage/language/golang.md +++ b/docs/docs/coverage/language/golang.md @@ -75,16 +75,18 @@ $ trivy rootfs ./your_binary It doesn't work with UPX-compressed binaries. #### Empty versions -There are times when Go uses the `(devel)` version for modules/dependencies and Trivy can't resolve them: +There are times when Go uses the `(devel)` version for modules/dependencies. - Only Go binaries installed using the `go install` command contain correct (semver) version for the main module. In other cases, Go uses the `(devel)` version[^3]. - Dependencies replaced with local ones use the `(devel)` versions. -In these cases, the version of such packages is empty. +In the first case, Trivy will attempt to parse any `-ldflags` as a secondary source, and will leave the version +empty if it cannot do so[^4]. For the second case, the version of such packages is empty. [^1]: It doesn't require the Internet access. [^2]: Need to download modules to local cache beforehand [^3]: See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477 +[^4]: See https://github.com/golang/go/issues/63432#issuecomment-1751610604 -[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies \ No newline at end of file +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies diff --git a/pkg/dependency/parser/golang/binary/parse.go b/pkg/dependency/parser/golang/binary/parse.go index 94fe3900b006..ae7d4d81adae 100644 --- a/pkg/dependency/parser/golang/binary/parse.go +++ b/pkg/dependency/parser/golang/binary/parse.go @@ -1,10 +1,14 @@ package binary import ( + "cmp" "debug/buildinfo" + "runtime/debug" "sort" "strings" + "github.com/spf13/pflag" + "golang.org/x/mod/semver" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/types" @@ -48,15 +52,18 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return nil, nil, convertError(err) } + ldflags := p.ldFlags(info.Settings) libs := make([]types.Library, 0, len(info.Deps)+2) libs = append(libs, []types.Library{ { // Add main module Name: info.Main.Path, // Only binaries installed with `go install` contain semver version of the main module. - // Other binaries use the `(devel)` version. + // Other binaries use the `(devel)` version, but still may contain a stamped version + // set via `go build -ldflags='-X main.version='`, so we fallback to this as. + // as a secondary source. // See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477. - Version: p.checkVersion(info.Main.Path, info.Main.Version), + Version: cmp.Or(p.checkVersion(info.Main.Path, info.Main.Version), p.ParseLDFlags(info.Main.Path, ldflags)), Relationship: types.RelationshipRoot, }, { @@ -93,8 +100,71 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // checkVersion detects `(devel)` versions, removes them and adds a debug message about it. func (p *Parser) checkVersion(name, version string) string { if version == "(devel)" { - p.logger.Debug("Unable to detect dependency version (`(devel)` is used). Version will be empty.", log.String("dependency", name)) + p.logger.Debug("Unable to detect main module's dependency version - `(devel)` is used", log.String("dependency", name)) return "" } return version } + +func (p *Parser) ldFlags(settings []debug.BuildSetting) []string { + for _, setting := range settings { + if setting.Key != "-ldflags" { + continue + } + + return strings.Fields(setting.Value) + } + return nil +} + +// ParseLDFlags attempts to parse the binary's version from any `-ldflags` passed to `go build` at build time. +func (p *Parser) ParseLDFlags(name string, flags []string) string { + p.logger.Debug("Parsing dependency's build info settings", "dependency", name, "-ldflags", flags) + fset := pflag.NewFlagSet("ldflags", pflag.ContinueOnError) + // This prevents the flag set from erroring out if other flags were provided. + // This helps keep the implementation small, so that only the -X flag is needed. + fset.ParseErrorsWhitelist.UnknownFlags = true + // The shorthand name is needed here because setting the full name + // to `X` will cause the flag set to look for `--X` instead of `-X`. + // The flag can also be set multiple times, so a string slice is needed + // to handle that edge case. + var x map[string]string + fset.StringToStringVarP(&x, "", "X", nil, "") + if err := fset.Parse(flags); err != nil { + p.logger.Error("Could not parse -ldflags found in build info", log.Err(err)) + return "" + } + + for key, val := range x { + // It's valid to set the -X flags with quotes so we trim any that might + // have been provided: Ex: + // + // -X main.version=1.0.0 + // -X=main.version=1.0.0 + // -X 'main.version=1.0.0' + // -X='main.version=1.0.0' + // -X="main.version=1.0.0" + // -X "main.version=1.0.0" + key = strings.TrimLeft(key, `'`) + val = strings.TrimRight(val, `'`) + if isValidXKey(key) && isValidSemVer(val) { + return val + } + } + + p.logger.Debug("Unable to detect dependency version used in `-ldflags` build info settings. Empty version used.", log.String("dependency", name)) + return "" +} + +func isValidXKey(key string) bool { + key = strings.ToLower(key) + // The check for a 'ver' prefix enables the parser to pick up Trivy's own version value that's set. + return strings.HasSuffix(key, "version") || strings.HasSuffix(key, "ver") +} + +func isValidSemVer(ver string) bool { + // semver.IsValid strictly checks for the v prefix so prepending 'v' + // here and checking validity again increases the chances that we + // parse a valid semver version. + return semver.IsValid(ver) || semver.IsValid("v"+ver) +} diff --git a/pkg/dependency/parser/golang/binary/parse_test.go b/pkg/dependency/parser/golang/binary/parse_test.go index 14038194b2c4..96dc7213311c 100644 --- a/pkg/dependency/parser/golang/binary/parse_test.go +++ b/pkg/dependency/parser/golang/binary/parse_test.go @@ -98,6 +98,22 @@ func TestParse(t *testing.T) { }, }, }, + { + name: "with -ldflags=\"-X main.version=v1.0.0\"", + inputFile: "testdata/main-version-via-ldflags.elf", + want: []types.Library{ + { + Name: "github.com/aquasecurity/test", + Version: "v1.0.0", + Relationship: types.RelationshipRoot, + }, + { + Name: "stdlib", + Version: "1.22.1", + Relationship: types.RelationshipDirect, + }, + }, + }, { name: "sad path", inputFile: "testdata/dummy", @@ -122,3 +138,108 @@ func TestParse(t *testing.T) { }) } } + +func TestParser_ParseLDFlags(t *testing.T) { + type args struct { + name string + flags []string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "with version suffix", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-s", + "-w", + "-X=foo=bar", + "-X='github.com/aquasecurity/trivy/pkg/version.version=v0.50.1'", + }, + }, + want: "v0.50.1", + }, + { + name: "with version suffix titlecased", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-s", + "-w", + "-X=foo=bar", + "-X='github.com/aquasecurity/trivy/pkg/version.Version=v0.50.1'", + }, + }, + want: "v0.50.1", + }, + { + name: "with ver suffix", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-s", + "-w", + "-X=foo=bar", + "-X='github.com/aquasecurity/trivy/pkg/version.ver=v0.50.1'", + }, + }, + want: "v0.50.1", + }, + { + name: "with ver suffix titlecased", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-s", + "-w", + "-X=foo=bar", + "-X='github.com/aquasecurity/trivy/pkg/version.Ver=v0.50.1'", + }, + }, + want: "v0.50.1", + }, + { + name: "with double quoted flag", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-s", + "-w", + "-X=foo=bar", + "-X=\"github.com/aquasecurity/trivy/pkg/version.Ver=0.50.1\"", + }, + }, + want: "0.50.1", + }, + { + name: "with semver version without v prefix", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-s", + "-w", + "-X=foo=bar", + "-X='github.com/aquasecurity/trivy/pkg/version.Ver=0.50.1'", + }, + }, + want: "0.50.1", + }, + { + name: "with no flags", + args: args{ + name: "github.com/aquasecurity/test", + flags: []string{}, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := binary.NewParser().(*binary.Parser) + assert.Equal(t, tt.want, p.ParseLDFlags(tt.args.name, tt.args.flags)) + }) + } +} diff --git a/pkg/dependency/parser/golang/binary/testdata/main-version-via-ldflags.elf b/pkg/dependency/parser/golang/binary/testdata/main-version-via-ldflags.elf new file mode 100755 index 0000000000000000000000000000000000000000..8271b6872e128160ccb717cc3de4755ac876ef38 GIT binary patch literal 1347317 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

{3PBnX=QbFCh3|_;!uKuYV&Ki*Q(x3! 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

eY_?cV`Jq__ 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

YC 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 Date: Thu, 2 May 2024 10:00:50 +0400 Subject: [PATCH 016/352] chore(deps): bump github.com/go-openapi/runtime from 0.27.1 to 0.28.0 (#6600) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 24 ++++++++++++------------ go.sum | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/go.mod b/go.mod index 786c80116062..5d5379e07beb 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.16.0 github.com/go-git/go-git/v5 v5.11.0 - github.com/go-openapi/runtime v0.27.1 + github.com/go-openapi/runtime v0.28.0 github.com/go-openapi/strfmt v0.23.0 github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -266,14 +266,14 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-openapi/analysis v0.21.5 // indirect + github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect - github.com/go-openapi/jsonpointer v0.20.1 // indirect - github.com/go-openapi/jsonreference v0.20.3 // indirect - github.com/go-openapi/loads v0.21.3 // indirect - github.com/go-openapi/spec v0.20.12 // indirect - github.com/go-openapi/swag v0.22.5 // indirect - github.com/go-openapi/validate v0.22.4 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/loads v0.22.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/validate v0.24.0 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/go-test/deep v1.1.0 // indirect github.com/gobwas/glob v0.2.3 // indirect @@ -388,10 +388,10 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect - go.opentelemetry.io/otel v1.23.1 // indirect - go.opentelemetry.io/otel/metric v1.23.1 // indirect - go.opentelemetry.io/otel/sdk v1.23.1 // indirect - go.opentelemetry.io/otel/trace v1.23.1 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/sdk v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/go.sum b/go.sum index 664801e3fc7f..7be061c58dbd 100644 --- a/go.sum +++ b/go.sum @@ -829,33 +829,33 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-openapi/analysis v0.21.5 h1:3tHfEBh6Ia8eKc4M7khOGjPOAlWKJ10d877Cr9teujI= -github.com/go-openapi/analysis v0.21.5/go.mod h1:25YcZosX9Lwz2wBsrFrrsL8bmjjXdlyP6zsr2AMy29M= +github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= +github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.20.1 h1:MkK4VEIEZMj4wT9PmjaUmGflVBr9nvud4Q4UVFbDoBE= -github.com/go-openapi/jsonpointer v0.20.1/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.20.3 h1:EjGcjTW8pD1mRis6+w/gmoBdqv5+RbE9B85D1NgDOVQ= -github.com/go-openapi/jsonreference v0.20.3/go.mod h1:FviDZ46i9ivh810gqzFLl5NttD5q3tSlMLqLr6okedM= -github.com/go-openapi/loads v0.21.3 h1:8sSH2FIm/SnbDUGv572md4YqVMFne/a9Eubvcd3anew= -github.com/go-openapi/loads v0.21.3/go.mod h1:Y3aMR24iHbKHppOj91nQ/SHc0cuPbAr4ndY4a02xydc= -github.com/go-openapi/runtime v0.27.1 h1:ae53yaOoh+fx/X5Eaq8cRmavHgDma65XPZuvBqvJYto= -github.com/go-openapi/runtime v0.27.1/go.mod h1:fijeJEiEclyS8BRurYE1DE5TLb9/KZl6eAdbzjsrlLU= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= +github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= +github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= +github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.20.12 h1:cgSLbrsmziAP2iais+Vz7kSazwZ8rsUZd6TUzdDgkVI= -github.com/go-openapi/spec v0.20.12/go.mod h1:iSCgnBcwbMW9SfzJb8iYynXvcY6C/QFrI7otzF7xGM4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.22.5 h1:fVS63IE3M0lsuWRzuom3RLwUMVI2peDH01s6M70ugys= -github.com/go-openapi/swag v0.22.5/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= -github.com/go-openapi/validate v0.22.4 h1:5v3jmMyIPKTR8Lv9syBAIRxG6lY0RqeBPB1LKEijzk8= -github.com/go-openapi/validate v0.22.4/go.mod h1:qm6O8ZIcPVdSY5219468Jv7kBdGvkiZLPOmqnqTUZ2A= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= +github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -1729,20 +1729,20 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= -go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= -go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 h1:o8iWeVFa1BcLtVEV0LzrCxV2/55tB3xLxADr6Kyoey4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1/go.mod h1:SEVfdK4IoBnbT2FXNM/k8yC08MrfbhWk3U4ljM8B3HE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.23.1 h1:cfuy3bXmLJS7M1RZmAL6SuhGtKUp2KEsrm00OlAXkq4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.23.1/go.mod h1:22jr92C6KwlwItJmQzfixzQM3oyyuYLCfHiMY+rpsPU= -go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= -go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= -go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E= -go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk= -go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= -go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= From 261649b11511cf2884f305d05b8e43be3ff55ec7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 10:01:21 +0400 Subject: [PATCH 017/352] chore(deps): bump github.com/containerd/containerd from 1.7.13 to 1.7.16 (#6592) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5d5379e07beb..2ae035845aad 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cenkalti/backoff v2.2.1+incompatible github.com/cheggaaa/pb/v3 v3.1.4 - github.com/containerd/containerd v1.7.13 + github.com/containerd/containerd v1.7.16 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 github.com/docker/docker v25.0.5+incompatible github.com/docker/go-connections v0.5.0 @@ -235,7 +235,7 @@ require ( github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect - github.com/containerd/ttrpc v1.2.2 // indirect + github.com/containerd/ttrpc v1.2.3 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect diff --git a/go.sum b/go.sum index 7be061c58dbd..b0a6660357fe 100644 --- a/go.sum +++ b/go.sum @@ -583,8 +583,8 @@ github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is= -github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4= +github.com/containerd/containerd v1.7.16 h1:7Zsfe8Fkj4Wi2My6DXGQ87hiqIrmOXolm72ZEkFU5Mg= +github.com/containerd/containerd v1.7.16/go.mod h1:NL49g7A/Fui7ccmxV6zkBWwqMgmMxFWzujYCc+JLt7k= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -626,8 +626,8 @@ github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDG github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.2.2 h1:9vqZr0pxwOF5koz6N0N3kJ0zDHokrcPxIR/ZR2YFtOs= -github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak= +github.com/containerd/ttrpc v1.2.3 h1:4jlhbXIGvijRtNC8F/5CpuJZ7yKOBFGFOOXg1bkISz0= +github.com/containerd/ttrpc v1.2.3/go.mod h1:ieWsXucbb8Mj9PH0rXCw1i8IunRbbAiDkpXkbfflWBM= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= From 551a46efccf1e6f80539933cd9118c653adcccd6 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 2 May 2024 10:24:30 +0400 Subject: [PATCH 018/352] docs(go): add stdlib (#6580) Signed-off-by: knqyf263 --- docs/docs/coverage/language/golang.md | 17 +++++++++++------ docs/docs/scanner/vulnerability.md | 2 ++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/docs/coverage/language/golang.md b/docs/docs/coverage/language/golang.md index 892746ecef54..3d57edade7ec 100644 --- a/docs/docs/coverage/language/golang.md +++ b/docs/docs/coverage/language/golang.md @@ -1,5 +1,9 @@ # Go +## Data Sources +The data sources are listed [here](../../scanner/vulnerability.md#data-sources-1). +Trivy uses Go Vulnerability Database for standard packages, such as `net/http`, and uses GitHub Advisory Database for third-party packages. + ## Features Trivy supports two types of Go scanning, Go Modules and binaries built by Go. @@ -12,10 +16,10 @@ The following scanners are supported. The table below provides an outline of the features Trivy offers. -| Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | -|----------|:-----------:|:-----------------|:----------------------------------:| -| Modules | ✅ | Include | ✅[^2] | -| Binaries | ✅ | Exclude | - | +| Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | Stdlib | +|----------|:-----------:|:-----------------|:------------------------------------:|:------:| +| Modules | ✅ | Include | ✅[^2] | - | +| Binaries | ✅ | Exclude | - | ✅[^4] | !!! note Trivy scans only dependencies of the Go project. @@ -82,11 +86,12 @@ There are times when Go uses the `(devel)` version for modules/dependencies. - Dependencies replaced with local ones use the `(devel)` versions. In the first case, Trivy will attempt to parse any `-ldflags` as a secondary source, and will leave the version -empty if it cannot do so[^4]. For the second case, the version of such packages is empty. +empty if it cannot do so[^5]. For the second case, the version of such packages is empty. [^1]: It doesn't require the Internet access. [^2]: Need to download modules to local cache beforehand [^3]: See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477 -[^4]: See https://github.com/golang/go/issues/63432#issuecomment-1751610604 +[^4]: Identify the Go version used to compile the binary and detect its vulnerabilities +[^5]: See https://github.com/golang/go/issues/63432#issuecomment-1751610604 [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md index e18fecfcbf30..ee76a8e6844c 100644 --- a/docs/docs/scanner/vulnerability.md +++ b/docs/docs/scanner/vulnerability.md @@ -91,6 +91,7 @@ See [here](../coverage/language/index.md#supported-languages) for the supported | | [GitHub Advisory Database (npm)][nodejs-ghsa] | ✅ | - | | Java | [GitHub Advisory Database (Maven)][java-ghsa] | ✅ | - | | Go | [GitHub Advisory Database (Go)][go-ghsa] | ✅ | - | +| | [Go Vulnerability Database][go-vulndb] | ✅ | - | | Rust | [Open Source Vulnerabilities (crates.io)][rust-osv] | ✅ | - | | .NET | [GitHub Advisory Database (NuGet)][dotnet-ghsa] | ✅ | - | | C/C++ | [GitLab Advisories Community][gitlab] | ✅ | 1 month | @@ -255,6 +256,7 @@ Total: 7 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 3, CRITICAL: 2) [go-ghsa]: https://github.com/advisories?query=ecosystem%3Ago [swift-ghsa]: https://github.com/advisories?query=ecosystem%3Aswift +[go-vulndb]: https://pkg.go.dev/vuln/ [php]: https://github.com/FriendsOfPHP/security-advisories [ruby]: https://github.com/rubysec/ruby-advisory-db [nodejs]: https://github.com/nodejs/security-wg From c8ed432f28501f0bf2bf53b4b812072796b176f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 10:26:47 +0400 Subject: [PATCH 019/352] chore(deps): bump github.com/aws/aws-sdk-go-v2/service/ecr from 1.24.6 to 1.27.4 (#6598) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2ae035845aad..28ad57a007d7 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/aws/aws-sdk-go-v2/credentials v1.17.10 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.15 github.com/aws/aws-sdk-go-v2/service/ec2 v1.155.1 - github.com/aws/aws-sdk-go-v2/service/ecr v1.24.6 + github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4 github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c diff --git a/go.sum b/go.sum index b0a6660357fe..3a4bd41f089e 100644 --- a/go.sum +++ b/go.sum @@ -414,8 +414,8 @@ github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= github.com/aws/aws-sdk-go-v2/service/ec2 v1.155.1 h1:JBwnHlQvL39eeT03+vmBZuziutTKljmOKboKxQuIBck= github.com/aws/aws-sdk-go-v2/service/ec2 v1.155.1/go.mod h1:xejKuuRDjz6z5OqyeLsz01MlOqqW7CqpAB4PabNvpu8= -github.com/aws/aws-sdk-go-v2/service/ecr v1.24.6 h1:cT7h+GWP2k0hJSsPmppKgxl4C9R6gCC5/oF4oHnmpK4= -github.com/aws/aws-sdk-go-v2/service/ecr v1.24.6/go.mod h1:AOHmGMoPtSY9Zm2zBuwUJQBisIvYAZeA1n7b6f4e880= +github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4 h1:Qr9W21mzWT3RhfYn9iAux7CeRIdbnTAqmiOlASqQgZI= +github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4/go.mod h1:if7ybzzjOmDB8pat9FE35AHTY6ZxlYSy3YviSmFZv8c= github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6 h1:Sc2mLjyA1R8z2l705AN7Wr7QOlnUxVnGPJeDIVyUSrs= github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6/go.mod h1:LzHcyOEvaLjbc5e+fP/KmPWBr+h/Ef+EHvnf1Pzo368= github.com/aws/aws-sdk-go-v2/service/efs v1.28.1 h1:dKtJBzCIew4/VDsYgrx6v140cIpQVoe93kCNniYATtE= From a8af76a471a67d5a8317b7d4e865b2df138fedaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 06:27:31 +0000 Subject: [PATCH 020/352] chore(deps): bump actions/checkout from 4.1.2 to 4.1.4 (#6587) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-update-labels.yaml | 2 +- .github/workflows/mkdocs-dev.yaml | 2 +- .github/workflows/mkdocs-latest.yaml | 2 +- .github/workflows/publish-chart.yaml | 4 ++-- .github/workflows/release.yaml | 4 ++-- .github/workflows/reusable-release.yaml | 2 +- .github/workflows/scan.yaml | 2 +- .github/workflows/test-docs.yaml | 2 +- .github/workflows/test.yaml | 12 ++++++------ 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/auto-update-labels.yaml b/.github/workflows/auto-update-labels.yaml index bc9263fa60d0..f10febe7c070 100644 --- a/.github/workflows/auto-update-labels.yaml +++ b/.github/workflows/auto-update-labels.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout main - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 - name: Set up Go uses: actions/setup-go@v5 diff --git a/.github/workflows/mkdocs-dev.yaml b/.github/workflows/mkdocs-dev.yaml index ab38f9e9d11f..c4bdf02a7583 100644 --- a/.github/workflows/mkdocs-dev.yaml +++ b/.github/workflows/mkdocs-dev.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout main - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 with: fetch-depth: 0 persist-credentials: true diff --git a/.github/workflows/mkdocs-latest.yaml b/.github/workflows/mkdocs-latest.yaml index 8e4a6c2ff820..0dd993b90513 100644 --- a/.github/workflows/mkdocs-latest.yaml +++ b/.github/workflows/mkdocs-latest.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout main - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 with: fetch-depth: 0 persist-credentials: true diff --git a/.github/workflows/publish-chart.yaml b/.github/workflows/publish-chart.yaml index dfdbaad56e93..d427b52044bc 100644 --- a/.github/workflows/publish-chart.yaml +++ b/.github/workflows/publish-chart.yaml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 with: fetch-depth: 0 - name: Install Helm @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 with: fetch-depth: 0 - name: Install chart-releaser diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8cbcd208e2c6..b830d044768e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout code - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 with: fetch-depth: 0 @@ -35,7 +35,7 @@ jobs: sudo apt-get -y install rpm reprepro createrepo-c distro-info - name: Checkout trivy-repo - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 with: repository: ${{ github.repository_owner }}/trivy-repo path: trivy-repo diff --git a/.github/workflows/reusable-release.yaml b/.github/workflows/reusable-release.yaml index 1c52b70166e8..72b7d1c970cb 100644 --- a/.github/workflows/reusable-release.yaml +++ b/.github/workflows/reusable-release.yaml @@ -69,7 +69,7 @@ jobs: password: ${{ secrets.ECR_SECRET_ACCESS_KEY }} - name: Checkout code - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 with: fetch-depth: 0 diff --git a/.github/workflows/scan.yaml b/.github/workflows/scan.yaml index c191204ee718..82c8903c43c0 100644 --- a/.github/workflows/scan.yaml +++ b/.github/workflows/scan.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 - name: Run Trivy vulnerability scanner and create GitHub issues uses: knqyf263/trivy-issue-action@v0.0.5 diff --git a/.github/workflows/test-docs.yaml b/.github/workflows/test-docs.yaml index 94d40b4f5760..550f56c57eed 100644 --- a/.github/workflows/test-docs.yaml +++ b/.github/workflows/test-docs.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 with: fetch-depth: 0 persist-credentials: true diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c9b93fb4e37d..2717221220a2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -25,7 +25,7 @@ jobs: remove-haskell: "true" if: matrix.operating-system == 'ubuntu-latest' - - uses: actions/checkout@v4.1.2 + - uses: actions/checkout@v4.1.4 - name: Set up Go uses: actions/setup-go@v5 @@ -79,7 +79,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 - name: Set up Go uses: actions/setup-go@v5 @@ -108,7 +108,7 @@ jobs: remove-haskell: "true" - name: Check out code into the Go module directory - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 - name: Set up Go uses: actions/setup-go@v5 @@ -128,7 +128,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 - name: Set up Go uses: actions/setup-go@v5 @@ -159,7 +159,7 @@ jobs: remove-haskell: 'true' - name: Checkout - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 - name: Set up Go uses: actions/setup-go@v5 @@ -193,7 +193,7 @@ jobs: if: matrix.operating-system == 'ubuntu-latest' - name: Checkout - uses: actions/checkout@v4.1.2 + uses: actions/checkout@v4.1.4 - name: Set up Go uses: actions/setup-go@v5 From 5566548b7879b70e45da5849807813263911ce67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 06:35:05 +0000 Subject: [PATCH 021/352] chore(deps): bump azure/setup-helm from 3.5 to 4 (#6590) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-chart.yaml b/.github/workflows/publish-chart.yaml index d427b52044bc..a37dd8a24b8b 100644 --- a/.github/workflows/publish-chart.yaml +++ b/.github/workflows/publish-chart.yaml @@ -26,7 +26,7 @@ jobs: with: fetch-depth: 0 - name: Install Helm - uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 + uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 with: version: v3.5.0 - name: Set up python From 4369a19af771f81df141530bacdc8680e7120ac7 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 2 May 2024 12:40:11 +0600 Subject: [PATCH 022/352] feat: add ubuntu 23.10 and 24.04 support (#6573) --- pkg/detector/ospkg/ubuntu/ubuntu.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/detector/ospkg/ubuntu/ubuntu.go b/pkg/detector/ospkg/ubuntu/ubuntu.go index 7c6453050992..b396d530d84e 100644 --- a/pkg/detector/ospkg/ubuntu/ubuntu.go +++ b/pkg/detector/ospkg/ubuntu/ubuntu.go @@ -60,6 +60,8 @@ var ( "22.04": time.Date(2027, 4, 23, 23, 59, 59, 0, time.UTC), "22.10": time.Date(2023, 7, 20, 23, 59, 59, 0, time.UTC), "23.04": time.Date(2024, 1, 20, 23, 59, 59, 0, time.UTC), + "23.10": time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC), + "24.04": time.Date(2034, 3, 31, 23, 59, 59, 0, time.UTC), } ) From bce70af3693aafe80842af467999c283924dc8aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 07:08:01 +0000 Subject: [PATCH 023/352] chore(deps): bump github.com/open-policy-agent/opa from 0.62.0 to 0.64.1 (#6596) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 30 ++++++++++++++--------------- go.sum | 60 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index 28ad57a007d7..bfd83a8b01a9 100644 --- a/go.mod +++ b/go.mod @@ -80,7 +80,7 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 github.com/moby/buildkit v0.12.5 - github.com/open-policy-agent/opa v0.62.0 + github.com/open-policy-agent/opa v0.64.1 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0-rc6 github.com/openvex/go-vex v0.2.5 @@ -108,9 +108,9 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/mod v0.16.0 - golang.org/x/net v0.23.0 + golang.org/x/net v0.24.0 golang.org/x/sync v0.6.0 - golang.org/x/term v0.18.0 + golang.org/x/term v0.19.0 golang.org/x/text v0.14.0 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 google.golang.org/protobuf v1.33.0 @@ -136,16 +136,16 @@ require ( github.com/owenrumney/squealer v1.2.2 github.com/zclconf/go-cty v1.14.1 github.com/zclconf/go-cty-yaml v1.0.3 - golang.org/x/crypto v0.21.0 + golang.org/x/crypto v0.22.0 helm.sh/helm/v3 v3.14.2 sigs.k8s.io/yaml v1.4.0 ) require ( cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute v1.23.3 // indirect + cloud.google.com/go/compute v1.24.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.5 // indirect + cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/storage v1.36.0 // indirect dario.cat/mergo v1.0.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect @@ -352,7 +352,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/client_golang v1.19.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect @@ -386,8 +386,8 @@ require ( github.com/yusufpapurcu/wmi v1.2.3 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/sdk v1.24.0 // indirect @@ -395,16 +395,16 @@ require ( go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/oauth2 v0.17.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.19.0 // indirect - google.golang.org/api v0.155.0 // indirect + google.golang.org/api v0.162.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/grpc v1.62.0 // indirect + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/grpc v1.63.2 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 3a4bd41f089e..741fd4854dbc 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= @@ -111,8 +111,8 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= @@ -1389,8 +1389,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/open-policy-agent/opa v0.62.0 h1:8NAWkrg3tnMBi+pYqL7pEi7h6QmbMmVf/5IyjJS/A2s= -github.com/open-policy-agent/opa v0.62.0/go.mod h1:FD8D++1j1m74Qam2iUnKlfPDeoxWTXANaRUVu8W/tmA= +github.com/open-policy-agent/opa v0.64.1 h1:n8IJTYlFWzqiOYx+JiawbErVxiqAyXohovcZxYbskxQ= +github.com/open-policy-agent/opa v0.64.1/go.mod h1:j4VeLorVpKipnkQ2TDjWshEuV3cvP/rHzQhYaraUXZY= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -1471,8 +1471,8 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -1725,10 +1725,10 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 h1:o8iWeVFa1BcLtVEV0LzrCxV2/55tB3xLxADr6Kyoey4= @@ -1784,8 +1784,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1898,8 +1898,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1926,8 +1926,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2081,8 +2081,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2231,8 +2231,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA= -google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= +google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps= +google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2347,12 +2347,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -2392,8 +2392,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= -google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From c17176ba9770976ce94082ee5180887716fa310f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 12:31:02 +0400 Subject: [PATCH 024/352] chore(deps): bump github.com/testcontainers/testcontainers-go from 0.28.0 to 0.30.0 (#6595) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index bfd83a8b01a9..efa7f5301198 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( github.com/moby/buildkit v0.12.5 github.com/open-policy-agent/opa v0.64.1 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.1.0-rc6 + github.com/opencontainers/image-spec v1.1.0 github.com/openvex/go-vex v0.2.5 github.com/owenrumney/go-sarif/v2 v2.3.0 github.com/package-url/packageurl-go v0.1.2 @@ -98,7 +98,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.28.0 + github.com/testcontainers/testcontainers-go v0.30.0 github.com/testcontainers/testcontainers-go/modules/localstack v0.28.0 github.com/tetratelabs/wazero v1.7.0 github.com/twitchtv/twirp v8.1.2+incompatible @@ -387,7 +387,7 @@ require ( go.mongodb.org/mongo-driver v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/sdk v1.24.0 // indirect diff --git a/go.sum b/go.sum index 741fd4854dbc..0583c6c7503b 100644 --- a/go.sum +++ b/go.sum @@ -1399,8 +1399,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU= -github.com/opencontainers/image-spec v1.1.0-rc6/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -1631,8 +1631,8 @@ github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BG github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= -github.com/testcontainers/testcontainers-go v0.28.0 h1:1HLm9qm+J5VikzFDYhOd+Zw12NtOl+8drH2E8nTY1r8= -github.com/testcontainers/testcontainers-go v0.28.0/go.mod h1:COlDpUXbwW3owtpMkEB1zo9gwb1CoKVKlyrVPejF4AU= +github.com/testcontainers/testcontainers-go v0.30.0 h1:jmn/XS22q4YRrcMwWg0pAwlClzs/abopbsBzrepyc4E= +github.com/testcontainers/testcontainers-go v0.30.0/go.mod h1:K+kHNGiM5zjklKjgTtcrEetF3uhWbMUyqAQoyoh8Pf0= github.com/testcontainers/testcontainers-go/modules/localstack v0.28.0 h1:NOtK4tz2J1KbdAV6Lk9AQPUXB6Op8jGzKNfwVCThRxU= github.com/testcontainers/testcontainers-go/modules/localstack v0.28.0/go.mod h1:nLimAfgHTQfaDZ2cO8/B4Z1qr8e020sM3ybpSsOVAUY= github.com/tetratelabs/wazero v1.7.0 h1:jg5qPydno59wqjpGrHph81lbtHzTrWzwwtD4cD88+hQ= @@ -1727,8 +1727,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 h1:o8iWeVFa1BcLtVEV0LzrCxV2/55tB3xLxADr6Kyoey4= From 2dc76ba782eb763487d95e9ab16d8fd50a50220e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 12:31:57 +0400 Subject: [PATCH 025/352] chore(deps): bump sigstore/cosign-installer from 3.4.0 to 3.5.0 (#6588) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/reusable-release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-release.yaml b/.github/workflows/reusable-release.yaml index 72b7d1c970cb..8f5d26a52922 100644 --- a/.github/workflows/reusable-release.yaml +++ b/.github/workflows/reusable-release.yaml @@ -36,7 +36,7 @@ jobs: remove-haskell: 'true' - name: Cosign install - uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 + uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 - name: Set up QEMU uses: docker/setup-qemu-action@v3 From 8e814fa23d886a3378f413b0fff881c1a27e9dc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 12:32:24 +0400 Subject: [PATCH 026/352] chore(deps): bump google.golang.org/protobuf from 1.33.0 to 1.34.0 (#6597) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index efa7f5301198..c76b55b405fe 100644 --- a/go.mod +++ b/go.mod @@ -113,7 +113,7 @@ require ( golang.org/x/term v0.19.0 golang.org/x/text v0.14.0 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 - google.golang.org/protobuf v1.33.0 + google.golang.org/protobuf v1.34.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.29.3 k8s.io/utils v0.0.0-20231127182322-b307cd553661 diff --git a/go.sum b/go.sum index 0583c6c7503b..03b0074d09c4 100644 --- a/go.sum +++ b/go.sum @@ -2410,8 +2410,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= +google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 03830c50c95540b22e9e739002a205a4d8d5273c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 14:29:16 +0400 Subject: [PATCH 027/352] chore(deps): bump github.com/sigstore/rekor from 1.2.2 to 1.3.6 (#6599) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 35 +++++++++++----------- go.sum | 95 +++++++++++++++++++++++++++------------------------------- 2 files changed, 61 insertions(+), 69 deletions(-) diff --git a/go.mod b/go.mod index c76b55b405fe..a7d9be671687 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2/config v1.27.10 github.com/aws/aws-sdk-go-v2/credentials v1.17.10 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.15 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.9 github.com/aws/aws-sdk-go-v2/service/ec2 v1.155.1 github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4 github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 @@ -89,7 +89,7 @@ require ( github.com/quasilyte/go-ruleguard/dsl v0.3.22 github.com/samber/lo v1.39.0 github.com/secure-systems-lab/go-securesystemslib v0.8.0 - github.com/sigstore/rekor v1.2.2 + github.com/sigstore/rekor v1.3.6 github.com/sirupsen/logrus v1.9.3 github.com/sosedoff/gitkit v0.4.0 github.com/spdx/tools-golang v0.5.4-0.20231108154018-0c0f394b5e1a // v0.5.3 with necessary changes. Can be upgraded to version 0.5.4 after release. @@ -112,7 +112,7 @@ require ( golang.org/x/sync v0.6.0 golang.org/x/term v0.19.0 golang.org/x/text v0.14.0 - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/protobuf v1.34.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.29.3 @@ -142,11 +142,11 @@ require ( ) require ( - cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute v1.24.0 // indirect + cloud.google.com/go v0.112.1 // indirect + cloud.google.com/go/compute v1.25.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.6 // indirect - cloud.google.com/go/storage v1.36.0 // indirect + cloud.google.com/go/storage v1.39.1 // indirect dario.cat/mergo v1.0.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect @@ -158,7 +158,7 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/Intevation/gval v1.3.0 // indirect github.com/Intevation/jsonpath v0.2.1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect @@ -211,7 +211,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 // indirect github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5 // indirect github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6 // indirect - github.com/aws/aws-sdk-go-v2/service/kms v1.27.7 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 // indirect github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6 // indirect github.com/aws/aws-sdk-go-v2/service/mq v1.20.6 // indirect github.com/aws/aws-sdk-go-v2/service/neptune v1.28.1 // indirect @@ -274,14 +274,13 @@ require ( github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/validate v0.24.0 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/go-test/deep v1.1.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-yaml v1.9.5 // indirect github.com/gofrs/uuid v4.3.1+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang-jwt/jwt/v5 v5.2.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/btree v1.1.2 // indirect github.com/google/gnostic-models v0.6.8 // indirect @@ -290,7 +289,7 @@ require ( github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/googleapis/gax-go/v2 v2.12.3 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect @@ -310,7 +309,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.9 // indirect @@ -386,7 +385,7 @@ require ( github.com/yusufpapurcu/wmi v1.2.3 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect @@ -395,15 +394,15 @@ require ( go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/oauth2 v0.17.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.19.0 // indirect - google.golang.org/api v0.162.0 // indirect + google.golang.org/api v0.172.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240311173647-c811ad7063a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/grpc v1.63.2 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 03b0074d09c4..a93f6ad56542 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= -cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= @@ -69,8 +69,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= +cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU= +cloud.google.com/go/compute v1.25.0/go.mod h1:GR7F0ZPZH8EhChlMo9FkLd7eUTwEymjqQagxzilIxIE= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= @@ -173,8 +173,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= -cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= +cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= @@ -189,6 +189,8 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA= @@ -227,8 +229,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= @@ -236,8 +238,8 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CycloneDX/cyclonedx-go v0.8.0 h1:FyWVj6x6hoJrui5uRQdYZcSievw3Z32Z88uYzG/0D6M= github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0= github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible h1:juIaKLLVhqzP55d8x4cSVgwyQv76Z55/fRv/UBr2KkQ= github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs= @@ -378,8 +380,8 @@ github.com/aws/aws-sdk-go-v2/credentials v1.17.10 h1:qDZ3EA2lv1KangvQB6y258OssCH github.com/aws/aws-sdk-go-v2/credentials v1.17.10/go.mod h1:6t3sucOaYDwDssHQa0ojH1RpmVmF5/jArkye1b2FKMI= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.15 h1:2MUXyGW6dVaQz6aqycpbdLIH1NMcUI6kW6vQ0RabGYg= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.15/go.mod h1:aHbhbR6WEQgHAiRj41EQ2W47yOYwNtIkWTXmcAtYqj8= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.9 h1:vXY/Hq1XdxHBIYgBUmug/AbMyIe1AKulPYS2/VE1X70= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.9/go.mod h1:GyJJTZoHVuENM4TeJEl5Ffs4W9m19u+4wKJcDi/GZ4A= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= @@ -446,8 +448,8 @@ github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5 h1:yCkyZDGahaCaAkdpVx8Te05t6e github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5/go.mod h1:/KmX+vXMPJGAB56reo95tnsXa6QPNx6qli4L1AmYb7E= github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6 h1:FO/aIHk86VePDUh/3Q/A5pnvu45miO1GZB8rIq2BUlA= github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6/go.mod h1:Sj7qc+P/GOGOPMDn8+B7Cs+WPq1Gk+R6CXRXVhZtWcA= -github.com/aws/aws-sdk-go-v2/service/kms v1.27.7 h1:wN7AN7iOiAgT9HmdifZNSvbr6S7gSpLjSSOQHIaGmFc= -github.com/aws/aws-sdk-go-v2/service/kms v1.27.7/go.mod h1:D9FVDkZjkZnnFHymJ3fPVz0zOUlNSd0xcIIVmmrAac8= +github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 h1:yS0JkEdV6h9JOo8sy2JSpjX+i7vsKifU8SIeHrqiDhU= +github.com/aws/aws-sdk-go-v2/service/kms v1.30.0/go.mod h1:+I8VUUSVD4p5ISQtzpgSva4I8cJ4SQ4b1dcBcof7O+g= github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6 h1:w8lI9zlVwRTL9f4KB9fRThddhRivv+EQQzv2nU8JDQo= github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6/go.mod h1:0V5z1X/8NA9eQ5cZSz5ZaHU8xA/hId2ZAlsHeO7Jrdk= github.com/aws/aws-sdk-go-v2/service/mq v1.20.6 h1:n86T5yw0kS6a5nbpkEpDzLPCBXXb35lx3iDkmQWlizA= @@ -544,8 +546,6 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= @@ -762,8 +762,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= -github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -789,8 +787,6 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -857,20 +853,17 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= -github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -909,8 +902,8 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= -github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= @@ -1041,8 +1034,8 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -1181,8 +1174,8 @@ github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.0/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 h1:PPPlUUqPP6fLudIK4n0l0VU4KT2cQGnheW9x8pNiCHI= @@ -1214,9 +1207,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/liamg/iamgo v0.0.9 h1:tADGm3xVotyRJmuKKaH4+zsBn7LOcvgdpuF3WsSKW3c= github.com/liamg/iamgo v0.0.9/go.mod h1:Kk6ZxBF/GQqG9nnaUjIi6jf+WXNpeOTyhwc6gnguaZQ= github.com/liamg/jfather v0.0.7 h1:Xf78zS263yfT+xr2VSo6+kyAy4ROlCacRqJG7s5jt4k= @@ -1543,8 +1535,8 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sigstore/rekor v1.2.2 h1:5JK/zKZvcQpL/jBmHvmFj3YbpDMBQnJQ6ygp8xdF3bY= -github.com/sigstore/rekor v1.2.2/go.mod h1:FGnWBGWzeNceJnp0x9eDFd41mI8aQqCjj+Zp0IEs0Qg= +github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8= +github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1725,8 +1717,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= @@ -1926,8 +1918,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2179,8 +2171,9 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 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.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -2231,8 +2224,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps= -google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= +google.golang.org/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk= +google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2347,12 +2340,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= +google.golang.org/genproto/googleapis/api v0.0.0-20240311173647-c811ad7063a7 h1:oqta3O3AnlWbmIE3bFnWbu4bRxZjfbWCp0cKSuZh01E= +google.golang.org/genproto/googleapis/api v0.0.0-20240311173647-c811ad7063a7/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= From 194a81468826e9f1f172aab05573a4e418ffb4bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 14:50:48 +0400 Subject: [PATCH 028/352] chore(deps): bump github.com/zclconf/go-cty from 1.14.1 to 1.14.4 (#6601) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a7d9be671687..40012b392c2e 100644 --- a/go.mod +++ b/go.mod @@ -134,7 +134,7 @@ require ( github.com/liamg/memoryfs v1.6.0 github.com/mitchellh/go-homedir v1.1.0 github.com/owenrumney/squealer v1.2.2 - github.com/zclconf/go-cty v1.14.1 + github.com/zclconf/go-cty v1.14.4 github.com/zclconf/go-cty-yaml v1.0.3 golang.org/x/crypto v0.22.0 helm.sh/helm/v3 v3.14.2 diff --git a/go.sum b/go.sum index a93f6ad56542..6858e5ccce0b 100644 --- a/go.sum +++ b/go.sum @@ -1692,8 +1692,8 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPS github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA= -github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= +github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-yaml v1.0.3 h1:og/eOQ7lvA/WWhHGFETVWNduJM7Rjsv2RRpx1sdFMLc= github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= From c6d5d856ce0d87d8eda2e27a432277aee5319a35 Mon Sep 17 00:00:00 2001 From: chenk Date: Thu, 2 May 2024 14:08:59 +0300 Subject: [PATCH 029/352] BREAKING: add support for k8s `disable-node-collector` flag (#6311) Signed-off-by: chenk --- .../configuration/cli/trivy_kubernetes.md | 2 +- pkg/flag/kubernetes_flags.go | 28 +++---- pkg/flag/options.go | 7 +- pkg/k8s/commands/cluster.go | 8 +- pkg/k8s/commands/run.go | 1 - pkg/k8s/report/report.go | 28 +++---- pkg/k8s/report/report_test.go | 42 ++++++----- pkg/k8s/report/summary.go | 9 +-- pkg/k8s/report/summary_test.go | 26 ++----- pkg/k8s/writer.go | 4 +- pkg/k8s/writer_test.go | 73 +++++++++++++------ 11 files changed, 114 insertions(+), 114 deletions(-) diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 7b7c91f3d88e..4c3aaf5bb144 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -34,11 +34,11 @@ trivy kubernetes [flags] [CONTEXT] --cache-ttl duration cache TTL when using redis as cache backend --clear-cache clear image caches without scanning --compliance string compliance report to generate (k8s-nsa,k8s-cis,k8s-pss-baseline,k8s-pss-restricted) - --components strings specify which components to scan (workload,infra) (default [workload,infra]) --config-data strings specify paths from which data for the Rego policies will be recursively loaded --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --disable-node-collector When the flag is activated, the node-collector job will not be executed, thus skipping misconfiguration findings on the node. --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --exclude-kinds strings indicate the kinds exclude from scanning (example: node) diff --git a/pkg/flag/kubernetes_flags.go b/pkg/flag/kubernetes_flags.go index e325eb861045..996317e0be4f 100644 --- a/pkg/flag/kubernetes_flags.go +++ b/pkg/flag/kubernetes_flags.go @@ -15,19 +15,6 @@ var ( ConfigName: "kubernetes.kubeconfig", Usage: "specify the kubeconfig file path to use", } - ComponentsFlag = Flag[[]string]{ - Name: "components", - ConfigName: "kubernetes.components", - Default: []string{ - "workload", - "infra", - }, - Values: []string{ - "workload", - "infra", - }, - Usage: "specify which components to scan", - } K8sVersionFlag = Flag[string]{ Name: "k8s-version", ConfigName: "kubernetes.k8s-version", @@ -38,6 +25,11 @@ var ( ConfigName: "kubernetes.tolerations", Usage: "specify node-collector job tolerations (example: key1=value1:NoExecute,key2=value2:NoSchedule)", } + DisableNodeCollector = Flag[bool]{ + Name: "disable-node-collector", + ConfigName: "kubernetes.disableNodeCollector", + Usage: "When the flag is activated, the node-collector job will not be executed, thus skipping misconfiguration findings on the node.", + } NodeCollectorNamespace = Flag[string]{ Name: "node-collector-namespace", ConfigName: "kubernetes.node-collector.namespace", @@ -97,9 +89,9 @@ var ( type K8sFlagGroup struct { KubeConfig *Flag[string] - Components *Flag[[]string] K8sVersion *Flag[string] Tolerations *Flag[[]string] + DisableNodeCollector *Flag[bool] NodeCollectorImageRef *Flag[string] NodeCollectorNamespace *Flag[string] ExcludeOwned *Flag[bool] @@ -114,12 +106,12 @@ type K8sFlagGroup struct { type K8sOptions struct { KubeConfig string - Components []string K8sVersion string Tolerations []corev1.Toleration NodeCollectorImageRef string NodeCollectorNamespace string ExcludeOwned bool + DisableNodeCollector bool ExcludeNodes map[string]string ExcludeKinds []string IncludeKinds []string @@ -132,9 +124,9 @@ type K8sOptions struct { func NewK8sFlagGroup() *K8sFlagGroup { return &K8sFlagGroup{ KubeConfig: KubeConfigFlag.Clone(), - Components: ComponentsFlag.Clone(), K8sVersion: K8sVersionFlag.Clone(), Tolerations: TolerationsFlag.Clone(), + DisableNodeCollector: DisableNodeCollector.Clone(), NodeCollectorNamespace: NodeCollectorNamespace.Clone(), ExcludeOwned: ExcludeOwned.Clone(), ExcludeNodes: ExcludeNodes.Clone(), @@ -155,8 +147,8 @@ func (f *K8sFlagGroup) Name() string { func (f *K8sFlagGroup) Flags() []Flagger { return []Flagger{ f.KubeConfig, - f.Components, f.K8sVersion, + f.DisableNodeCollector, f.Tolerations, f.NodeCollectorNamespace, f.ExcludeOwned, @@ -199,9 +191,9 @@ func (f *K8sFlagGroup) ToOptions() (K8sOptions, error) { return K8sOptions{ KubeConfig: f.KubeConfig.Value(), - Components: f.Components.Value(), K8sVersion: f.K8sVersion.Value(), Tolerations: tolerations, + DisableNodeCollector: f.DisableNodeCollector.Value(), NodeCollectorNamespace: f.NodeCollectorNamespace.Value(), ExcludeOwned: f.ExcludeOwned.Value(), ExcludeNodes: exludeNodeLabels, diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 9448f7c0f2fa..9d49f5dfe807 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -360,15 +360,10 @@ func (o *Options) Align() { } // Vulnerability scanning is disabled by default for CycloneDX. - if o.Format == types.FormatCycloneDX && !viper.IsSet(ScannersFlag.ConfigName) && len(o.K8sOptions.Components) == 0 { // remove K8sOptions.Components validation check when vuln scan is supported for k8s report with cycloneDX + if o.Format == types.FormatCycloneDX && !viper.IsSet(ScannersFlag.ConfigName) { log.Info(`"--format cyclonedx" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the CycloneDX report.`) o.Scanners = nil } - - if o.Format == types.FormatCycloneDX && len(o.K8sOptions.Components) > 0 { - log.Info(`"k8s with --format cyclonedx" disable security scanning`) - o.Scanners = nil - } } // RegistryOpts returns options for OCI registries diff --git a/pkg/k8s/commands/cluster.go b/pkg/k8s/commands/cluster.go index bc985bff7393..6b169771f1ca 100644 --- a/pkg/k8s/commands/cluster.go +++ b/pkg/k8s/commands/cluster.go @@ -3,13 +3,13 @@ package commands import ( "context" - "golang.org/x/exp/slices" "golang.org/x/xerrors" k8sArtifacts "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" "github.com/aquasecurity/trivy-kubernetes/pkg/k8s" "github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s" "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" ) @@ -34,7 +34,7 @@ func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) err trivyk8s.WithIncludeKinds(opts.IncludeKinds), trivyk8s.WithExcludeOwned(opts.ExcludeOwned), } - if opts.Scanners.AnyEnabled(types.MisconfigScanner) && slices.Contains(opts.Components, "infra") { + if opts.Scanners.AnyEnabled(types.MisconfigScanner) && !opts.DisableNodeCollector { artifacts, err = trivyk8s.New(cluster, k8sOpts...).ListArtifactAndNodeInfo(ctx, trivyk8s.WithScanJobNamespace(opts.NodeCollectorNamespace), trivyk8s.WithIgnoreLabels(opts.ExcludeNodes), @@ -53,6 +53,10 @@ func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) err return xerrors.Errorf(`unknown format %q. Use "json" or "table" or "cyclonedx"`, opts.Format) } + if !opts.DisableNodeCollector && !opts.Quiet { + log.InfoContext(ctx, "Node scanning is enabled") + log.InfoContext(ctx, "If you want to disable Node scanning via an in-cluster Job, please try '--disable-node-collector' to disable the Node-Collector job.") + } runner := newRunner(opts, cluster.GetCurrentContext()) return runner.run(ctx, artifacts) } diff --git a/pkg/k8s/commands/run.go b/pkg/k8s/commands/run.go index 01ebf1db645f..3516f1afc8e9 100644 --- a/pkg/k8s/commands/run.go +++ b/pkg/k8s/commands/run.go @@ -115,7 +115,6 @@ func (r *runner) run(ctx context.Context, artifacts []*k8sArtifacts.Artifact) er Report: r.flagOpts.ReportFormat, Output: output, Severities: r.flagOpts.Severities, - Components: r.flagOpts.Components, Scanners: r.flagOpts.ScanOptions.Scanners, APIVersion: r.flagOpts.AppVersion, }); err != nil { diff --git a/pkg/k8s/report/report.go b/pkg/k8s/report/report.go index 0861a8669143..1db95394514e 100644 --- a/pkg/k8s/report/report.go +++ b/pkg/k8s/report/report.go @@ -33,7 +33,6 @@ type Option struct { Severities []dbTypes.Severity ColumnHeading []string Scanners types.Scanners - Components []string APIVersion string } @@ -134,12 +133,12 @@ type reports struct { // - misconfiguration report // - rbac report // - infra checks report -func SeparateMisconfigReports(k8sReport Report, scanners types.Scanners, components []string) []reports { +func SeparateMisconfigReports(k8sReport Report, scanners types.Scanners) []reports { var workloadMisconfig, infraMisconfig, rbacAssessment, workloadVulnerabilities, infraVulnerabilities, workloadResource []Resource for _, resource := range k8sReport.Resources { switch { - case vulnerabilitiesOrSecretResource(resource): + case vulnerabilitiesOrSecretResource(resource) && !infraResource(resource): if resource.Namespace == infraNamespace || nodeInfoResource(resource) { infraVulnerabilities = append(infraVulnerabilities, nodeKind(resource)) } else { @@ -150,8 +149,7 @@ func SeparateMisconfigReports(k8sReport Report, scanners types.Scanners, compone case infraResource(resource): infraMisconfig = append(infraMisconfig, nodeKind(resource)) case scanners.Enabled(types.MisconfigScanner) && - !rbacResource(resource) && - slices.Contains(components, workloadComponent): + !rbacResource(resource): workloadMisconfig = append(workloadMisconfig, resource) } } @@ -159,22 +157,21 @@ func SeparateMisconfigReports(k8sReport Report, scanners types.Scanners, compone var r []reports workloadResource = append(workloadResource, workloadVulnerabilities...) workloadResource = append(workloadResource, workloadMisconfig...) - if shouldAddToReport(scanners, components, workloadComponent) { + if shouldAddToReport(scanners) { workloadReport := Report{ SchemaVersion: 0, ClusterName: k8sReport.ClusterName, Resources: workloadResource, name: "Workload Assessment", } - if slices.Contains(components, workloadComponent) { - r = append(r, reports{ - Report: workloadReport, - Columns: WorkloadColumns(), - }) - } + r = append(r, reports{ + Report: workloadReport, + Columns: WorkloadColumns(), + }) + } infraMisconfig = append(infraMisconfig, infraVulnerabilities...) - if shouldAddToReport(scanners, components, infraComponent) { + if shouldAddToReport(scanners) { r = append(r, reports{ Report: Report{ SchemaVersion: 0, @@ -266,12 +263,11 @@ func (r Report) PrintErrors() { } } -func shouldAddToReport(scanners types.Scanners, components []string, componentType string) bool { +func shouldAddToReport(scanners types.Scanners) bool { return scanners.AnyEnabled( types.MisconfigScanner, types.VulnerabilityScanner, - types.SecretScanner) && - slices.Contains(components, componentType) + types.SecretScanner) } func vulnerabilitiesOrSecretResource(resource Resource) bool { diff --git a/pkg/k8s/report/report_test.go b/pkg/k8s/report/report_test.go index 6d14b52e12a9..e9e9ae5e3a91 100644 --- a/pkg/k8s/report/report_test.go +++ b/pkg/k8s/report/report_test.go @@ -515,7 +515,6 @@ func Test_separateMisconfigReports(t *testing.T) { name string k8sReport Report scanners types.Scanners - components []string expectedReports []Report }{ { @@ -525,10 +524,6 @@ func Test_separateMisconfigReports(t *testing.T) { types.MisconfigScanner, types.RBACScanner, }, - components: []string{ - workloadComponent, - infraComponent, - }, expectedReports: []Report{ // the order matter for the test { @@ -545,10 +540,6 @@ func Test_separateMisconfigReports(t *testing.T) { name: "Config and Infra for the same resource", k8sReport: k8sReport, scanners: types.Scanners{types.MisconfigScanner}, - components: []string{ - workloadComponent, - infraComponent, - }, expectedReports: []Report{ // the order matter for the test { @@ -569,10 +560,9 @@ func Test_separateMisconfigReports(t *testing.T) { }, }, { - name: "Config Report Only", - k8sReport: k8sReport, - scanners: types.Scanners{types.MisconfigScanner}, - components: []string{workloadComponent}, + name: "Config Report Only", + k8sReport: k8sReport, + scanners: types.Scanners{types.MisconfigScanner}, expectedReports: []Report{ { Resources: []Resource{ @@ -580,15 +570,29 @@ func Test_separateMisconfigReports(t *testing.T) { {Kind: "StatefulSet"}, }, }, + { + Resources: []Resource{ + {Kind: "Pod"}, + }, + }, }, }, { - name: "Infra Report Only", - k8sReport: k8sReport, - scanners: types.Scanners{types.MisconfigScanner}, - components: []string{infraComponent}, + name: "Infra Report Only", + k8sReport: k8sReport, + scanners: types.Scanners{types.MisconfigScanner}, expectedReports: []Report{ - {Resources: []Resource{{Kind: "Pod"}}}, + { + Resources: []Resource{ + {Kind: "Deployment"}, + {Kind: "StatefulSet"}, + }, + }, + { + Resources: []Resource{ + {Kind: "Pod"}, + }, + }, }, }, @@ -597,7 +601,7 @@ func Test_separateMisconfigReports(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - reports := SeparateMisconfigReports(tt.k8sReport, tt.scanners, tt.components) + reports := SeparateMisconfigReports(tt.k8sReport, tt.scanners) assert.Equal(t, len(tt.expectedReports), len(reports)) for i := range reports { diff --git a/pkg/k8s/report/summary.go b/pkg/k8s/report/summary.go index f35a1b3f6624..de81e1a7b532 100644 --- a/pkg/k8s/report/summary.go +++ b/pkg/k8s/report/summary.go @@ -35,7 +35,7 @@ func NewSummaryWriter(output io.Writer, requiredSevs []dbTypes.Severity, columnH } } -func ColumnHeading(scanners types.Scanners, components, availableColumns []string) []string { +func ColumnHeading(scanners types.Scanners, availableColumns []string) []string { columns := []string{ NamespaceColumn, ResourceColumn, @@ -47,12 +47,7 @@ func ColumnHeading(scanners types.Scanners, components, availableColumns []strin case types.VulnerabilityScanner: securityOptions[VulnerabilitiesColumn] = nil case types.MisconfigScanner: - if slices.Contains(components, workloadComponent) { - securityOptions[MisconfigurationsColumn] = nil - } - if slices.Contains(components, infraComponent) { - securityOptions[MisconfigurationsColumn] = nil - } + securityOptions[MisconfigurationsColumn] = nil case types.SecretScanner: securityOptions[SecretsColumn] = nil case types.RBACScanner: diff --git a/pkg/k8s/report/summary_test.go b/pkg/k8s/report/summary_test.go index 8744db1c6233..7a38f3b01993 100644 --- a/pkg/k8s/report/summary_test.go +++ b/pkg/k8s/report/summary_test.go @@ -20,7 +20,6 @@ func TestReport_ColumnHeading(t *testing.T) { tests := []struct { name string scanners types.Scanners - components []string availableColumns []string want []string }{ @@ -28,10 +27,6 @@ func TestReport_ColumnHeading(t *testing.T) { name: "filter workload columns", scanners: allScanners, availableColumns: WorkloadColumns(), - components: []string{ - workloadComponent, - infraComponent, - }, want: []string{ NamespaceColumn, ResourceColumn, @@ -43,7 +38,6 @@ func TestReport_ColumnHeading(t *testing.T) { { name: "filter rbac columns", scanners: allScanners, - components: []string{}, availableColumns: RoleColumns(), want: []string{ NamespaceColumn, @@ -52,12 +46,8 @@ func TestReport_ColumnHeading(t *testing.T) { }, }, { - name: "filter infra columns", - scanners: allScanners, - components: []string{ - workloadComponent, - infraComponent, - }, + name: "filter infra columns", + scanners: allScanners, availableColumns: InfraColumns(), want: []string{ NamespaceColumn, @@ -68,12 +58,8 @@ func TestReport_ColumnHeading(t *testing.T) { }, }, { - name: "config column only", - scanners: types.Scanners{types.MisconfigScanner}, - components: []string{ - workloadComponent, - infraComponent, - }, + name: "config column only", + scanners: types.Scanners{types.MisconfigScanner}, availableColumns: WorkloadColumns(), want: []string{ NamespaceColumn, @@ -84,7 +70,6 @@ func TestReport_ColumnHeading(t *testing.T) { { name: "secret column only", scanners: types.Scanners{types.SecretScanner}, - components: []string{}, availableColumns: WorkloadColumns(), want: []string{ NamespaceColumn, @@ -95,7 +80,6 @@ func TestReport_ColumnHeading(t *testing.T) { { name: "vuln column only", scanners: types.Scanners{types.VulnerabilityScanner}, - components: []string{}, availableColumns: WorkloadColumns(), want: []string{ NamespaceColumn, @@ -107,7 +91,7 @@ func TestReport_ColumnHeading(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - column := ColumnHeading(tt.scanners, tt.components, tt.availableColumns) + column := ColumnHeading(tt.scanners, tt.availableColumns) if !assert.Equal(t, column, tt.want) { t.Error(fmt.Errorf("TestReport_ColumnHeading want %v got %v", tt.want, column)) } diff --git a/pkg/k8s/writer.go b/pkg/k8s/writer.go index 13204501fb59..abc5bb381b64 100644 --- a/pkg/k8s/writer.go +++ b/pkg/k8s/writer.go @@ -23,7 +23,7 @@ func Write(ctx context.Context, k8sreport report.Report, option report.Option) e } return jwriter.Write(k8sreport) case types.FormatTable: - separatedReports := report.SeparateMisconfigReports(k8sreport, option.Scanners, option.Components) + separatedReports := report.SeparateMisconfigReports(k8sreport, option.Scanners) if option.Report == report.SummaryReport { target := fmt.Sprintf("Summary Report for %s", k8sreport.ClusterName) @@ -35,7 +35,7 @@ func Write(ctx context.Context, k8sreport report.Report, option report.Option) e Output: option.Output, Report: option.Report, Severities: option.Severities, - ColumnHeading: report.ColumnHeading(option.Scanners, option.Components, r.Columns), + ColumnHeading: report.ColumnHeading(option.Scanners, r.Columns), } if err := writer.Write(ctx, r.Report); err != nil { diff --git a/pkg/k8s/writer_test.go b/pkg/k8s/writer_test.go index 23342a044916..1b5b6af93c9c 100644 --- a/pkg/k8s/writer_test.go +++ b/pkg/k8s/writer_test.go @@ -23,9 +23,6 @@ const ( tableFormat = "table" jsonFormat = "json" cycloneDXFormat = "cyclonedx" - - workloadComponent = "workload" - infraComponent = "infra" ) var ( @@ -202,18 +199,16 @@ func TestReportWrite_Summary(t *testing.T) { report report.Report opt report.Option scanners types.Scanners - components []string severities []dbTypes.Severity expectedOutput string }{ { - name: "Only config, all severities", + name: "Only config, all serverities", report: report.Report{ ClusterName: "test", Resources: []report.Resource{deployOrionWithMisconfigs}, }, scanners: types.Scanners{types.MisconfigScanner}, - components: []string{workloadComponent}, severities: allSeverities, expectedOutput: `Summary Report for test ======================= @@ -226,16 +221,24 @@ Workload Assessment ├───────────┼──────────────┼───┼───┼───┼───┼───┤ │ default │ Deploy/orion │ 1 │ 2 │ 1 │ 2 │ 1 │ └───────────┴──────────────┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN + + +Infra Assessment +┌───────────┬──────────┬───────────────────┐ +│ Namespace │ Resource │ Misconfigurations │ +│ │ ├───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ +└───────────┴──────────┴───┴───┴───┴───┴───┘ Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, }, { - name: "Only vuln, all severities", + name: "Only vuln, all serverities", report: report.Report{ ClusterName: "test", Resources: []report.Resource{deployOrionWithVulns}, }, scanners: types.Scanners{types.VulnerabilityScanner}, - components: []string{workloadComponent}, severities: allSeverities, expectedOutput: `Summary Report for test ======================= @@ -248,10 +251,19 @@ Workload Assessment ├───────────┼──────────────┼───┼───┼───┼───┼───┤ │ default │ Deploy/orion │ 2 │ 1 │ 2 │ 1 │ 1 │ └───────────┴──────────────┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN + + +Infra Assessment +┌───────────┬──────────┬───────────────────┐ +│ Namespace │ Resource │ Vulnerabilities │ +│ │ ├───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ +└───────────┴──────────┴───┴───┴───┴───┴───┘ Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, }, { - name: "Only rbac, all severities", + name: "Only rbac, all serverities", report: report.Report{ ClusterName: "test", Resources: []report.Resource{roleWithMisconfig}, @@ -272,13 +284,12 @@ RBAC Assessment Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, }, { - name: "Only secret, all severities", + name: "Only secret, all serverities", report: report.Report{ ClusterName: "test", Resources: []report.Resource{deployLuaWithSecrets}, }, scanners: types.Scanners{types.SecretScanner}, - components: []string{workloadComponent}, severities: allSeverities, expectedOutput: `Summary Report for test ======================= @@ -291,20 +302,37 @@ Workload Assessment ├───────────┼────────────┼───┼───┼───┼───┼───┤ │ default │ Deploy/lua │ 1 │ │ 1 │ │ │ └───────────┴────────────┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN + + +Infra Assessment +┌───────────┬──────────┬───────────────────┐ +│ Namespace │ Resource │ Secrets │ +│ │ ├───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ +└───────────┴──────────┴───┴───┴───┴───┴───┘ Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, }, { - name: "apiserver, only infra and severities", + name: "apiserver, only infra and serverities", report: report.Report{ ClusterName: "test", Resources: []report.Resource{apiseverPodWithMisconfigAndInfra}, }, scanners: types.Scanners{types.MisconfigScanner}, - components: []string{infraComponent}, severities: allSeverities, expectedOutput: `Summary Report for test ======================= +Workload Assessment +┌───────────┬──────────┬───────────────────┐ +│ Namespace │ Resource │ Misconfigurations │ +│ │ ├───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ +└───────────┴──────────┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN + + Infra Assessment ┌─────────────┬────────────────────┬───────────────────┐ │ Namespace │ Resource │ Misconfigurations │ @@ -316,7 +344,7 @@ Infra Assessment Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, }, { - name: "apiserver, vuln,config,secret and severities", + name: "apiserver, vuln,config,secret and serverities", report: report.Report{ ClusterName: "test", Resources: []report.Resource{apiseverPodWithMisconfigAndInfra}, @@ -326,11 +354,19 @@ Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, types.MisconfigScanner, types.SecretScanner, }, - components: []string{infraComponent}, severities: allSeverities, expectedOutput: `Summary Report for test ======================= +Workload Assessment +┌───────────┬──────────┬───────────────────┬───────────────────┬───────────────────┐ +│ Namespace │ Resource │ Vulnerabilities │ Misconfigurations │ Secrets │ +│ │ ├───┬───┬───┬───┬───┼───┬───┬───┬───┬───┼───┬───┬───┬───┬───┤ +│ │ │ C │ H │ M │ L │ U │ C │ H │ M │ L │ U │ C │ H │ M │ L │ U │ +└───────────┴──────────┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ +Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN + + Infra Assessment ┌─────────────┬────────────────────┬───────────────────┬───────────────────┬───────────────────┐ │ Namespace │ Resource │ Vulnerabilities │ Misconfigurations │ Secrets │ @@ -342,7 +378,7 @@ Infra Assessment Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, }, { - name: "apiserver, all misconfig and vuln scanners and severities", + name: "apiserver, all misconfig and vuln scanners and serverities", report: report.Report{ ClusterName: "test", Resources: []report.Resource{apiseverPodWithMisconfigAndInfra}, @@ -351,10 +387,6 @@ Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, types.MisconfigScanner, types.VulnerabilityScanner, }, - components: []string{ - workloadComponent, - infraComponent, - }, severities: allSeverities, expectedOutput: `Summary Report for test ======================= @@ -390,7 +422,6 @@ Severities: C=CRITICAL H=HIGH M=MEDIUM L=LOW U=UNKNOWN`, Output: &output, Scanners: tc.scanners, Severities: tc.severities, - Components: tc.components, } err := Write(context.Background(), tc.report, opt) From e739ab85063c82a817cdf33130d7dd1ca9ddb65a Mon Sep 17 00:00:00 2001 From: chenk Date: Thu, 2 May 2024 14:49:39 +0300 Subject: [PATCH 030/352] feat: support `--skip-images` scanning flag (#6334) Signed-off-by: chenk --- .../references/configuration/cli/trivy_kubernetes.md | 1 + pkg/flag/kubernetes_flags.go | 10 ++++++++++ pkg/k8s/scanner/scanner.go | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 4c3aaf5bb144..bcb1729fc91b 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -96,6 +96,7 @@ trivy kubernetes [flags] [CONTEXT] --skip-db-update skip updating vulnerability database --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip + --skip-images skip the downloading and scanning of images (vulnerabilities and secrets) in the cluster resources --skip-java-db-update skip updating Java index database --skip-policy-update skip fetching rego policy updates -t, --template string output template diff --git a/pkg/flag/kubernetes_flags.go b/pkg/flag/kubernetes_flags.go index 996317e0be4f..b88d14cb8b9e 100644 --- a/pkg/flag/kubernetes_flags.go +++ b/pkg/flag/kubernetes_flags.go @@ -47,6 +47,11 @@ var ( ConfigName: "kubernetes.exclude.owned", Usage: "exclude resources that have an owner reference", } + SkipImages = Flag[bool]{ + Name: "skip-images", + ConfigName: "kubernetes.skipImages", + Usage: "skip the downloading and scanning of images (vulnerabilities and secrets) in the cluster resources", + } ExcludeNodes = Flag[[]string]{ Name: "exclude-nodes", ConfigName: "kubernetes.exclude.nodes", @@ -95,6 +100,7 @@ type K8sFlagGroup struct { NodeCollectorImageRef *Flag[string] NodeCollectorNamespace *Flag[string] ExcludeOwned *Flag[bool] + SkipImages *Flag[bool] ExcludeNodes *Flag[[]string] ExcludeKinds *Flag[[]string] IncludeKinds *Flag[[]string] @@ -118,6 +124,7 @@ type K8sOptions struct { ExcludeNamespaces []string IncludeNamespaces []string QPS float32 + SkipImages bool Burst int } @@ -136,6 +143,7 @@ func NewK8sFlagGroup() *K8sFlagGroup { IncludeNamespaces: IncludeNamespaces.Clone(), NodeCollectorImageRef: NodeCollectorImageRef.Clone(), QPS: QPS.Clone(), + SkipImages: SkipImages.Clone(), Burst: Burst.Clone(), } } @@ -159,6 +167,7 @@ func (f *K8sFlagGroup) Flags() []Flagger { f.ExcludeNamespaces, f.IncludeNamespaces, f.QPS, + f.SkipImages, f.Burst, } } @@ -199,6 +208,7 @@ func (f *K8sFlagGroup) ToOptions() (K8sOptions, error) { ExcludeNodes: exludeNodeLabels, NodeCollectorImageRef: f.NodeCollectorImageRef.Value(), QPS: float32(f.QPS.Value()), + SkipImages: f.SkipImages.Value(), ExcludeKinds: f.ExcludeKinds.Value(), IncludeKinds: f.IncludeKinds.Value(), ExcludeNamespaces: f.ExcludeNamespaces.Value(), diff --git a/pkg/k8s/scanner/scanner.go b/pkg/k8s/scanner/scanner.go index 68698ea1d3d2..b0c055e0b886 100644 --- a/pkg/k8s/scanner/scanner.go +++ b/pkg/k8s/scanner/scanner.go @@ -89,7 +89,7 @@ func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact) onItem := func(ctx context.Context, artifact *artifacts.Artifact) (scanResult, error) { scanResults := scanResult{} - if s.opts.Scanners.AnyEnabled(types.VulnerabilityScanner, types.SecretScanner) { + if s.opts.Scanners.AnyEnabled(types.VulnerabilityScanner, types.SecretScanner) && !s.opts.SkipImages { opts := s.opts opts.Credentials = make([]ftypes.Credential, len(s.opts.Credentials)) copy(opts.Credentials, s.opts.Credentials) From a2a02de7c5a3b29d0354e076c39f92ce25e56395 Mon Sep 17 00:00:00 2001 From: chenk Date: Thu, 2 May 2024 15:59:22 +0300 Subject: [PATCH 031/352] docs: update trivy k8s with new experience (#6465) Signed-off-by: chenk --- docs/docs/target/kubernetes.md | 146 ++++++++++++------ docs/imgs/trivy-k8s.png | Bin 406077 -> 338450 bytes docs/tutorials/kubernetes/cluster-scanning.md | 45 ++---- 3 files changed, 115 insertions(+), 76 deletions(-) diff --git a/docs/docs/target/kubernetes.md b/docs/docs/target/kubernetes.md index f7dc440f05f4..7a6183f51733 100644 --- a/docs/docs/target/kubernetes.md +++ b/docs/docs/target/kubernetes.md @@ -9,7 +9,7 @@ Trivy can also be installed *inside* your cluster as a Kubernetes Operator, and When scanning a Kubernetes cluster, Trivy differentiates between the following: 1. Cluster infrastructure (e.g api-server, kubelet, addons) -1. Cluster configuration (e.g Roles, ClusterRoles). +1. Cluster configuration (e.g Roles, ClusterRoles). 1. Application workloads (e.g nginx, postgresql). When scanning any of the above, the container image is scanned separately to the Kubernetes resource definition (the YAML manifest) that defines the resource. @@ -30,58 +30,79 @@ Kubernetes resource definition is scanned for: Trivy follows the behavior of the `kubectl` tool as much as possible. -### Scope - -The command expects an argument that selects the scope of the scan (similarly to how `kubectl` expects an argument after `kubectl get`). This argument can be: -1. A Kubernetes Kind. e.g `pod`, `deployment`, etc. -2. A Kubernetes Resource. e.g `pods/mypod`, etc. -3. `all`. Scan common workload kinds, as listed [here](https://github.com/aquasecurity/trivy-kubernetes/blob/bf8cc2a00d9772e0aa271f06d375b936152b54b1/pkg/k8s/k8s.go#L296:L314) -4. `cluster` scan the entire cluster including all namespaced resources and cluster level resources. - -Examples: - -``` -trivy k8s all -trivy k8s pods -trivy k8s deploy myapp -trivy k8s pod/mypod -trivy k8s pods,deploy -trivy k8s cluster +```sh +trivy k8s [flags] [CONTEXT] - if the target name [CONTEXT] is not specified, the default will be used. ``` -Note that the scope argument must appear last in the command line, after any other flag. +for example: -### Cluster +```sh +trivy k8s --report summary +``` By default Trivy will look for a [`kubeconfig` configuration file in the default location](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/), and use the default cluster that is specified. You can also specify a `kubeconfig` using the `--kubeconfig` flag: -``` +```sh trivy k8s --kubeconfig ~/.kube/config2 ``` -### Namespace +By default, all cluster resource images will be downloaded and scanned. -By default Trivy will scan all namespaces (following `kubectl` behavior). To specify a namespace use the `--namespace` flag: +### Skip-images +You can control whether Trivy will scan and download the cluster resource images. To disable this feature, add the --skip-images flag. + +- `--skip-images` flag will prevent the downloading and scanning of images (including vulnerabilities and secrets) in the cluster resources. + +Example: + +```sh +trivy k8s --report summary --skip-images ``` -trivy k8s --kubeconfig ~/.kube/config2 --namespace default -``` -### Node -You can exclude specific nodes from the scan using the `--exclude-nodes` flag, which takes a label in the format `label-name:label-value` and excludes all matching nodes: +### Include/Exclude Kinds + +You can control which kinds of resources will be discovered using the `--include-kinds` or `--exclude-kinds` comma-separated flags: + +***Note:*** Both flags (`--include-kinds` or `--exclude-kinds`) cannot be set in conjunction. + +- `--include-kinds` will include the listed kinds in cluster scanning. +- `--exclude-kinds` will exclude the listed kinds from cluster scanning. + +By default, all kinds will be included in cluster scanning. + +Example: +```sh +trivy k8s --report summary --exclude-kinds node,pod ``` -trivy k8s cluster --report summary --exclude-nodes kubernetes.io/arch:arm6 + +### Include/Exclude Namespaces + +You can control which namespaces will be discovered using the `--include-namespaces` or `--exclude-namespaces` comma-separated flags: + +***Note:*** Both flags (`--include-namespaces` or `--exclude-namespaces`) cannot be set in conjunction. + +- `--include-namespaces` will include the listed namespaces in cluster scanning. +- `--exclude-namespaces` will exclude the listed namespaces from cluster scanning. + +By default, all namespaces will be included in cluster scanning. + +Example: + +```sh +trivy k8s --report summary --exclude-namespace dev-system,staging-system ``` ## Control Plane and Node Components Vulnerability Scanning -Trivy is capable of discovering Kubernetes control plane (apiserver, controller-manager and etc) and node components(kubelet, kube-proxy and etc), matching them against the [official Kubernetes vulnerability database feed](https://github.com/aquasecurity/vuln-list-k8s), and reporting any vulnerabilities it finds +Trivy is capable of discovering Kubernetes control plane (apiserver, controller-manager and etc) and node components(kubelet, kube-proxy and etc), matching them against the [official Kubernetes vulnerability database feed](https://github.com/aquasecurity/vuln-list-k8s), and reporting any vulnerabilities it finds. +To read more about KBOM, see the [documentation for Kubernetes scanning](./sbom.md#kbom). -``` -trivy k8s cluster --scanners vuln --report all +```sh +trivy k8s --scanners vuln --report all NodeComponents/kind-control-plane (kubernetes) @@ -101,13 +122,43 @@ Total: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 2, CRITICAL: 0) └────────────────┴────────────────┴──────────┴────────┴───────────────────┴──────────────────────────────────┴───────────────────────────────────────────────────┘ ``` +## Node-Collector + +Node-collector is a scan job that collects node configuration parameters and permission information. This information will be evaluated against Kubernetes hardening (e.g. CIS benchmark) and best practices values. The scan results will be output in infrastructure assessment and CIS benchmark compliance reports. + +### Disable Node Collector + +You can control whether the node scan-job (`node-collector`) will run in the cluster. To disable it, add the `--disable-node-collector` flag + +- `--disable-node-collector` This flag will exclude findings related to Node (infra assessment) misconfigurations + +By default, the node scan-job (`node-collector`) will run in the cluster. + +Example: + +```sh +trivy k8s --report summary --disable-node-collector +``` + +### Taints and Tolerations -### Components types +The node-collector scan-job will run on every node. In case the node has been tainted, it is possible to add toleration to the scan job for it to be scheduled on the tainted node. for more details [see k8s docs](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) -You can control what kinds of components are discovered using the `--components` flag: -- `--components infra` will discover only cluster infrastructure components. -- `--components workload` will discover only application workloads. -- If the flag is omitted: infra, workloads, and RBAC are discovered. +- `--tolerations key1=value1:NoExecute,key2=value2:NoSchedule` this flag wil enable node-collector to be schedule on tainted Node + +Example: + +```sh +trivy k8s --report summary --tolerations key1=value1:NoExecute,key2=value2:NoSchedule +``` + +### Exclude Nodes by Label + +You can exclude specific nodes from the scan using the `--exclude-nodes` flag, which takes a label in the format `label-name:label-value` and excludes all matching nodes: + +```sh +trivy k8s --report summary --exclude-nodes kubernetes.io/arch:arm6 +``` ## Reporting and filtering @@ -117,8 +168,8 @@ You can always choose the report granularity using the `--report summary`/`--rep Scan a full cluster and generate a simple summary report: -``` -$ trivy k8s --report=summary cluster +```sh +trivy k8s --report=summary ``` ![k8s Summary Report](../../imgs/trivy-k8s.png) @@ -126,15 +177,15 @@ $ trivy k8s --report=summary cluster Filter by severity: ``` -trivy k8s --severity=CRITICAL --report=all cluster +trivy k8s --severity=CRITICAL --report=all ``` Filter by scanners (Vulnerabilities, Secrets or Misconfigurations): ``` -trivy k8s --scanners=secret --report=summary cluster +trivy k8s --scanners=secret --report=summary # or -trivy k8s --scanners=misconfig --report=summary cluster +trivy k8s --scanners=misconfig --report=summary ``` The supported output formats are `table`, which is the default, and `json`. @@ -300,6 +351,7 @@ trivy k8s --format json -o results.json cluster ## Compliance + This section describes Kubernetes specific compliance reports. For an overview of Trivy's Compliance feature, including working with custom compliance, check out the [Compliance documentation](../compliance/compliance.md). @@ -318,7 +370,7 @@ Scan the cluster for Kubernetes Pod Security Standards Baseline compliance: ``` -$ trivy k8s cluster --compliance=k8s-pss-baseline --report summary +trivy k8s --compliance=k8s-pss-baseline --report summary ``` @@ -326,7 +378,7 @@ Get the detailed report for checks: ``` -$ trivy k8s cluster --compliance=k8s-cis --report all +trivy k8s --compliance=k8s-cis --report all ``` @@ -334,7 +386,7 @@ Get summary report in JSON format: ``` -$ trivy k8s cluster --compliance=k8s-cis --report summary --format json +trivy k8s --compliance=k8s-cis --report summary --format json ``` @@ -342,7 +394,7 @@ Get detailed report in JSON format: ``` -$ trivy k8s cluster --compliance=k8s-cis --report all --format json +trivy k8s --compliance=k8s-cis --report all --format json ``` @@ -355,7 +407,7 @@ Trivy can generate KBOM in CycloneDX format: ```sh -$ trivy k8s cluster --format cyclonedx --output mykbom.cdx.json +trivy k8s --format cyclonedx --output mykbom.cdx.json ``` @@ -363,7 +415,7 @@ Trivy can also scan that generated KBOM (or any SBOM) for vulnerabilities: ```sh -$ trivy sbom mykbom.cdx.json +trivy sbom mykbom.cdx.json ``` diff --git a/docs/imgs/trivy-k8s.png b/docs/imgs/trivy-k8s.png index e959d50c7039070d109c1663d6f4fac499db633d..d9060bdda1b97a73b4b6ea9ea18fb22497df3b78 100644 GIT binary patch literal 338450 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

{M2AEd0nS1{32 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 j&_#D^EvkpFN6xR#ZoxD*Q- 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

XnXE~p7XKEcU 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%0AS~na^wrB5Fl44qJ~nEG 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�=VVhxvxTJjHihY;az% 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

f^7|Qirgvg0q7rv| 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%0A>P`WhfnB*}MK*gBb;LQ)YB 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

{0{-&XYJf$1F+Zm1IJptJL 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 diff --git a/docs/tutorials/kubernetes/cluster-scanning.md b/docs/tutorials/kubernetes/cluster-scanning.md index a0d097cd78a4..4cd2de6ee694 100644 --- a/docs/tutorials/kubernetes/cluster-scanning.md +++ b/docs/tutorials/kubernetes/cluster-scanning.md @@ -1,58 +1,51 @@ # Kubernetes Scanning Tutorial -## Prerequisites +## Prerequisites To test the following commands yourself, make sure that you’re connected to a Kubernetes cluster. A simple kind, a Docker-Desktop or microk8s cluster will do. In our case, we’ll use a one-node kind cluster. - -Pro tip: The output of the commands will be even more interesting if you have some workloads running in your cluster. +Pro tip: The output of the commands will be even more interesting if you have some workloads running in your cluster. ## Cluster Scanning Trivy K8s is great to get an overview of all the vulnerabilities and misconfiguration issues or to scan specific workloads that are running in your cluster. You would want to use the Trivy K8s command either on your own local cluster or in your CI/CD pipeline post deployments. -The `trivy k8s` command is part of the Trivy CLI. +The `trivy k8s` command is part of the Trivy CLI. -With the following command, we can scan our entire Kubernetes cluster for vulnerabilities and get a summary of the scan: +With the following command, we can scan our entire Kubernetes cluster for vulnerabilities and get a summary of the scan: -``` -trivy k8s --report=summary cluster +```sh +trivy k8s --report=summary ``` To get detailed information for all your resources, just replace ‘summary’ with ‘all’: -``` -trivy k8s --report=all cluster +```sh +trivy k8s --report=all ``` However, we recommend displaying all information only in case you scan a specific namespace or resource since you can get overwhelmed with additional details. Furthermore, we can specify the namespace that Trivy is supposed to scan to focus on specific resources in the scan result: -``` -trivy k8s -n kube-system --report=summary cluster +```sh +trivy k8s --include-namespaces kube-system --report summary ``` Again, if you’d like to receive additional details, use the ‘--report=all’ flag: -``` -trivy k8s -n kube-system --report=all cluster +```sh +trivy k8s --include-namespaces kube-system --report all ``` Like with scanning for vulnerabilities, we can also filter in-cluster security issues by severity of the vulnerabilities: -``` -trivy k8s --severity=CRITICAL --report=summary cluster +```sh +trivy k8s --severity=CRITICAL --report=summary ``` Note that you can use any of the Trivy flags on the Trivy K8s command. -With the Trivy K8s command, you can also scan specific workloads that are running within your cluster, such as our deployment: - -``` -trivy k8s --namespace app --report=summary deployments/react-application -``` - -## Trivy Operator +## Trivy Operator The Trivy K8s command is an imperative model to scan resources. We wouldn’t want to manually scan each resource across different environments. The larger the cluster and the more workloads are running in it, the more error-prone this process would become. With the Trivy Operator, we can automate the scanning process after the deployment. @@ -66,15 +59,9 @@ This has several benefits: - The CRDs can be both machine and human-readable depending on which applications consume the CRDs. This allows for more versatile applications of the Trivy operator. - There are several ways that you can install the Trivy Operator in your cluster. In this guide, we’re going to use the Helm installation based on the [following documentation.](../../docs/target/kubernetes.md#trivy-operator) Please follow the Trivy Operator documentation for further information on: - [Installation of the Trivy Operator](https://aquasecurity.github.io/trivy-operator/latest/getting-started/installation/) -- [Getting started guide](https://aquasecurity.github.io/trivy-operator/latest/getting-started/quick-start/) - - - - - +- [Getting started guide](https://aquasecurity.github.io/trivy-operator/latest/getting-started/quick-start/) \ No newline at end of file From 6a2225b42571dc7ec2ac4628eced6a8d0dcc7959 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 2 May 2024 19:34:41 +0600 Subject: [PATCH 032/352] docs: use `generic` link from `trivy-repo` (#6606) --- docs/getting-started/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 92daf75e36c4..51ea9e207214 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -34,9 +34,9 @@ In this section you will find an aggregation of the different ways to install Tr Add repository setting to `/etc/apt/sources.list.d`. ``` bash - sudo apt-get install wget apt-transport-https gnupg lsb-release + sudo apt-get install wget apt-transport-https gnupg wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null - echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list + echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | sudo tee -a /etc/apt/sources.list.d/trivy.list sudo apt-get update sudo apt-get install trivy ``` From cdee7030ac43ce6ad727c00b178b38bf5670899a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 17:34:52 +0400 Subject: [PATCH 033/352] chore(deps): bump github.com/aws/aws-sdk-go-v2/feature/s3/manager from 1.15.15 to 1.16.15 (#6593) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 40012b392c2e..58a09356b4a1 100644 --- a/go.mod +++ b/go.mod @@ -29,9 +29,9 @@ require ( github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb github.com/aquasecurity/trivy-policies v0.10.0 github.com/aws/aws-sdk-go-v2 v1.26.1 - github.com/aws/aws-sdk-go-v2/config v1.27.10 - github.com/aws/aws-sdk-go-v2/credentials v1.17.10 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.9 + github.com/aws/aws-sdk-go-v2/config v1.27.11 + github.com/aws/aws-sdk-go-v2/credentials v1.17.11 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15 github.com/aws/aws-sdk-go-v2/service/ec2 v1.155.1 github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4 github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 @@ -220,7 +220,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/service/sns v1.26.6 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1 // indirect github.com/beorn7/perks v1.0.1 // indirect diff --git a/go.sum b/go.sum index 6858e5ccce0b..27b212d83350 100644 --- a/go.sum +++ b/go.sum @@ -374,14 +374,14 @@ github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+ github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= -github.com/aws/aws-sdk-go-v2/config v1.27.10 h1:PS+65jThT0T/snC5WjyfHHyUgG+eBoupSDV+f838cro= -github.com/aws/aws-sdk-go-v2/config v1.27.10/go.mod h1:BePM7Vo4OBpHreKRUMuDXX+/+JWP38FLkzl5m27/Jjs= -github.com/aws/aws-sdk-go-v2/credentials v1.17.10 h1:qDZ3EA2lv1KangvQB6y258OssCHD0xvaGiEDkG4X/10= -github.com/aws/aws-sdk-go-v2/credentials v1.17.10/go.mod h1:6t3sucOaYDwDssHQa0ojH1RpmVmF5/jArkye1b2FKMI= +github.com/aws/aws-sdk-go-v2/config v1.27.11 h1:f47rANd2LQEYHda2ddSCKYId18/8BhSRM4BULGmfgNA= +github.com/aws/aws-sdk-go-v2/config v1.27.11/go.mod h1:SMsV78RIOYdve1vf36z8LmnszlRWkwMQtomCAI0/mIE= +github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.9 h1:vXY/Hq1XdxHBIYgBUmug/AbMyIe1AKulPYS2/VE1X70= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.9/go.mod h1:GyJJTZoHVuENM4TeJEl5Ffs4W9m19u+4wKJcDi/GZ4A= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15 h1:7Zwtt/lP3KNRkeZre7soMELMGNoBrutx8nobg1jKWmo= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15/go.mod h1:436h2adoHb57yd+8W+gYPrrA9U/R/SuAuOO42Ushzhw= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= @@ -468,8 +468,8 @@ github.com/aws/aws-sdk-go-v2/service/sns v1.26.6 h1:w2YwF8889ardGU3Y0qZbJ4Zzh+Q/ github.com/aws/aws-sdk-go-v2/service/sns v1.26.6/go.mod h1:IrcbquqMupzndZ20BXxDxjM7XenTRhbwBOetk4+Z5oc= github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6 h1:UdbDTllc7cmusTTMy1dcTrYKRl4utDEsmKh9ZjvhJCc= github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6/go.mod h1:mCUv04gd/7g+/HNzDB4X6dzJuygji0ckvB3Lg/TdG5Y= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.4 h1:WzFol5Cd+yDxPAdnzTA5LmpHYSWinhmSj4rQChV0ee8= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.4/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 h1:vN8hEbpRnL7+Hopy9dzmRle1xmDc7o8tmY0klsr175w= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.5/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 h1:Jux+gDDyi1Lruk+KHF91tK2KCuY61kzoCpvtvJJBtOE= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n2HZPkcKgPAi1phU= From 37da98df45f6014fcd5f1744e2e26351b61d2a02 Mon Sep 17 00:00:00 2001 From: simar7 <1254783+simar7@users.noreply.github.com> Date: Thu, 2 May 2024 12:16:17 -0600 Subject: [PATCH 034/352] feat(misconf): Use updated terminology for misconfiguration checks (#6476) Signed-off-by: Simar --- docs/docs/configuration/cache.md | 2 +- docs/docs/coverage/iac/helm.md | 2 +- .../references/configuration/cli/trivy_aws.md | 12 +- .../configuration/cli/trivy_config.md | 12 +- .../configuration/cli/trivy_filesystem.md | 12 +- .../configuration/cli/trivy_image.md | 12 +- .../configuration/cli/trivy_kubernetes.md | 12 +- .../configuration/cli/trivy_repository.md | 12 +- .../configuration/cli/trivy_rootfs.md | 12 +- .../references/configuration/cli/trivy_vm.md | 4 +- .../scanner/misconfiguration/check/builtin.md | 21 + .../{policy => check}/exceptions.md | 14 +- .../misconfiguration/custom/testing.md | 4 +- .../misconfiguration/policy/builtin.md | 24 - .../misconfiguration/custom-checks.md | 2 +- go.mod | 10 +- go.sum | 675 +++++++++++++++++- integration/aws_cloud_test.go | 4 +- magefiles/magefile.go | 3 + mkdocs.yml | 4 +- pkg/cloud/aws/commands/run.go | 5 + pkg/cloud/aws/commands/run_test.go | 176 ++--- pkg/cloud/aws/scanner/scanner.go | 13 +- pkg/commands/app.go | 4 +- pkg/commands/app_test.go | 6 +- pkg/commands/artifact/run.go | 17 +- pkg/commands/operation/operation.go | 10 +- pkg/compliance/spec/compliance.go | 2 +- .../imgconf/dockerfile/dockerfile_test.go | 2 +- pkg/fanal/cache/key.go | 2 +- pkg/flag/misconf_flags.go | 50 +- pkg/flag/rego_flags.go | 78 +- pkg/iac/rego/embed.go | 2 +- pkg/iac/rego/embed_test.go | 4 +- pkg/iac/rego/load.go | 4 +- pkg/iac/rego/load_test.go | 4 +- pkg/iac/rego/result.go | 4 +- pkg/iac/rego/result_test.go | 2 +- pkg/iac/rego/scanner.go | 4 +- pkg/iac/rego/schemas/dockerfile.json | 2 +- pkg/iac/rego/schemas/kubernetes.json | 2 +- pkg/iac/rego/schemas/rbac.json | 2 +- pkg/iac/rules/register.go | 2 +- pkg/iac/rules/rules.go | 144 ++-- pkg/iac/scanners/terraform/module_test.go | 2 +- pkg/misconf/scanner.go | 2 +- pkg/policy/policy.go | 54 +- pkg/policy/policy_test.go | 2 +- pkg/result/filter_test.go | 4 +- pkg/rpc/server/listen_test.go | 2 +- pkg/version/version.go | 8 +- pkg/version/version_test.go | 4 +- 52 files changed, 1074 insertions(+), 398 deletions(-) create mode 100644 docs/docs/scanner/misconfiguration/check/builtin.md rename docs/docs/scanner/misconfiguration/{policy => check}/exceptions.md (79%) delete mode 100644 docs/docs/scanner/misconfiguration/policy/builtin.md diff --git a/docs/docs/configuration/cache.md b/docs/docs/configuration/cache.md index 3fd74a393892..d8149f16ccab 100644 --- a/docs/docs/configuration/cache.md +++ b/docs/docs/configuration/cache.md @@ -70,7 +70,7 @@ $ trivy server --cache-backend redis://localhost:6379 \ [trivy-db]: ./db.md#vulnerability-database [trivy-java-db]: ./db.md#java-index-database -[misconf-policies]: ../scanner/misconfiguration/policy/builtin.md +[misconf-policies]: ../scanner/misconfiguration/check/builtin.md [^1]: Downloaded when scanning for vulnerabilities [^2]: Downloaded when scanning `jar/war/par/ear` files diff --git a/docs/docs/coverage/iac/helm.md b/docs/docs/coverage/iac/helm.md index 4f9f87de8608..cc8ddc0656a4 100644 --- a/docs/docs/coverage/iac/helm.md +++ b/docs/docs/coverage/iac/helm.md @@ -11,7 +11,7 @@ The following scanners are supported. Trivy recursively searches directories and scans all found Helm files. It evaluates variables, functions, and other elements within Helm templates and resolve the chart to Kubernetes manifests then run the Kubernetes checks. -See [here](../../scanner/misconfiguration/policy/builtin.md) for more details on the built-in policies. +See [here](../../scanner/misconfiguration/check/builtin.md) for more details on the built-in policies. ### Value overrides There are a number of options for overriding values in Helm charts. diff --git a/docs/docs/references/configuration/cli/trivy_aws.md b/docs/docs/references/configuration/cli/trivy_aws.md index 0997f062b55e..44774dffcd16 100644 --- a/docs/docs/references/configuration/cli/trivy_aws.md +++ b/docs/docs/references/configuration/cli/trivy_aws.md @@ -69,9 +69,11 @@ trivy aws [flags] --account string The AWS account to scan. It's useful to specify this when reviewing cached results for multiple accounts. --arn string The AWS ARN to show results for. Useful to filter results once a scan is cached. --cf-params strings specify paths to override the CloudFormation parameters files + --check-namespaces strings Rego namespaces + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") --compliance string compliance report to generate (aws-cis-1.2,aws-cis-1.4) - --config-data strings specify paths from which data for the Rego policies will be recursively loaded - --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files + --config-data strings specify paths from which data for the Rego checks will be recursively loaded --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages --endpoint string AWS Endpoint override --exit-code int specify exit code when any security issues are found @@ -91,14 +93,12 @@ trivy aws [flags] --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) -o, --output string output file name --output-plugin-arg string [EXPERIMENTAL] output plugin arguments - --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") - --policy-namespaces strings Rego namespaces --region string AWS Region to scan --report string specify a report format for the output (all,summary) (default "all") - --reset-policy-bundle remove policy bundle + --reset-checks-bundle remove checks bundle --service strings Only scan AWS Service(s) specified with this flag. Can specify multiple services using --service A --service B etc. -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) - --skip-policy-update skip fetching rego policy updates + --skip-check-update skip fetching rego check updates --skip-service strings Skip selected AWS Service(s) specified with this flag. Can specify multiple services using --skip-service A --skip-service B etc. -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 070257b4a896..73bf450244e0 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -12,10 +12,12 @@ trivy config [flags] DIR --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend --cf-params strings specify paths to override the CloudFormation parameters files + --check-namespaces strings Rego namespaces + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") --clear-cache clear image caches without scanning --compliance string compliance report to generate - --config-data strings specify paths from which data for the Rego policies will be recursively loaded - --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files + --config-data strings specify paths from which data for the Rego checks will be recursively loaded --enable-modules strings [EXPERIMENTAL] module names to enable --exit-code int specify exit code when any security issues are found --file-patterns strings specify config file patterns @@ -36,19 +38,17 @@ trivy config [flags] DIR -o, --output string output file name --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. - --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") - --policy-namespaces strings Rego namespaces --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend --redis-tls enable redis TLS with public certificates, if using redis as cache backend --registry-token string registry token --report string specify a compliance report format for the output (all,summary) (default "all") - --reset-policy-bundle remove policy bundle + --reset-checks-bundle remove checks bundle -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) + --skip-check-update skip fetching rego check updates --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip - --skip-policy-update skip fetching rego policy updates -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --tf-vars strings specify paths to override the Terraform tfvars files diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 7aeb72ca5970..79601ddc05e3 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -22,10 +22,12 @@ trivy filesystem [flags] PATH --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend --cf-params strings specify paths to override the CloudFormation parameters files + --check-namespaces strings Rego namespaces + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") --clear-cache clear image caches without scanning --compliance string compliance report to generate - --config-data strings specify paths from which data for the Rego policies will be recursively loaded - --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files + --config-data strings specify paths from which data for the Rego checks will be recursively loaded --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages @@ -61,8 +63,6 @@ trivy filesystem [flags] PATH --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. - --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") - --policy-namespaces strings Rego namespaces --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend @@ -71,18 +71,18 @@ trivy filesystem [flags] PATH --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --report string specify a compliance report format for the output (all,summary) (default "all") --reset remove all caches and database - --reset-policy-bundle remove policy bundle + --reset-checks-bundle remove checks bundle --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") --server string server address in client mode -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --skip-check-update skip fetching rego check updates --skip-db-update skip updating vulnerability database --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database - --skip-policy-update skip fetching rego policy updates -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --tf-vars strings specify paths to override the Terraform tfvars files diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index e5d91f31eb7c..ab7951fa8d5e 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -36,10 +36,12 @@ trivy image [flags] IMAGE_NAME ``` --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend + --check-namespaces strings Rego namespaces + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") --clear-cache clear image caches without scanning --compliance string compliance report to generate (docker-cis) - --config-data strings specify paths from which data for the Rego policies will be recursively loaded - --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files + --config-data strings specify paths from which data for the Rego checks will be recursively loaded --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages @@ -81,8 +83,6 @@ trivy image [flags] IMAGE_NAME --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. --platform string set platform in the form os/arch if image is multi-platform capable --podman-host string unix podman socket path to use for podman scanning - --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") - --policy-namespaces strings Rego namespaces --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend @@ -92,18 +92,18 @@ trivy image [flags] IMAGE_NAME --removed-pkgs detect vulnerabilities of removed packages (only for Alpine) --report string specify a format for the compliance report. (all,summary) (default "summary") --reset remove all caches and database - --reset-policy-bundle remove policy bundle + --reset-checks-bundle remove checks bundle --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") --server string server address in client mode -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --skip-check-update skip fetching rego check updates --skip-db-update skip updating vulnerability database --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database - --skip-policy-update skip fetching rego policy updates -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --token string for authentication in client/server mode diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index bcb1729fc91b..62ee6cd3b422 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -32,10 +32,12 @@ trivy kubernetes [flags] [CONTEXT] --burst int specify the maximum burst for throttle (default 10) --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend + --check-namespaces strings Rego namespaces + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") --clear-cache clear image caches without scanning --compliance string compliance report to generate (k8s-nsa,k8s-cis,k8s-pss-baseline,k8s-pss-restricted) - --config-data strings specify paths from which data for the Rego policies will be recursively loaded - --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files + --config-data strings specify paths from which data for the Rego checks will be recursively loaded --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages --disable-node-collector When the flag is activated, the node-collector job will not be executed, thus skipping misconfiguration findings on the node. @@ -76,8 +78,6 @@ trivy kubernetes [flags] [CONTEXT] --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. - --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") - --policy-namespaces strings Rego namespaces --qps float specify the maximum QPS to the master from this client (default 5) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend @@ -87,18 +87,18 @@ trivy kubernetes [flags] [CONTEXT] --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --report string specify a report format for the output (all,summary) (default "all") --reset remove all caches and database - --reset-policy-bundle remove policy bundle + --reset-checks-bundle remove checks bundle --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,rbac) (default [vuln,misconfig,secret,rbac]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --skip-check-update skip fetching rego check updates --skip-db-update skip updating vulnerability database --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip --skip-images skip the downloading and scanning of images (vulnerabilities and secrets) in the cluster resources --skip-java-db-update skip updating Java index database - --skip-policy-update skip fetching rego policy updates -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --tolerations strings specify node-collector job tolerations (example: key1=value1:NoExecute,key2=value2:NoSchedule) diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index fa4d13bbdeee..e3daa569d9f4 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -22,10 +22,12 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend --cf-params strings specify paths to override the CloudFormation parameters files + --check-namespaces strings Rego namespaces + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") --clear-cache clear image caches without scanning --commit string pass the commit hash to be scanned - --config-data strings specify paths from which data for the Rego policies will be recursively loaded - --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files + --config-data strings specify paths from which data for the Rego checks will be recursively loaded --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages @@ -61,8 +63,6 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. - --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") - --policy-namespaces strings Rego namespaces --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend @@ -70,18 +70,18 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --registry-token string registry token --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --reset remove all caches and database - --reset-policy-bundle remove policy bundle + --reset-checks-bundle remove checks bundle --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") --server string server address in client mode -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --skip-check-update skip fetching rego check updates --skip-db-update skip updating vulnerability database --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database - --skip-policy-update skip fetching rego policy updates --tag string pass the tag name to be scanned -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 088b5deb6d88..4bc3fc61d2af 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -25,9 +25,11 @@ trivy rootfs [flags] ROOTDIR --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend --cf-params strings specify paths to override the CloudFormation parameters files + --check-namespaces strings Rego namespaces + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") --clear-cache clear image caches without scanning - --config-data strings specify paths from which data for the Rego policies will be recursively loaded - --config-policy strings specify the paths to the Rego policy files or to the directories containing them, applying config files + --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files + --config-data strings specify paths from which data for the Rego checks will be recursively loaded --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages @@ -63,8 +65,6 @@ trivy rootfs [flags] ROOTDIR --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. - --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") - --policy-namespaces strings Rego namespaces --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend @@ -72,18 +72,18 @@ trivy rootfs [flags] ROOTDIR --registry-token string registry token --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --reset remove all caches and database - --reset-policy-bundle remove policy bundle + --reset-checks-bundle remove checks bundle --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") --server string server address in client mode -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --skip-check-update skip fetching rego check updates --skip-db-update skip updating vulnerability database --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database - --skip-policy-update skip fetching rego policy updates -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --tf-vars strings specify paths to override the Terraform tfvars files diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 7b186a505794..fc9b536a418d 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -23,6 +23,7 @@ trivy vm [flags] VM_IMAGE --aws-region string AWS region to scan --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend + --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") --clear-cache clear image caches without scanning --compliance string compliance report to generate --custom-headers strings custom headers in client mode @@ -56,14 +57,13 @@ trivy vm [flags] VM_IMAGE -o, --output string output file name --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) - --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend --redis-tls enable redis TLS with public certificates, if using redis as cache backend --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --reset remove all caches and database - --reset-policy-bundle remove policy bundle + --reset-checks-bundle remove checks bundle --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/docs/docs/scanner/misconfiguration/check/builtin.md b/docs/docs/scanner/misconfiguration/check/builtin.md new file mode 100644 index 000000000000..8b513f47607a --- /dev/null +++ b/docs/docs/scanner/misconfiguration/check/builtin.md @@ -0,0 +1,21 @@ +# Built-in Checks + +## Check Sources +Built-in checks are mainly written in [Rego][rego] and Go. +Those checks are managed under [trivy-checks repository][trivy-checks]. +See [here](../../../coverage/iac/index.md) for the list of supported config types. + +For suggestions or issues regarding policy content, please open an issue under the [trivy-checks][trivy-checks] repository. + +## Check Distribution +Trivy checks are distributed as an OPA bundle on [GitHub Container Registry][ghcr] (GHCR). +When misconfiguration detection is enabled, Trivy pulls the OPA bundle from GHCR as an OCI artifact and stores it in the cache. +Those checks are then loaded into Trivy OPA engine and used for detecting misconfigurations. +If Trivy is unable to pull down newer checks, it will use the embedded set of checks as a fallback. This is also the case in air-gap environments where `--skip-policy-update` might be passed. + +## Update Interval +Trivy checks for updates to OPA bundle on GHCR every 24 hours and pulls it if there are any updates. + +[rego]: https://www.openpolicyagent.org/docs/latest/policy-language/ +[trivy-checks]: https://github.com/aquasecurity/trivy-checks +[ghcr]: https://github.com/aquasecurity/trivy-checks/pkgs/container/trivy-checks \ No newline at end of file diff --git a/docs/docs/scanner/misconfiguration/policy/exceptions.md b/docs/docs/scanner/misconfiguration/check/exceptions.md similarity index 79% rename from docs/docs/scanner/misconfiguration/policy/exceptions.md rename to docs/docs/scanner/misconfiguration/check/exceptions.md index 9d0e109fcdd5..e4020c029908 100644 --- a/docs/docs/scanner/misconfiguration/policy/exceptions.md +++ b/docs/docs/scanner/misconfiguration/check/exceptions.md @@ -28,8 +28,6 @@ The `exception` rule must be defined under `namespace.exceptions`. This example exempts all built-in policies for Kubernetes. -For more details, see [an example][ns-example]. - ## Rule-based exceptions There are some cases where you need more flexibility and granularity in defining which cases to exempt. Rule-based exceptions lets you granularly choose which individual rules to exempt, while also declaring under which conditions to exempt them. @@ -87,12 +85,8 @@ If you want to apply rule-based exceptions to built-in policies, you have to def } ``` -This exception is applied to [KSV012][ksv012] in trivy-policies. -You can get the package names in the [trivy-policies repository][trivy-policies] or the JSON output from Trivy. - -For more details, see [an example][rule-example]. +This exception is applied to [KSV012][ksv012] in trivy-checks. +You can get the package names in the [trivy-checks repository][trivy-checks] or the JSON output from Trivy. -[ns-example]: https://github.com/aquasecurity/trivy/tree/{{ git.commit }}/examples/misconf/namespace-exception -[rule-example]: https://github.com/aquasecurity/trivy/tree/{{ git.commit }}/examples/misconf/rule-exception -[ksv012]: https://github.com/aquasecurity/trivy-policies/blob/main/rules/kubernetes/policies/pss/restricted/3_runs_as_root.rego -[trivy-policies]: https://github.com/aquasecurity/trivy-policies/ \ No newline at end of file +[ksv012]: https://github.com/aquasecurity/trivy-checks/blob/f36a5b732c4b1293a720c40baab0a7c106ea455e/checks/kubernetes/pss/restricted/3_runs_as_root.rego +[trivy-checks]: https://github.com/aquasecurity/trivy-checks/ \ No newline at end of file diff --git a/docs/docs/scanner/misconfiguration/custom/testing.md b/docs/docs/scanner/misconfiguration/custom/testing.md index e09df1a35bc0..fcda218ff8eb 100644 --- a/docs/docs/scanner/misconfiguration/custom/testing.md +++ b/docs/docs/scanner/misconfiguration/custom/testing.md @@ -22,7 +22,7 @@ For more details, see [Policy Testing][opa-testing]. } ``` -To write tests for custom policies, you can refer to existing tests under [trivy-policies][trivy-policies]. +To write tests for custom policies, you can refer to existing tests under [trivy-checks][trivy-checks]. ## Go testing [Fanal][fanal] which is a core library of Trivy can be imported as a Go library. @@ -85,6 +85,6 @@ The following example stores allowed and denied configuration files in a directo `Dockerfile.allowed` has one successful result in `Successes`, while `Dockerfile.denied` has one failure result in `Failures`. [opa-testing]: https://www.openpolicyagent.org/docs/latest/policy-testing/ -[defsec]: https://github.com/aquasecurity/trivy-policies/tree/main +[defsec]: https://github.com/aquasecurity/trivy-checks/tree/main [table]: https://github.com/golang/go/wiki/TableDrivenTests [fanal]: https://github.com/aquasecurity/fanal \ No newline at end of file diff --git a/docs/docs/scanner/misconfiguration/policy/builtin.md b/docs/docs/scanner/misconfiguration/policy/builtin.md deleted file mode 100644 index aeaa48afbb27..000000000000 --- a/docs/docs/scanner/misconfiguration/policy/builtin.md +++ /dev/null @@ -1,24 +0,0 @@ -# Built-in Policies - -## Policy Sources -Built-in policies are mainly written in [Rego][rego] and Go. -Those policies are managed under [trivy-policies repository][trivy-policies]. -See [here](../../../coverage/iac/index.md) for the list of supported config types. - -For suggestions or issues regarding policy content, please open an issue under the [trivy-policies][trivy-policies] repository. - -## Policy Distribution -Trivy policies are distributed as an OPA bundle on [GitHub Container Registry][ghcr] (GHCR). -When misconfiguration detection is enabled, Trivy pulls the OPA bundle from GHCR as an OCI artifact and stores it in the cache. -Those policies are then loaded into Trivy OPA engine and used for detecting misconfigurations. -If Trivy is unable to pull down newer policies, it will use the embedded set of policies as a fallback. This is also the case in air-gap environments where `--skip-policy-update` might be passed. - -## Update Interval -Trivy checks for updates to OPA bundle on GHCR every 24 hours and pulls it if there are any updates. - -[rego]: https://www.openpolicyagent.org/docs/latest/policy-language/ - -[kubernetes-policies]: https://github.com/aquasecurity/trivy-policies/tree/main/rules/kubernetes/policies -[docker-policies]: https://github.com/aquasecurity/trivy-policies/tree/main/rules/docker/policies -[trivy-policies]: https://github.com/aquasecurity/trivy-policies -[ghcr]: https://github.com/aquasecurity/trivy-policies/pkgs/container/trivy-policies \ No newline at end of file diff --git a/docs/tutorials/misconfiguration/custom-checks.md b/docs/tutorials/misconfiguration/custom-checks.md index f36f855185a5..97ab67a7f649 100644 --- a/docs/tutorials/misconfiguration/custom-checks.md +++ b/docs/tutorials/misconfiguration/custom-checks.md @@ -108,4 +108,4 @@ Please replace: * [Rego provides a long list of courses](https://academy.styra.com/collections) that can be useful in writing more complex checks * [The Rego documentation provides detailed information on the different types, iterations etc.](https://www.openpolicyagent.org/docs/latest/) -* Have a look at the [built-in checks](https://github.com/aquasecurity/trivy-policies/tree/main/checks) for Trivy for inspiration on how to write custom checks. \ No newline at end of file +* Have a look at the [built-in checks](https://github.com/aquasecurity/trivy-checks/tree/main/checks) for Trivy for inspiration on how to write custom checks. \ No newline at end of file diff --git a/go.mod b/go.mod index 58a09356b4a1..bf0df6324df8 100644 --- a/go.mod +++ b/go.mod @@ -21,13 +21,13 @@ require ( github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 github.com/aquasecurity/loading v0.0.5 github.com/aquasecurity/table v1.8.0 - github.com/aquasecurity/testdocker v0.0.0-20230111101738-e741bda259da + github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334 github.com/aquasecurity/tml v0.6.1 github.com/aquasecurity/trivy-aws v0.8.0 + github.com/aquasecurity/trivy-checks v0.10.5-0.20240430045208-6cc735de6b9e github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb - github.com/aquasecurity/trivy-policies v0.10.0 github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2/config v1.27.11 github.com/aws/aws-sdk-go-v2/credentials v1.17.11 @@ -42,7 +42,7 @@ require ( github.com/cheggaaa/pb/v3 v3.1.4 github.com/containerd/containerd v1.7.16 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 - github.com/docker/docker v25.0.5+incompatible + github.com/docker/docker v26.0.1+incompatible github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.16.0 github.com/go-git/go-git/v5 v5.11.0 @@ -242,7 +242,7 @@ require ( github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/distribution/reference v0.5.0 // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/docker/cli v25.0.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect @@ -324,6 +324,7 @@ require ( github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/spdystream v0.2.0 // indirect @@ -391,7 +392,6 @@ require ( go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/sdk v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect diff --git a/go.sum b/go.sum index 27b212d83350..9c2e8568c932 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,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.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.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 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.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -16,6 +17,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -27,28 +29,92 @@ cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y 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.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= 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.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -56,12 +122,44 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM 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.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= 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= @@ -69,128 +167,449 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU= cloud.google.com/go/compute v1.25.0/go.mod h1:GR7F0ZPZH8EhChlMo9FkLd7eUTwEymjqQagxzilIxIE= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= 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.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= 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.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA= @@ -247,6 +666,7 @@ github.com/Intevation/gval v1.3.0 h1:+Ze5sft5MmGbZrHj06NVUbcxCb67l9RaPTLMNr37mjw github.com/Intevation/gval v1.3.0/go.mod h1:xmGyGpP5be12EL0P12h+dqiYG8qn2j3PJxIgkoOHO5o= github.com/Intevation/jsonpath v0.2.1 h1:rINNQJ0Pts5XTFEG+zamtdL7l9uuE1z0FBA+r55Sw+A= github.com/Intevation/jsonpath v0.2.1/go.mod h1:WnZ8weMmwAx/fAO3SutjYFU+v7DFreNYnibV7CiaYIw= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -299,6 +719,10 @@ github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7l github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -312,6 +736,7 @@ github.com/alicebob/miniredis/v2 v2.31.1 h1:7XAt0uUg3DtwEKW5ZAGa+K7FZV2DdKQo5K/6 github.com/alicebob/miniredis/v2 v2.31.1/go.mod h1:UB/T2Uztp7MlFSDakaX1sTXUv5CASoprx0wulRT6HBg= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antchfx/htmlquery v1.3.0 h1:5I5yNFOVI+egyia5F2s/5Do2nFWxJz41Tr3DyfKD25E= @@ -319,6 +744,9 @@ github.com/antchfx/htmlquery v1.3.0/go.mod h1:zKPDVTMhfOmcwxheXUsx4rKJy8KEY/PU6e github.com/antchfx/xpath v1.2.3 h1:CCZWOzv5bAqjVv0offZ2LVgVYFbeldKQVuLNbViZdes= github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= @@ -341,20 +769,20 @@ github.com/aquasecurity/loading v0.0.5 h1:2iq02sPSSMU+ULFPmk0v0lXnK/eZ2e0dRAj/Dl github.com/aquasecurity/loading v0.0.5/go.mod h1:NSHeeq1JTDTFuXAe87q4yQ2DX57pXiaQMqq8Zm9HCJA= github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0= github.com/aquasecurity/table v1.8.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw= -github.com/aquasecurity/testdocker v0.0.0-20230111101738-e741bda259da h1:pj/adfN0Wbzc0H8YkI1nX5K92wOU5/1/1TRuuc0y5Nw= -github.com/aquasecurity/testdocker v0.0.0-20230111101738-e741bda259da/go.mod h1:852lbQLpK2nCwlR4ZLYIccxYCfoQao6q9Nl6tjz54v8= +github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334 h1:MgvbLyLBW8+uVD/Tv6uKw9ia8dfHynwVT/VKn5s5idI= +github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334/go.mod h1:TKXn7bPfMM52ETP4sjjwkTKCZ18CqCs+I/vtFePSdBc= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= github.com/aquasecurity/trivy-aws v0.8.0 h1:4ij8MiZ2sJUH+vWpSeoGVhPr109ZBcNp7LNLfPuv5Cw= github.com/aquasecurity/trivy-aws v0.8.0/go.mod h1:Pb9xqOuTKMHVgjsnjvudjqZh3nmzdFqFVfRkXnoIZBM= +github.com/aquasecurity/trivy-checks v0.10.5-0.20240430045208-6cc735de6b9e h1:s0P4VeCqb7tWw06/L1cZ5/42AWy6VZFuLZ96THPJmmM= +github.com/aquasecurity/trivy-checks v0.10.5-0.20240430045208-6cc735de6b9e/go.mod h1:UIFQxYlKcL7EGhNVicFmZ6XxZ2UpFZU7bNKEv/Y/6XM= github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTUAkbGqpzt9nJsO24RAdfG+ZSiLFj0G2jO8c= github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb h1:U07awOdXGT8NMwTPVuXkL/cKZyvO4PuG+VX1oIvsuiQ= github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb/go.mod h1:+NJBTgQErUmq21Ag71q/EuXZKIP+/OJvBAR0G+YUkKo= -github.com/aquasecurity/trivy-policies v0.10.0 h1:QONOsIFi6+WyB+7NGMBQeCgMFcRg6RV9dTBBpeOFDxU= -github.com/aquasecurity/trivy-policies v0.10.0/go.mod h1:7WU0GTUqtQxqQ+FV3JAy7lskQQZU6lp7Mz1i8GEapFw= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -495,6 +923,8 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= @@ -516,6 +946,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -541,11 +973,15 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= @@ -700,8 +1136,8 @@ github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+ github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= @@ -716,9 +1152,8 @@ github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v23.0.0-rc.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v26.0.1+incompatible h1:t39Hm6lpXuXtgkF0dm1t9a5HkbUfdGy6XbWexmGr+hA= +github.com/docker/docker v26.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= @@ -761,7 +1196,12 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -774,6 +1214,8 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= @@ -796,6 +1238,11 @@ github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= @@ -814,6 +1261,8 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -852,6 +1301,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= @@ -877,6 +1328,7 @@ github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XE github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y= github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0= github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= @@ -904,7 +1356,10 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -941,6 +1396,7 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -952,8 +1408,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= -github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -998,6 +1454,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -1023,6 +1480,8 @@ github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -1034,10 +1493,13 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +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/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= @@ -1064,6 +1526,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -1124,6 +1588,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -1161,21 +1626,27 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.0/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 h1:PPPlUUqPP6fLudIK4n0l0VU4KT2cQGnheW9x8pNiCHI= @@ -1194,6 +1665,7 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -1224,6 +1696,9 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -1277,6 +1752,7 @@ github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vq github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -1287,6 +1763,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -1315,6 +1793,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/buildkit v0.12.5 h1:RNHH1l3HDhYyZafr5EgstEu8aGNCwyfvMtrQDtjH9T0= github.com/moby/buildkit v0.12.5/go.mod h1:YGwjA2loqyiYfZeEo8FtI7z4x5XponAaIWsWcSjWwso= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= @@ -1391,6 +1871,7 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -1431,16 +1912,22 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 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.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -1463,6 +1950,7 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= @@ -1491,6 +1979,7 @@ github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= @@ -1498,6 +1987,8 @@ 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 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.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= @@ -1505,6 +1996,8 @@ github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWx 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/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= 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/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= @@ -1564,7 +2057,9 @@ github.com/spdx/tools-golang v0.5.4-0.20231108154018-0c0f394b5e1a h1:uuREJ3I15VL github.com/spdx/tools-golang v0.5.4-0.20231108154018-0c0f394b5e1a/go.mod h1:BHs8QEhK6MbFGdyjxvuBtzJtCLrN5bwUBC9fzQlYBXs= 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.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -1609,6 +2104,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -1680,6 +2176,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= @@ -1696,6 +2193,8 @@ github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8 github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-yaml v1.0.3 h1:og/eOQ7lvA/WWhHGFETVWNduJM7Rjsv2RRpx1sdFMLc= github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= @@ -1736,6 +2235,8 @@ go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35 go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= @@ -1768,8 +2269,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 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-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= @@ -1778,20 +2281,36 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1815,8 +2334,12 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= @@ -1871,6 +2394,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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= @@ -1882,11 +2407,16 @@ golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= @@ -1916,8 +2446,13 @@ golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7Lm golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1933,6 +2468,7 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= @@ -2015,6 +2551,8 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2022,6 +2560,7 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2029,8 +2568,11 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2050,12 +2592,15 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2068,8 +2613,11 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= @@ -2086,8 +2634,10 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= @@ -2097,12 +2647,17 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -2118,6 +2673,7 @@ golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -2148,18 +2704,23 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= @@ -2174,6 +2735,14 @@ golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNq golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= 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.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -2223,7 +2792,16 @@ google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaE google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= google.golang.org/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk= google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -2274,7 +2852,9 @@ google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -2307,6 +2887,7 @@ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2 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-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= 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= @@ -2339,7 +2920,36 @@ google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53B google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= google.golang.org/genproto/googleapis/api v0.0.0-20240311173647-c811ad7063a7 h1:oqta3O3AnlWbmIE3bFnWbu4bRxZjfbWCp0cKSuZh01E= @@ -2376,6 +2986,7 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/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.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= @@ -2385,6 +2996,11 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= @@ -2403,6 +3019,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= @@ -2466,6 +3084,7 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= @@ -2513,35 +3132,69 @@ k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk= modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA= modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg= modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/sqlite v1.29.7 h1:Puwf5TIYuOipbcRnpFnLlGlR03DKenw8ggf3ijnuNQ0= modernc.org/sqlite v1.29.7/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= diff --git a/integration/aws_cloud_test.go b/integration/aws_cloud_test.go index 9a8cb8781474..3290d5daeee1 100644 --- a/integration/aws_cloud_test.go +++ b/integration/aws_cloud_test.go @@ -25,7 +25,7 @@ func TestAwsCommandRun(t *testing.T) { { name: "fail without region", options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, }, envs: map[string]string{ "AWS_ACCESS_KEY_ID": "test", @@ -39,7 +39,7 @@ func TestAwsCommandRun(t *testing.T) { "AWS_PROFILE": "non-existent-profile", }, options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, AWSOptions: flag.AWSOptions{ Region: "us-east-1", }, diff --git a/magefiles/magefile.go b/magefiles/magefile.go index 9e456f9bdf40..15b9759459b6 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -420,16 +420,19 @@ func installed(cmd string) bool { type Schema mg.Namespace +// Generate generates Cloud Schema for misconfiguration scanning func (Schema) Generate() error { return sh.RunWith(ENV, "go", "run", "-tags=mage_schema", "./magefiles", "--", "generate") } +// Verify verifies Cloud Schema for misconfiguration scanning func (Schema) Verify() error { return sh.RunWith(ENV, "go", "run", "-tags=mage_schema", "./magefiles", "--", "verify") } type CloudActions mg.Namespace +// Generate generates the list of possible cloud actions with AWS func (CloudActions) Generate() error { return sh.RunWith(ENV, "go", "run", "-tags=mage_cloudactions", "./magefiles") } diff --git a/mkdocs.yml b/mkdocs.yml index 75aff50f9fe2..c3437fecf413 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -55,8 +55,8 @@ nav: - Misconfiguration: - Overview: docs/scanner/misconfiguration/index.md - Policy: - - Built-in Policies: docs/scanner/misconfiguration/policy/builtin.md - - Exceptions: docs/scanner/misconfiguration/policy/exceptions.md + - Built-in Checks: docs/scanner/misconfiguration/check/builtin.md + - Exceptions: docs/scanner/misconfiguration/check/exceptions.md - Custom Policies: - Overview: docs/scanner/misconfiguration/custom/index.md - Data: docs/scanner/misconfiguration/custom/data.md diff --git a/pkg/cloud/aws/commands/run.go b/pkg/cloud/aws/commands/run.go index 374abbd91289..af8afb36f8de 100644 --- a/pkg/cloud/aws/commands/run.go +++ b/pkg/cloud/aws/commands/run.go @@ -3,6 +3,7 @@ package commands import ( "context" "errors" + "sort" "strings" "github.com/aws/aws-sdk-go-v2/service/sts" @@ -161,6 +162,10 @@ func Run(ctx context.Context, opt flag.Options) error { log.DebugContext(ctx, "Writing report to output...") + sort.Slice(results, func(i, j int) bool { + return results[i].Rule().AVDID < results[j].Rule().AVDID + }) + res := results.GetFailed() if opt.MisconfOptions.IncludeNonFailures { res = results diff --git a/pkg/cloud/aws/commands/run_test.go b/pkg/cloud/aws/commands/run_test.go index fe25bf20098d..05c88560d1bf 100644 --- a/pkg/cloud/aws/commands/run_test.go +++ b/pkg/cloud/aws/commands/run_test.go @@ -142,30 +142,6 @@ const expectedS3ScanResult = `{ } } }, - { - "Type": "AWS", - "ID": "AVD-AWS-0132", - "AVDID": "AVD-AWS-0132", - "Title": "S3 encryption should use Customer Managed Keys", - "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", - "Message": "Bucket does not encrypt data with a customer managed key.", - "Resolution": "Enable encryption using customer managed keys", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0132" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, { "Type": "AWS", "ID": "AVD-AWS-0091", @@ -260,6 +236,30 @@ const expectedS3ScanResult = `{ "Lines": null } } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0132", + "AVDID": "AVD-AWS-0132", + "Title": "S3 encryption should use Customer Managed Keys", + "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", + "Message": "Bucket does not encrypt data with a customer managed key.", + "Resolution": "Enable encryption using customer managed keys", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0132" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } } ] } @@ -355,7 +355,7 @@ const expectedCustomScanResult = `{ "Type": "AWS", "Title": "Bad input data", "Description": "Just failing rule with input data", - "Message": "Rego policy resulted in DENY", + "Message": "Rego check resulted in DENY", "Namespace": "user.whatever", "Query": "deny", "Severity": "LOW", @@ -480,30 +480,6 @@ const expectedCustomScanResult = `{ } } }, - { - "Type": "AWS", - "ID": "AVD-AWS-0132", - "AVDID": "AVD-AWS-0132", - "Title": "S3 encryption should use Customer Managed Keys", - "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", - "Message": "Bucket does not encrypt data with a customer managed key.", - "Resolution": "Enable encryption using customer managed keys", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0132" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, { "Type": "AWS", "ID": "AVD-AWS-0091", @@ -598,6 +574,30 @@ const expectedCustomScanResult = `{ "Lines": null } } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0132", + "AVDID": "AVD-AWS-0132", + "Title": "S3 encryption should use Customer Managed Keys", + "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", + "Message": "Bucket does not encrypt data with a customer managed key.", + "Resolution": "Enable encryption using customer managed keys", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0132" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } } ] } @@ -659,10 +659,10 @@ const expectedS3AndCloudTrailResult = `{ "Type": "AWS", "ID": "AVD-AWS-0015", "AVDID": "AVD-AWS-0015", - "Title": "Cloudtrail should be encrypted at rest to secure access to sensitive trail data", - "Description": "Cloudtrail logs should be encrypted at rest to secure the sensitive data. Cloudtrail logs record all activity that occurs in the the account through API calls and would be one of the first places to look when reacting to a breach.", - "Message": "Trail is not encrypted.", - "Resolution": "Enable encryption at rest", + "Title": "CloudTrail should use Customer managed keys to encrypt the logs", + "Description": "Using Customer managed keys provides comprehensive control over cryptographic keys, enabling management of policies, permissions, and rotation, thus enhancing security and compliance measures for sensitive data and systems.", + "Message": "CloudTrail does not use a customer managed key to encrypt the logs.", + "Resolution": "Use Customer managed key", "Severity": "HIGH", "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0015", "References": [ @@ -835,30 +835,6 @@ const expectedS3AndCloudTrailResult = `{ } } }, - { - "Type": "AWS", - "ID": "AVD-AWS-0132", - "AVDID": "AVD-AWS-0132", - "Title": "S3 encryption should use Customer Managed Keys", - "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", - "Message": "Bucket does not encrypt data with a customer managed key.", - "Resolution": "Enable encryption using customer managed keys", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0132" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, { "Type": "AWS", "ID": "AVD-AWS-0091", @@ -953,6 +929,30 @@ const expectedS3AndCloudTrailResult = `{ "Lines": null } } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0132", + "AVDID": "AVD-AWS-0132", + "Title": "S3 encryption should use Customer Managed Keys", + "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", + "Message": "Bucket does not encrypt data with a customer managed key.", + "Resolution": "Enable encryption using customer managed keys", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0132" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } } ] } @@ -977,7 +977,7 @@ func Test_Run(t *testing.T) { { name: "succeed with cached infra", options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, AWSOptions: flag.AWSOptions{ Region: "us-east-1", Services: []string{"s3"}, @@ -1005,16 +1005,16 @@ func Test_Run(t *testing.T) { }, RegoOptions: flag.RegoOptions{ Trace: true, - PolicyPaths: []string{ + CheckPaths: []string{ filepath.Join(regoDir, "policies"), }, - PolicyNamespaces: []string{ + CheckNamespaces: []string{ "user", }, DataPaths: []string{ filepath.Join(regoDir, "data"), }, - SkipPolicyUpdate: true, + SkipCheckUpdate: true, }, MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, }, @@ -1082,7 +1082,7 @@ deny { Format: "table", ReportFormat: "summary", }, - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, }, cacheContent: "testdata/s3onlycache.json", allServices: []string{"s3"}, @@ -1098,7 +1098,7 @@ Summary Report for compliance: my-custom-spec { name: "scan an unsupported service", options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, AWSOptions: flag.AWSOptions{ Region: "us-east-1", Account: "123456789", @@ -1115,7 +1115,7 @@ Summary Report for compliance: my-custom-spec { name: "scan every service", options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, AWSOptions: flag.AWSOptions{ Region: "us-east-1", Account: "123456789", @@ -1135,7 +1135,7 @@ Summary Report for compliance: my-custom-spec { name: "skip certain services and include specific services", options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, AWSOptions: flag.AWSOptions{ Region: "us-east-1", Services: []string{"s3"}, @@ -1158,7 +1158,7 @@ Summary Report for compliance: my-custom-spec { name: "only skip certain services but scan the rest", options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, AWSOptions: flag.AWSOptions{ Region: "us-east-1", SkipServices: []string{ @@ -1183,7 +1183,7 @@ Summary Report for compliance: my-custom-spec { name: "fail - service specified to both include and exclude", options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, AWSOptions: flag.AWSOptions{ Region: "us-east-1", Services: []string{"s3"}, @@ -1201,7 +1201,7 @@ Summary Report for compliance: my-custom-spec { name: "ignore findings with .trivyignore", options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, AWSOptions: flag.AWSOptions{ Region: "us-east-1", Services: []string{"s3"}, diff --git a/pkg/cloud/aws/scanner/scanner.go b/pkg/cloud/aws/scanner/scanner.go index d1efe2b78d4a..fcbabe0d8558 100644 --- a/pkg/cloud/aws/scanner/scanner.go +++ b/pkg/cloud/aws/scanner/scanner.go @@ -72,13 +72,14 @@ func (s *AWSScanner) Scan(ctx context.Context, option flag.Options) (scan.Result var policyPaths []string var downloadedPolicyPaths []string var err error - downloadedPolicyPaths, err = operation.InitBuiltinPolicies(context.Background(), option.CacheDir, option.Quiet, option.SkipPolicyUpdate, option.MisconfOptions.PolicyBundleRepository, option.RegistryOpts()) + + downloadedPolicyPaths, err = operation.InitBuiltinPolicies(context.Background(), option.CacheDir, option.Quiet, option.SkipCheckUpdate, option.MisconfOptions.ChecksBundleRepository, option.RegistryOpts()) if err != nil { - if !option.SkipPolicyUpdate { - s.logger.Error("Falling back to embedded policies", log.Err(err)) + if !option.SkipCheckUpdate { + s.logger.Error("Falling back to embedded checks", log.Err(err)) } } else { - s.logger.Debug("Policies successfully loaded from disk") + s.logger.Debug("Checks successfully loaded from disk") policyPaths = append(policyPaths, downloadedPolicyPaths...) scannerOpts = append(scannerOpts, options.ScannerWithEmbeddedPolicies(false), @@ -86,7 +87,7 @@ func (s *AWSScanner) Scan(ctx context.Context, option flag.Options) (scan.Result } var policyFS fs.FS - policyFS, policyPaths, err = misconf.CreatePolicyFS(append(policyPaths, option.RegoOptions.PolicyPaths...)) + policyFS, policyPaths, err = misconf.CreatePolicyFS(append(policyPaths, option.RegoOptions.CheckPaths...)) if err != nil { return nil, false, xerrors.Errorf("unable to create policyfs: %w", err) } @@ -105,7 +106,7 @@ func (s *AWSScanner) Scan(ctx context.Context, option flag.Options) (scan.Result options.ScannerWithDataFilesystem(dataFS), ) - scannerOpts = addPolicyNamespaces(option.RegoOptions.PolicyNamespaces, scannerOpts) + scannerOpts = addPolicyNamespaces(option.RegoOptions.CheckNamespaces, scannerOpts) if option.Compliance.Spec.ID != "" { scannerOpts = append(scannerOpts, options.ScannerWithSpec(option.Compliance.Spec.ID)) diff --git a/pkg/commands/app.go b/pkg/commands/app.go index af2902a14e0d..38ef4c382765 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -1230,10 +1230,10 @@ func showVersion(cacheDir, outputFormat string, w io.Writer) error { } func validateArgs(cmd *cobra.Command, args []string) error { - // '--clear-cache', '--download-db-only', '--download-java-db-only', '--reset' and '--generate-default-config' don't conduct the subsequent scanning + // '--clear-cache', '--download-db-only', '--download-java-db-only', '--reset', '--reset-checks-bundle' and '--generate-default-config' don't conduct the subsequent scanning if viper.GetBool(flag.ClearCacheFlag.ConfigName) || viper.GetBool(flag.DownloadDBOnlyFlag.ConfigName) || viper.GetBool(flag.ResetFlag.ConfigName) || viper.GetBool(flag.GenerateDefaultConfigFlag.ConfigName) || - viper.GetBool(flag.DownloadJavaDBOnlyFlag.ConfigName) || viper.GetBool(flag.ResetPolicyBundleFlag.ConfigName) { + viper.GetBool(flag.DownloadJavaDBOnlyFlag.ConfigName) || viper.GetBool(flag.ResetChecksBundleFlag.ConfigName) { return nil } diff --git a/pkg/commands/app_test.go b/pkg/commands/app_test.go index 0a4651d9e63d..c1f1593cf53c 100644 --- a/pkg/commands/app_test.go +++ b/pkg/commands/app_test.go @@ -43,7 +43,7 @@ Java DB: UpdatedAt: 2023-03-14 00:47:02.774253754 +0000 UTC NextUpdate: 2023-03-17 00:47:02.774253254 +0000 UTC DownloadedAt: 2023-03-14 03:04:55.058541039 +0000 UTC -Policy Bundle: +Check Bundle: Digest: sha256:19a017cdc798631ad42f6f4dce823d77b2989128f0e1a7f9bc83ae3c59024edd DownloadedAt: 2023-03-02 01:06:08.191725 +0000 UTC `, @@ -81,11 +81,11 @@ Java DB: UpdatedAt: 2023-03-14 00:47:02.774253754 +0000 UTC NextUpdate: 2023-03-17 00:47:02.774253254 +0000 UTC DownloadedAt: 2023-03-14 03:04:55.058541039 +0000 UTC -Policy Bundle: +Check Bundle: Digest: sha256:19a017cdc798631ad42f6f4dce823d77b2989128f0e1a7f9bc83ae3c59024edd DownloadedAt: 2023-03-02 01:06:08.191725 +0000 UTC ` - jsonOutput := `{"Version":"dev","VulnerabilityDB":{"Version":2,"NextUpdate":"2022-03-02T12:07:07.99504023Z","UpdatedAt":"2022-03-02T06:07:07.99504083Z","DownloadedAt":"2022-03-02T10:03:38.383312Z"},"JavaDB":{"Version":1,"NextUpdate":"2023-03-17T00:47:02.774253254Z","UpdatedAt":"2023-03-14T00:47:02.774253754Z","DownloadedAt":"2023-03-14T03:04:55.058541039Z"},"PolicyBundle":{"Digest":"sha256:19a017cdc798631ad42f6f4dce823d77b2989128f0e1a7f9bc83ae3c59024edd","DownloadedAt":"2023-03-02T01:06:08.191725Z"}} + jsonOutput := `{"Version":"dev","VulnerabilityDB":{"Version":2,"NextUpdate":"2022-03-02T12:07:07.99504023Z","UpdatedAt":"2022-03-02T06:07:07.99504083Z","DownloadedAt":"2022-03-02T10:03:38.383312Z"},"JavaDB":{"Version":1,"NextUpdate":"2023-03-17T00:47:02.774253254Z","UpdatedAt":"2023-03-14T00:47:02.774253754Z","DownloadedAt":"2023-03-14T03:04:55.058541039Z"},"CheckBundle":{"Digest":"sha256:19a017cdc798631ad42f6f4dce823d77b2989128f0e1a7f9bc83ae3c59024edd","DownloadedAt":"2023-03-02T01:06:08.191725Z"}} ` tests := []struct { name string diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index fbfe257312ba..a1fde23d519f 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -367,10 +367,10 @@ func (r *runner) initCache(opts flag.Options) error { return SkipScan } - if opts.ResetPolicyBundle { - c, err := policy.NewClient(fsutils.CacheDir(), true, opts.MisconfOptions.PolicyBundleRepository) + if opts.ResetChecksBundle { + c, err := policy.NewClient(fsutils.CacheDir(), true, opts.MisconfOptions.ChecksBundleRepository) if err != nil { - return xerrors.Errorf("failed to instantiate policy client: %w", err) + return xerrors.Errorf("failed to instantiate check client: %w", err) } if err := c.Clear(); err != nil { return xerrors.Errorf("failed to remove the cache: %w", err) @@ -579,10 +579,11 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi var downloadedPolicyPaths []string var disableEmbedded bool - downloadedPolicyPaths, err := operation.InitBuiltinPolicies(context.Background(), opts.CacheDir, opts.Quiet, opts.SkipPolicyUpdate, opts.MisconfOptions.PolicyBundleRepository, opts.RegistryOpts()) + + downloadedPolicyPaths, err := operation.InitBuiltinPolicies(context.Background(), opts.CacheDir, opts.Quiet, opts.SkipCheckUpdate, opts.MisconfOptions.ChecksBundleRepository, opts.RegistryOpts()) if err != nil { - if !opts.SkipPolicyUpdate { - log.Error("Falling back to embedded policies", log.Err(err)) + if !opts.SkipCheckUpdate { + log.Error("Falling back to embedded checks", log.Err(err)) } } else { log.Debug("Policies successfully loaded from disk") @@ -591,8 +592,8 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi configScannerOptions = misconf.ScannerOption{ Debug: opts.Debug, Trace: opts.Trace, - Namespaces: append(opts.PolicyNamespaces, rego.BuiltinNamespaces()...), - PolicyPaths: append(opts.PolicyPaths, downloadedPolicyPaths...), + Namespaces: append(opts.CheckNamespaces, rego.BuiltinNamespaces()...), + PolicyPaths: append(opts.CheckPaths, downloadedPolicyPaths...), DataPaths: opts.DataPaths, HelmValues: opts.HelmValues, HelmValueFiles: opts.HelmValueFiles, diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index e97a9bd8f1ee..84783ba073ab 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -149,13 +149,13 @@ func showDBInfo(cacheDir string) error { } // InitBuiltinPolicies downloads the built-in policies and loads them -func InitBuiltinPolicies(ctx context.Context, cacheDir string, quiet, skipUpdate bool, policyBundleRepository string, registryOpts ftypes.RegistryOptions) ([]string, error) { +func InitBuiltinPolicies(ctx context.Context, cacheDir string, quiet, skipUpdate bool, checkBundleRepository string, registryOpts ftypes.RegistryOptions) ([]string, error) { mu.Lock() defer mu.Unlock() - client, err := policy.NewClient(cacheDir, quiet, policyBundleRepository) + client, err := policy.NewClient(cacheDir, quiet, checkBundleRepository) if err != nil { - return nil, xerrors.Errorf("policy client error: %w", err) + return nil, xerrors.Errorf("check client error: %w", err) } needsUpdate := false @@ -177,11 +177,11 @@ func InitBuiltinPolicies(ctx context.Context, cacheDir string, quiet, skipUpdate policyPaths, err := client.LoadBuiltinPolicies() if err != nil { if skipUpdate { - msg := "No downloadable policies were loaded as --skip-policy-update is enabled" + msg := "No downloadable policies were loaded as --skip-check-update is enabled" log.Info(msg) return nil, xerrors.Errorf(msg) } - return nil, xerrors.Errorf("policy load error: %w", err) + return nil, xerrors.Errorf("check load error: %w", err) } return policyPaths, nil } diff --git a/pkg/compliance/spec/compliance.go b/pkg/compliance/spec/compliance.go index 0f219dca7c60..bc91b7f664fe 100644 --- a/pkg/compliance/spec/compliance.go +++ b/pkg/compliance/spec/compliance.go @@ -9,7 +9,7 @@ import ( "golang.org/x/xerrors" "gopkg.in/yaml.v3" - sp "github.com/aquasecurity/trivy-policies/pkg/spec" + sp "github.com/aquasecurity/trivy-checks/pkg/spec" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" "github.com/aquasecurity/trivy/pkg/types" ) diff --git a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go index c23174d76540..273366f8856e 100644 --- a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go +++ b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go @@ -33,7 +33,7 @@ func Test_historyAnalyzer_Analyze(t *testing.T) { }, History: []v1.History{ { - // this is fine, see https://github.com/aquasecurity/trivy-policies/pull/60 for details + // this is fine, see https://github.com/aquasecurity/trivy-checks/pull/60 for details CreatedBy: "/bin/sh -c #(nop) ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /", EmptyLayer: false, }, diff --git a/pkg/fanal/cache/key.go b/pkg/fanal/cache/key.go index b11cfc4be1df..7d6720393554 100644 --- a/pkg/fanal/cache/key.go +++ b/pkg/fanal/cache/key.go @@ -36,7 +36,7 @@ func CalcKey(id string, analyzerVersions analyzer.Versions, hookVersions map[str return "", xerrors.Errorf("json encode error: %w", err) } - // Write policy, data contents and secret config file + // Write check, data contents and secret config file paths := append(artifactOpt.MisconfScannerOption.PolicyPaths, artifactOpt.MisconfScannerOption.DataPaths...) // Check if the secret config exists. diff --git a/pkg/flag/misconf_flags.go b/pkg/flag/misconf_flags.go index 57a91820c60d..a7f929fc4590 100644 --- a/pkg/flag/misconf_flags.go +++ b/pkg/flag/misconf_flags.go @@ -15,10 +15,17 @@ import ( // config-policy: "custom-policy/policy" // policy-namespaces: "user" var ( - ResetPolicyBundleFlag = Flag[bool]{ - Name: "reset-policy-bundle", - ConfigName: "misconfiguration.reset-policy-bundle", - Usage: "remove policy bundle", + ResetChecksBundleFlag = Flag[bool]{ + Name: "reset-checks-bundle", + ConfigName: "misconfiguration.reset-checks-bundle", + Usage: "remove checks bundle", + Aliases: []Alias{ + { + Name: "reset-policy-bundle", + ConfigName: "misconfiguration.reset-policy-bundle", + Deprecated: true, + }, + }, } IncludeNonFailuresFlag = Flag[bool]{ Name: "include-non-failures", @@ -71,11 +78,18 @@ var ( ConfigName: "misconfiguration.terraform.exclude-downloaded-modules", Usage: "exclude misconfigurations for downloaded terraform modules", } - PolicyBundleRepositoryFlag = Flag[string]{ - Name: "policy-bundle-repository", - ConfigName: "misconfiguration.policy-bundle-repository", + ChecksBundleRepositoryFlag = Flag[string]{ + Name: "checks-bundle-repository", + ConfigName: "misconfiguration.checks-bundle-repository", Default: fmt.Sprintf("%s:%d", policy.BundleRepository, policy.BundleVersion), - Usage: "OCI registry URL to retrieve policy bundle from", + Usage: "OCI registry URL to retrieve checks bundle from", + Aliases: []Alias{ + { + Name: "policy-bundle-repository", + ConfigName: "misconfiguration.policy-bundle-repository", + Deprecated: true, + }, + }, } MisconfigScannersFlag = Flag[[]string]{ Name: "misconfig-scanners", @@ -88,8 +102,8 @@ var ( // MisconfFlagGroup composes common printer flag structs used for commands providing misconfiguration scanning. type MisconfFlagGroup struct { IncludeNonFailures *Flag[bool] - ResetPolicyBundle *Flag[bool] - PolicyBundleRepository *Flag[string] + ResetChecksBundle *Flag[bool] + ChecksBundleRepository *Flag[string] // Values Files HelmValues *Flag[[]string] @@ -106,8 +120,8 @@ type MisconfFlagGroup struct { type MisconfOptions struct { IncludeNonFailures bool - ResetPolicyBundle bool - PolicyBundleRepository string + ResetChecksBundle bool + ChecksBundleRepository string // Values Files HelmValues []string @@ -125,8 +139,8 @@ type MisconfOptions struct { func NewMisconfFlagGroup() *MisconfFlagGroup { return &MisconfFlagGroup{ IncludeNonFailures: IncludeNonFailuresFlag.Clone(), - ResetPolicyBundle: ResetPolicyBundleFlag.Clone(), - PolicyBundleRepository: PolicyBundleRepositoryFlag.Clone(), + ResetChecksBundle: ResetChecksBundleFlag.Clone(), + ChecksBundleRepository: ChecksBundleRepositoryFlag.Clone(), HelmValues: HelmSetFlag.Clone(), HelmFileValues: HelmSetFileFlag.Clone(), @@ -148,8 +162,8 @@ func (f *MisconfFlagGroup) Name() string { func (f *MisconfFlagGroup) Flags() []Flagger { return []Flagger{ f.IncludeNonFailures, - f.ResetPolicyBundle, - f.PolicyBundleRepository, + f.ResetChecksBundle, + f.ChecksBundleRepository, f.HelmValues, f.HelmValueFiles, f.HelmFileValues, @@ -170,8 +184,8 @@ func (f *MisconfFlagGroup) ToOptions() (MisconfOptions, error) { return MisconfOptions{ IncludeNonFailures: f.IncludeNonFailures.Value(), - ResetPolicyBundle: f.ResetPolicyBundle.Value(), - PolicyBundleRepository: f.PolicyBundleRepository.Value(), + ResetChecksBundle: f.ResetChecksBundle.Value(), + ChecksBundleRepository: f.ChecksBundleRepository.Value(), HelmValues: f.HelmValues.Value(), HelmValueFiles: f.HelmValueFiles.Value(), HelmFileValues: f.HelmFileValues.Value(), diff --git a/pkg/flag/rego_flags.go b/pkg/flag/rego_flags.go index e0b21f73030b..e7358e065b33 100644 --- a/pkg/flag/rego_flags.go +++ b/pkg/flag/rego_flags.go @@ -7,66 +7,74 @@ package flag // config-policy: "custom-policy/policy" // policy-namespaces: "user" var ( - SkipPolicyUpdateFlag = Flag[bool]{ - Name: "skip-policy-update", - ConfigName: "rego.skip-policy-update", - Usage: "skip fetching rego policy updates", + SkipCheckUpdateFlag = Flag[bool]{ + Name: "skip-check-update", + ConfigName: "rego.skip-check-update", + Usage: "skip fetching rego check updates", + Aliases: []Alias{ + { + Name: "skip-policy-update", + Deprecated: true, + }, + }, } TraceFlag = Flag[bool]{ Name: "trace", ConfigName: "rego.trace", Usage: "enable more verbose trace output for custom queries", } - ConfigPolicyFlag = Flag[[]string]{ - Name: "config-policy", - ConfigName: "rego.policy", - Usage: "specify the paths to the Rego policy files or to the directories containing them, applying config files", + ConfigCheckFlag = Flag[[]string]{ + Name: "config-check", + ConfigName: "rego.check", + Usage: "specify the paths to the Rego check files or to the directories containing them, applying config files", Aliases: []Alias{ - {Name: "policy"}, + {Name: "policy", Deprecated: true}, + {Name: "config-policy", Deprecated: true}, }, } ConfigDataFlag = Flag[[]string]{ Name: "config-data", ConfigName: "rego.data", - Usage: "specify paths from which data for the Rego policies will be recursively loaded", + Usage: "specify paths from which data for the Rego checks will be recursively loaded", Aliases: []Alias{ {Name: "data"}, }, } - PolicyNamespaceFlag = Flag[[]string]{ - Name: "policy-namespaces", + CheckNamespaceFlag = Flag[[]string]{ + Name: "check-namespaces", ConfigName: "rego.namespaces", Usage: "Rego namespaces", Aliases: []Alias{ {Name: "namespaces"}, + {Name: "policy-namespaces", Deprecated: true}, }, } ) // RegoFlagGroup composes common printer flag structs used for commands providing misconfinguration scanning. type RegoFlagGroup struct { - SkipPolicyUpdate *Flag[bool] - Trace *Flag[bool] - PolicyPaths *Flag[[]string] - DataPaths *Flag[[]string] - PolicyNamespaces *Flag[[]string] + SkipCheckUpdate *Flag[bool] + Trace *Flag[bool] + CheckPaths *Flag[[]string] + DataPaths *Flag[[]string] + CheckNamespaces *Flag[[]string] } type RegoOptions struct { - SkipPolicyUpdate bool - Trace bool - PolicyPaths []string - DataPaths []string - PolicyNamespaces []string + SkipCheckUpdate bool + Trace bool + CheckPaths []string + DataPaths []string + CheckNamespaces []string } func NewRegoFlagGroup() *RegoFlagGroup { return &RegoFlagGroup{ - SkipPolicyUpdate: SkipPolicyUpdateFlag.Clone(), - Trace: TraceFlag.Clone(), - PolicyPaths: ConfigPolicyFlag.Clone(), - DataPaths: ConfigDataFlag.Clone(), - PolicyNamespaces: PolicyNamespaceFlag.Clone(), + SkipCheckUpdate: SkipCheckUpdateFlag.Clone(), + Trace: TraceFlag.Clone(), + CheckPaths: ConfigCheckFlag.Clone(), + DataPaths: ConfigDataFlag.Clone(), + CheckNamespaces: CheckNamespaceFlag.Clone(), } } @@ -76,11 +84,11 @@ func (f *RegoFlagGroup) Name() string { func (f *RegoFlagGroup) Flags() []Flagger { return []Flagger{ - f.SkipPolicyUpdate, + f.SkipCheckUpdate, f.Trace, - f.PolicyPaths, + f.CheckPaths, f.DataPaths, - f.PolicyNamespaces, + f.CheckNamespaces, } } @@ -90,10 +98,10 @@ func (f *RegoFlagGroup) ToOptions() (RegoOptions, error) { } return RegoOptions{ - SkipPolicyUpdate: f.SkipPolicyUpdate.Value(), - Trace: f.Trace.Value(), - PolicyPaths: f.PolicyPaths.Value(), - DataPaths: f.DataPaths.Value(), - PolicyNamespaces: f.PolicyNamespaces.Value(), + SkipCheckUpdate: f.SkipCheckUpdate.Value(), + Trace: f.Trace.Value(), + CheckPaths: f.CheckPaths.Value(), + DataPaths: f.DataPaths.Value(), + CheckNamespaces: f.CheckNamespaces.Value(), }, nil } diff --git a/pkg/iac/rego/embed.go b/pkg/iac/rego/embed.go index d554ebf087a3..9d1ac6458c52 100644 --- a/pkg/iac/rego/embed.go +++ b/pkg/iac/rego/embed.go @@ -8,7 +8,7 @@ import ( "github.com/open-policy-agent/opa/ast" - checks "github.com/aquasecurity/trivy-policies" + checks "github.com/aquasecurity/trivy-checks" "github.com/aquasecurity/trivy/pkg/iac/rules" ) diff --git a/pkg/iac/rego/embed_test.go b/pkg/iac/rego/embed_test.go index d10f4d212a10..36d136259a5d 100644 --- a/pkg/iac/rego/embed_test.go +++ b/pkg/iac/rego/embed_test.go @@ -3,7 +3,7 @@ package rego import ( "testing" - rules2 "github.com/aquasecurity/trivy-policies" + checks "github.com/aquasecurity/trivy-checks" "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/open-policy-agent/opa/ast" "github.com/stretchr/testify/assert" @@ -84,7 +84,7 @@ deny[res]{ for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - policies, err := LoadPoliciesFromDirs(rules2.EmbeddedLibraryFileSystem, ".") + policies, err := LoadPoliciesFromDirs(checks.EmbeddedLibraryFileSystem, ".") require.NoError(t, err) newRule, err := ast.ParseModuleWithOpts("/rules/newrule.rego", tc.inputPolicy, ast.ParserOptions{ ProcessAnnotation: true, diff --git a/pkg/iac/rego/load.go b/pkg/iac/rego/load.go index 43f8fd76be68..4c37edb80f33 100644 --- a/pkg/iac/rego/load.go +++ b/pkg/iac/rego/load.go @@ -173,7 +173,7 @@ func (s *Scanner) fallbackChecks(compiler *ast.Compiler) { } s.debug.Log("Found embedded check: %s", embedded.Package.Location.File) - delete(s.policies, loc) // remove bad policy + delete(s.policies, loc) // remove bad check s.policies[embedded.Package.Location.File] = embedded delete(s.embeddedChecks, embedded.Package.Location.File) // avoid infinite loop if embedded check contains ref error excludedFiles = append(excludedFiles, e.Location.File) @@ -228,7 +228,7 @@ func (s *Scanner) compilePolicies(srcFS fs.FS, paths []string) error { return err } if custom { - s.inputSchema = nil // discard auto detected input schema in favor of policy defined schema + s.inputSchema = nil // discard auto detected input schema in favor of check defined schema } compiler := ast.NewCompiler(). diff --git a/pkg/iac/rego/load_test.go b/pkg/iac/rego/load_test.go index fdd56a56347d..984fec9c4caf 100644 --- a/pkg/iac/rego/load_test.go +++ b/pkg/iac/rego/load_test.go @@ -8,7 +8,7 @@ import ( "testing" "testing/fstest" - trivy_policies "github.com/aquasecurity/trivy-policies" + checks "github.com/aquasecurity/trivy-checks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -197,7 +197,7 @@ deny { } fsys := fstest.MapFS(tt.files) - trivy_policies.EmbeddedPolicyFileSystem = embeddedChecksFS + checks.EmbeddedPolicyFileSystem = embeddedChecksFS err := scanner.LoadPolicies(false, false, fsys, []string{"."}, nil) if tt.expectedErr != "" { diff --git a/pkg/iac/rego/result.go b/pkg/iac/rego/result.go index dd2f7629d3a3..9217c15f5479 100644 --- a/pkg/iac/rego/result.go +++ b/pkg/iac/rego/result.go @@ -67,7 +67,7 @@ func parseResult(raw interface{}) *regoResult { case map[string]interface{}: result = parseCause(val) default: - result.Message = "Rego policy resulted in DENY" + result.Message = "Rego check resulted in DENY" } return &result } @@ -150,7 +150,7 @@ func (s *Scanner) convertResults(set rego.ResultSet, input Input, namespace, rul regoResult.Filepath = input.Path } if regoResult.Message == "" { - regoResult.Message = fmt.Sprintf("Rego policy rule: %s.%s", namespace, rule) + regoResult.Message = fmt.Sprintf("Rego check rule: %s.%s", namespace, rule) } regoResult.StartLine += offset regoResult.EndLine += offset diff --git a/pkg/iac/rego/result_test.go b/pkg/iac/rego/result_test.go index d958f7962b10..a3ee4e57c900 100644 --- a/pkg/iac/rego/result_test.go +++ b/pkg/iac/rego/result_test.go @@ -17,7 +17,7 @@ func Test_parseResult(t *testing.T) { input: nil, want: regoResult{ Managed: true, - Message: "Rego policy resulted in DENY", + Message: "Rego check resulted in DENY", }, }, { diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go index 6c1fc04c5065..001e8f52a080 100644 --- a/pkg/iac/rego/scanner.go +++ b/pkg/iac/rego/scanner.go @@ -248,7 +248,7 @@ func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, } if isPolicyWithSubtype(s.sourceType) { - // skip if policy isn't relevant to what is being scanned + // skip if check isn't relevant to what is being scanned if !isPolicyApplicable(staticMeta, inputs...) { continue } @@ -326,7 +326,7 @@ func isPolicyApplicable(staticMetadata *StaticMetadata, inputs ...Input) bool { continue } - if len(staticMetadata.InputOptions.Selectors) == 0 { // policy always applies if no selectors + if len(staticMetadata.InputOptions.Selectors) == 0 { // check always applies if no selectors return true } diff --git a/pkg/iac/rego/schemas/dockerfile.json b/pkg/iac/rego/schemas/dockerfile.json index d769cb195bae..0805b47f702d 100644 --- a/pkg/iac/rego/schemas/dockerfile.json +++ b/pkg/iac/rego/schemas/dockerfile.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/aquasecurity/trivy-policies/blob/main/pkg/rego/schemas/dockerfile.json", + "$id": "https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/dockerfile.json", "type": "object", "properties": { "Stages": { diff --git a/pkg/iac/rego/schemas/kubernetes.json b/pkg/iac/rego/schemas/kubernetes.json index 1975944b7790..27de806d8bb5 100644 --- a/pkg/iac/rego/schemas/kubernetes.json +++ b/pkg/iac/rego/schemas/kubernetes.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/aquasecurity/trivy-policies/blob/main/pkg/rego/schemas/kubernetes.json", + "$id": "https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/kubernetes.json", "type": "object", "properties": { "apiVersion": { diff --git a/pkg/iac/rego/schemas/rbac.json b/pkg/iac/rego/schemas/rbac.json index c251890f91fd..0bbc356297af 100644 --- a/pkg/iac/rego/schemas/rbac.json +++ b/pkg/iac/rego/schemas/rbac.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/aquasecurity/trivy-policies/blob/main/pkg/rego/schemas/rbac.json", + "$id": "https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/rbac.json", "type": "object", "properties": { "apiVersion": { diff --git a/pkg/iac/rules/register.go b/pkg/iac/rules/register.go index 0170a8d63979..ab847de2e1dc 100755 --- a/pkg/iac/rules/register.go +++ b/pkg/iac/rules/register.go @@ -5,7 +5,7 @@ import ( "gopkg.in/yaml.v3" - "github.com/aquasecurity/trivy-policies/specs" + "github.com/aquasecurity/trivy-checks/specs" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/scan" dftypes "github.com/aquasecurity/trivy/pkg/iac/types" diff --git a/pkg/iac/rules/rules.go b/pkg/iac/rules/rules.go index bb3eec14ae35..96a73deba46f 100644 --- a/pkg/iac/rules/rules.go +++ b/pkg/iac/rules/rules.go @@ -1,79 +1,79 @@ package rules import ( - trules "github.com/aquasecurity/trivy-policies/pkg/rules" + trules "github.com/aquasecurity/trivy-checks/pkg/rules" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/accessanalyzer" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/apigateway" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/athena" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/cloudfront" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/cloudtrail" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/cloudwatch" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/codebuild" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/config" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/documentdb" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/dynamodb" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/ec2" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/ecr" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/ecs" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/efs" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/eks" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/elasticache" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/elasticsearch" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/elb" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/emr" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/iam" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/kinesis" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/kms" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/lambda" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/mq" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/msk" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/neptune" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/rds" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/redshift" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/s3" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/sam" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/sns" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/sqs" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/ssm" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/aws/workspaces" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/appservice" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/authorization" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/compute" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/container" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/database" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/datafactory" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/datalake" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/keyvault" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/monitor" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/network" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/securitycenter" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/storage" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/azure/synapse" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/cloudstack/compute" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/digitalocean/compute" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/digitalocean/spaces" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/github/actions" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/github/branch_protections" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/github/repositories" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/bigquery" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/compute" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/dns" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/gke" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/iam" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/kms" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/sql" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/google/storage" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/nifcloud/computing" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/nifcloud/dns" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/nifcloud/nas" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/nifcloud/network" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/nifcloud/rdb" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/nifcloud/sslcertificate" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/openstack/compute" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/openstack/networking" - _ "github.com/aquasecurity/trivy-policies/checks/cloud/oracle/compute" - _ "github.com/aquasecurity/trivy-policies/checks/kubernetes/network" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/accessanalyzer" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/apigateway" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/athena" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/cloudfront" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/cloudtrail" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/cloudwatch" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/codebuild" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/config" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/documentdb" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/dynamodb" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/ec2" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/ecr" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/ecs" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/efs" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/eks" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/elasticache" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/elasticsearch" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/elb" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/emr" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/iam" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/kinesis" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/kms" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/lambda" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/mq" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/msk" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/neptune" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/rds" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/redshift" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/s3" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/sam" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/sns" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/sqs" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/ssm" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/aws/workspaces" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/appservice" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/authorization" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/compute" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/container" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/database" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/datafactory" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/datalake" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/keyvault" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/monitor" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/network" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/securitycenter" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/storage" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/azure/synapse" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/cloudstack/compute" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/digitalocean/compute" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/digitalocean/spaces" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/github/actions" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/github/branch_protections" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/github/repositories" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/google/bigquery" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/google/compute" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/google/dns" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/google/gke" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/google/iam" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/google/kms" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/google/sql" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/google/storage" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/nifcloud/computing" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/nifcloud/dns" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/nifcloud/nas" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/nifcloud/network" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/nifcloud/rdb" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/nifcloud/sslcertificate" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/openstack/compute" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/openstack/networking" + _ "github.com/aquasecurity/trivy-checks/checks/cloud/oracle/compute" + _ "github.com/aquasecurity/trivy-checks/checks/kubernetes/network" ) func init() { diff --git a/pkg/iac/scanners/terraform/module_test.go b/pkg/iac/scanners/terraform/module_test.go index 61b1a0e359f6..d369ffefe44c 100644 --- a/pkg/iac/scanners/terraform/module_test.go +++ b/pkg/iac/scanners/terraform/module_test.go @@ -18,7 +18,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/terraform" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy-policies/checks/cloud/aws/iam" + "github.com/aquasecurity/trivy-checks/checks/cloud/aws/iam" ) var badRule = scan.Rule{ diff --git a/pkg/misconf/scanner.go b/pkg/misconf/scanner.go index 950ad73cbca6..b9ff185b1de4 100644 --- a/pkg/misconf/scanner.go +++ b/pkg/misconf/scanner.go @@ -390,7 +390,7 @@ func CreatePolicyFS(policyPaths []string) (fs.FS, []string, error) { } } - // policy paths are no longer needed as fs.FS contains only needed files now. + // check paths are no longer needed as fs.FS contains only needed files now. policyPaths = []string{"."} return mfs, policyPaths, nil diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go index 6d7aadfc8e3d..950644c0084d 100644 --- a/pkg/policy/policy.go +++ b/pkg/policy/policy.go @@ -18,8 +18,8 @@ import ( ) const ( - BundleVersion = 0 // Latest released MAJOR version for trivy-policies - BundleRepository = "ghcr.io/aquasecurity/trivy-policies" + BundleVersion = 0 // Latest released MAJOR version for trivy-checks + BundleRepository = "ghcr.io/aquasecurity/trivy-checks" policyMediaType = "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip" updateInterval = 24 * time.Hour ) @@ -46,29 +46,29 @@ func WithClock(c clock.Clock) Option { // Option is a functional option type Option func(*options) -// Client implements policy operations +// Client implements check operations type Client struct { *options - policyDir string - policyBundleRepo string - quiet bool + policyDir string + checkBundleRepo string + quiet bool } -// Metadata holds default policy metadata +// Metadata holds default check metadata type Metadata struct { Digest string DownloadedAt time.Time } func (m Metadata) String() string { - return fmt.Sprintf(`Policy Bundle: + return fmt.Sprintf(`Check Bundle: Digest: %s DownloadedAt: %s `, m.Digest, m.DownloadedAt.UTC()) } -// NewClient is the factory method for policy client -func NewClient(cacheDir string, quiet bool, policyBundleRepo string, opts ...Option) (*Client, error) { +// NewClient is the factory method for check client +func NewClient(cacheDir string, quiet bool, checkBundleRepo string, opts ...Option) (*Client, error) { o := &options{ clock: clock.RealClock{}, } @@ -77,22 +77,22 @@ func NewClient(cacheDir string, quiet bool, policyBundleRepo string, opts ...Opt opt(o) } - if policyBundleRepo == "" { - policyBundleRepo = fmt.Sprintf("%s:%d", BundleRepository, BundleVersion) + if checkBundleRepo == "" { + checkBundleRepo = fmt.Sprintf("%s:%d", BundleRepository, BundleVersion) } return &Client{ - options: o, - policyDir: filepath.Join(cacheDir, "policy"), - policyBundleRepo: policyBundleRepo, - quiet: quiet, + options: o, + policyDir: filepath.Join(cacheDir, "policy"), + checkBundleRepo: checkBundleRepo, + quiet: quiet, }, nil } func (c *Client) populateOCIArtifact(registryOpts types.RegistryOptions) error { if c.artifact == nil { - log.Debug("Loading policy bundle", log.String("repository", c.policyBundleRepo)) - art, err := oci.NewArtifact(c.policyBundleRepo, c.quiet, registryOpts) + log.Debug("Loading check bundle", log.String("repository", c.checkBundleRepo)) + art, err := oci.NewArtifact(c.checkBundleRepo, c.quiet, registryOpts) if err != nil { return xerrors.Errorf("OCI artifact error: %w", err) } @@ -120,7 +120,7 @@ func (c *Client) DownloadBuiltinPolicies(ctx context.Context, registryOpts types // Update metadata.json with the new digest and the current date if err = c.updateMetadata(digest, c.clock.Now()); err != nil { - return xerrors.Errorf("unable to update the policy metadata: %w", err) + return xerrors.Errorf("unable to update the check metadata: %w", err) } return nil @@ -140,7 +140,7 @@ func (c *Client) LoadBuiltinPolicies() ([]string, error) { } // If the "roots" field is not included in the manifest it defaults to [""] - // which means that ALL data and policy must come from the bundle. + // which means that ALL data and check must come from the bundle. if manifest.Roots == nil || len(*manifest.Roots) == 0 { return []string{c.contentDir()}, nil } @@ -153,7 +153,7 @@ func (c *Client) LoadBuiltinPolicies() ([]string, error) { return policyPaths, nil } -// NeedsUpdate returns if the default policy should be updated +// NeedsUpdate returns if the default check should be updated func (c *Client) NeedsUpdate(ctx context.Context, registryOpts types.RegistryOptions) (bool, error) { meta, err := c.GetMetadata() if err != nil { @@ -182,7 +182,7 @@ func (c *Client) NeedsUpdate(ctx context.Context, registryOpts types.RegistryOpt // Otherwise, if there are no updates in the remote registry, // the digest will be fetched every time even after this. if err = c.updateMetadata(meta.Digest, time.Now()); err != nil { - return false, xerrors.Errorf("unable to update the policy metadata: %w", err) + return false, xerrors.Errorf("unable to update the check metadata: %w", err) } return false, nil @@ -203,7 +203,7 @@ func (c *Client) manifestPath() string { func (c *Client) updateMetadata(digest string, now time.Time) error { f, err := os.Create(c.metadataPath()) if err != nil { - return xerrors.Errorf("failed to open a policy manifest: %w", err) + return xerrors.Errorf("failed to open a check manifest: %w", err) } defer f.Close() @@ -222,14 +222,14 @@ func (c *Client) updateMetadata(digest string, now time.Time) error { func (c *Client) GetMetadata() (*Metadata, error) { f, err := os.Open(c.metadataPath()) if err != nil { - log.Debug("Failed to open the policy metadata", log.Err(err)) + log.Debug("Failed to open the check metadata", log.Err(err)) return nil, err } defer f.Close() var meta Metadata if err = json.NewDecoder(f).Decode(&meta); err != nil { - log.Warn("Policy metadata decode error", log.Err(err)) + log.Warn("Check metadata decode error", log.Err(err)) return nil, err } @@ -237,9 +237,9 @@ func (c *Client) GetMetadata() (*Metadata, error) { } func (c *Client) Clear() error { - log.Info("Removing policy bundle...") + log.Info("Removing check bundle...") if err := os.RemoveAll(c.policyDir); err != nil { - return xerrors.Errorf("failed to remove policy bundle: %w", err) + return xerrors.Errorf("failed to remove check bundle: %w", err) } return nil } diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go index 0eb3190bf31d..a12a8374f25e 100644 --- a/pkg/policy/policy_test.go +++ b/pkg/policy/policy_test.go @@ -243,7 +243,7 @@ func TestClient_NeedsUpdate(t *testing.T) { }, }, nil) - // Create a policy directory + // Create a check directory err := os.MkdirAll(filepath.Join(tmpDir, "policy"), os.ModePerm) require.NoError(t, err) diff --git a/pkg/result/filter_test.go b/pkg/result/filter_test.go index d98048c6af1b..dec620817afe 100644 --- a/pkg/result/filter_test.go +++ b/pkg/result/filter_test.go @@ -570,7 +570,7 @@ func TestFilter(t *testing.T) { Vulnerabilities: []types.DetectedVulnerability{ vuln1, vuln2, // ignored by severity - vuln3, // ignored by policy + vuln3, // ignored by check }, }, }, @@ -606,7 +606,7 @@ func TestFilter(t *testing.T) { Misconfigurations: []types.DetectedMisconfiguration{ misconf1, misconf2, - misconf3, // ignored by policy + misconf3, // ignored by check }, }, }, diff --git a/pkg/rpc/server/listen_test.go b/pkg/rpc/server/listen_test.go index 5fff3f3bc46e..2a3399bf5eb9 100644 --- a/pkg/rpc/server/listen_test.go +++ b/pkg/rpc/server/listen_test.go @@ -306,7 +306,7 @@ func Test_VersionEndpoint(t *testing.T) { UpdatedAt: time.Date(2023, 7, 20, 12, 11, 37, 696263932, time.UTC), DownloadedAt: time.Date(2023, 7, 25, 7, 1, 41, 239158000, time.UTC), }, - PolicyBundle: &policy.Metadata{ + CheckBundle: &policy.Metadata{ Digest: "sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43", DownloadedAt: time.Date(2023, 7, 23, 16, 40, 33, 122462000, time.UTC), }, diff --git a/pkg/version/version.go b/pkg/version/version.go index 54914c563c87..c6b18be1eaef 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -22,7 +22,7 @@ type VersionInfo struct { Version string `json:",omitempty"` VulnerabilityDB *metadata.Metadata `json:",omitempty"` JavaDB *metadata.Metadata `json:",omitempty"` - PolicyBundle *policy.Metadata `json:",omitempty"` + CheckBundle *policy.Metadata `json:",omitempty"` } func formatDBMetadata(title string, meta metadata.Metadata) string { @@ -42,8 +42,8 @@ func (v *VersionInfo) String() string { if v.JavaDB != nil { output += formatDBMetadata("Java DB", *v.JavaDB) } - if v.PolicyBundle != nil { - output += v.PolicyBundle.String() + if v.CheckBundle != nil { + output += v.CheckBundle.String() } return output } @@ -102,6 +102,6 @@ func NewVersionInfo(cacheDir string) VersionInfo { Version: ver, VulnerabilityDB: dbMeta, JavaDB: javadbMeta, - PolicyBundle: pbMeta, + CheckBundle: pbMeta, } } diff --git a/pkg/version/version_test.go b/pkg/version/version_test.go index 84411593541a..c8797611a788 100644 --- a/pkg/version/version_test.go +++ b/pkg/version/version_test.go @@ -26,7 +26,7 @@ func Test_BuildVersionInfo(t *testing.T) { UpdatedAt: time.Date(2023, 7, 25, 1, 3, 52, 169192765, time.UTC), DownloadedAt: time.Date(2023, 7, 25, 9, 37, 48, 906152000, time.UTC), }, - PolicyBundle: &policy.Metadata{ + CheckBundle: &policy.Metadata{ Digest: "sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43", DownloadedAt: time.Date(2023, 7, 23, 16, 40, 33, 122462000, time.UTC), }, @@ -46,7 +46,7 @@ Java DB: UpdatedAt: 2023-07-25 01:03:52.169192765 +0000 UTC NextUpdate: 2023-07-28 01:03:52.169192565 +0000 UTC DownloadedAt: 2023-07-25 09:37:48.906152 +0000 UTC -Policy Bundle: +Check Bundle: Digest: sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43 DownloadedAt: 2023-07-23 16:40:33.122462 +0000 UTC ` From 715963d754d056fee6b6ce8f7d39c17cab0e4c57 Mon Sep 17 00:00:00 2001 From: Marlon M <49958228+maxthier@users.noreply.github.com> Date: Fri, 3 May 2024 06:03:59 +0200 Subject: [PATCH 035/352] docs: remove mention of GitLab Gold because it doesn't exist anymore (#6609) --- docs/tutorials/integrations/gitlab-ci.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/integrations/gitlab-ci.md b/docs/tutorials/integrations/gitlab-ci.md index d1afc9128e4d..dbfe46d1ca4d 100644 --- a/docs/tutorials/integrations/gitlab-ci.md +++ b/docs/tutorials/integrations/gitlab-ci.md @@ -49,7 +49,7 @@ trivy: cache: paths: - .trivycache/ - # Enables https://docs.gitlab.com/ee/user/application_security/container_scanning/ (Container Scanning report is available on GitLab EE Ultimate or GitLab.com Gold) + # Enables https://docs.gitlab.com/ee/user/application_security/container_scanning/ (Container Scanning report is available on GitLab Ultimate) artifacts: reports: container_scanning: gl-container-scanning-report.json From 58cfd1b0746828282d88468fc92c7b18a1eca52a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 08:05:14 +0400 Subject: [PATCH 036/352] chore(deps): bump github.com/docker/docker from 26.0.1+incompatible to 26.0.2+incompatible (#6612) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index bf0df6324df8..ebce0b46f297 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/cheggaaa/pb/v3 v3.1.4 github.com/containerd/containerd v1.7.16 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 - github.com/docker/docker v26.0.1+incompatible + github.com/docker/docker v26.0.2+incompatible github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.16.0 github.com/go-git/go-git/v5 v5.11.0 diff --git a/go.sum b/go.sum index 9c2e8568c932..0b1d4019fd85 100644 --- a/go.sum +++ b/go.sum @@ -1152,8 +1152,9 @@ github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v26.0.1+incompatible h1:t39Hm6lpXuXtgkF0dm1t9a5HkbUfdGy6XbWexmGr+hA= github.com/docker/docker v26.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v26.0.2+incompatible h1:yGVmKUFGgcxA6PXWAokO0sQL22BrQ67cgVjko8tGdXE= +github.com/docker/docker v26.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= From 3ccb1a0f103afdd7e18acae45a39b81119d2256f Mon Sep 17 00:00:00 2001 From: chenk Date: Fri, 3 May 2024 07:50:53 +0300 Subject: [PATCH 037/352] docs: trivy-k8s new experiance remove un-used section (#6608) Signed-off-by: chenk --- docs/docs/target/kubernetes.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/docs/target/kubernetes.md b/docs/docs/target/kubernetes.md index 7a6183f51733..a92057dc5341 100644 --- a/docs/docs/target/kubernetes.md +++ b/docs/docs/target/kubernetes.md @@ -28,8 +28,6 @@ Kubernetes resource definition is scanned for: ## Kubernetes target configurations -Trivy follows the behavior of the `kubectl` tool as much as possible. - ```sh trivy k8s [flags] [CONTEXT] - if the target name [CONTEXT] is not specified, the default will be used. ``` From 770b14113cbbaaf55ff26ac8ba160800951b4386 Mon Sep 17 00:00:00 2001 From: simar7 <1254783+simar7@users.noreply.github.com> Date: Thu, 2 May 2024 23:04:10 -0600 Subject: [PATCH 038/352] perf(misconf): Improve cause performance (#6586) Signed-off-by: Simar --- pkg/iac/scan/code.go | 11 ++++++++++- pkg/misconf/scanner.go | 34 ++++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/pkg/iac/scan/code.go b/pkg/iac/scan/code.go index 5041b23ca2cc..e61b547988c6 100644 --- a/pkg/iac/scan/code.go +++ b/pkg/iac/scan/code.go @@ -1,6 +1,8 @@ package scan import ( + "bufio" + "bytes" "fmt" "io/fs" "path/filepath" @@ -149,7 +151,14 @@ func (r *Result) GetCode(opts ...CodeOption) (*Code, error) { Lines: nil, } - rawLines := strings.Split(string(content), "\n") + var rawLines []string + bs := bufio.NewScanner(bytes.NewReader(content)) + for bs.Scan() { + rawLines = append(rawLines, bs.Text()) + } + if bs.Err() != nil { + return nil, fmt.Errorf("failed to scan file : %w", err) + } var highlightedLines []string if settings.includeHighlighted { diff --git a/pkg/misconf/scanner.go b/pkg/misconf/scanner.go index b9ff185b1de4..9d81851b844f 100644 --- a/pkg/misconf/scanner.go +++ b/pkg/misconf/scanner.go @@ -503,20 +503,26 @@ func NewCauseWithCode(underlying scan.Result) types.CauseMetadata { }, }) } - if code, err := underlying.GetCode(); err == nil { - cause.Code = types.Code{ - Lines: lo.Map(code.Lines, func(l scan.Line, i int) types.Line { - return types.Line{ - Number: l.Number, - Content: l.Content, - IsCause: l.IsCause, - Annotation: l.Annotation, - Truncated: l.Truncated, - Highlighted: l.Highlighted, - FirstCause: l.FirstCause, - LastCause: l.LastCause, - } - }), + + // only failures have a code cause + // failures can happen either due to lack of + // OR misconfiguration of something + if underlying.Status() == scan.StatusFailed { + if code, err := underlying.GetCode(); err == nil { + cause.Code = types.Code{ + Lines: lo.Map(code.Lines, func(l scan.Line, i int) types.Line { + return types.Line{ + Number: l.Number, + Content: l.Content, + IsCause: l.IsCause, + Annotation: l.Annotation, + Truncated: l.Truncated, + Highlighted: l.Highlighted, + FirstCause: l.FirstCause, + LastCause: l.LastCause, + } + }), + } } } return cause From 998f750432a91e1e1832d507e66aab77d02449f9 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 3 May 2024 15:14:34 +0400 Subject: [PATCH 039/352] feat: introduce package UIDs for improved vulnerability mapping (#6583) Signed-off-by: knqyf263 --- Dockerfile.protoc | 4 +- integration/client_server_test.go | 17 +- integration/integration_test.go | 37 +- integration/registry_test.go | 4 +- integration/repo_test.go | 6 +- integration/sbom_test.go | 134 ++-- integration/testdata/almalinux-8.json.golden | 3 +- integration/testdata/alpine-310.json.golden | 12 +- .../alpine-39-high-critical.json.golden | 6 +- .../alpine-39-ignore-cveids.json.golden | 6 +- integration/testdata/alpine-39.json.golden | 18 +- .../testdata/alpine-distroless.json.golden | 3 +- integration/testdata/amazon-1.json.golden | 3 +- integration/testdata/amazon-2.json.golden | 6 +- .../busybox-with-lockfile.json.golden | 6 +- integration/testdata/centos-6.json.golden | 6 +- .../centos-7-ignore-unfixed.json.golden | 6 +- .../testdata/centos-7-medium.json.golden | 3 +- integration/testdata/centos-7.json.golden | 9 +- integration/testdata/cocoapods.json.golden | 6 +- .../testdata/composer.lock.json.golden | 9 +- integration/testdata/conan.json.golden | 24 +- .../debian-buster-ignore-unfixed.json.golden | 3 +- .../testdata/debian-buster.json.golden | 6 +- .../testdata/debian-stretch.json.golden | 15 +- .../testdata/distroless-base.json.golden | 12 +- .../testdata/distroless-python27.json.golden | 12 +- integration/testdata/dotnet.json.golden | 6 +- integration/testdata/fluentd-gems.json.golden | 6 +- .../fluentd-multiple-lockfiles.json.golden | 3 + integration/testdata/gomod-skip.json.golden | 12 +- integration/testdata/gomod.json.golden | 15 +- integration/testdata/gradle.json.golden | 6 +- integration/testdata/mariner-1.0.json.golden | 6 +- .../testdata/minikube-kbom.json.golden | 1 + integration/testdata/mix.lock.json.golden | 33 +- integration/testdata/npm-with-dev.json.golden | 42 +- integration/testdata/npm.json.golden | 39 +- integration/testdata/nuget.json.golden | 9 +- .../testdata/opensuse-leap-151.json.golden | 6 +- .../testdata/oraclelinux-8.json.golden | 6 +- .../testdata/packagesprops.json.golden | 6 +- integration/testdata/photon-30.json.golden | 9 +- integration/testdata/pip.json.golden | 27 +- integration/testdata/pipenv.json.golden | 9 +- integration/testdata/pnpm.json.golden | 6 +- integration/testdata/poetry.json.golden | 12 +- integration/testdata/pom.json.golden | 6 +- integration/testdata/pubspec.lock.json.golden | 9 +- integration/testdata/rockylinux-8.json.golden | 3 +- .../testdata/spring4shell-jre11.json.golden | 3 +- .../testdata/spring4shell-jre8.json.golden | 3 +- integration/testdata/swift.json.golden | 9 +- integration/testdata/test-repo.json.golden | 6 +- integration/testdata/ubi-7.json.golden | 3 +- .../ubuntu-1804-ignore-unfixed.json.golden | 12 +- integration/testdata/ubuntu-1804.json.golden | 15 +- integration/testdata/yarn.json.golden | 6 +- integration/vm_test.go | 4 +- pkg/fanal/applier/applier_test.go | 22 + pkg/fanal/applier/docker.go | 48 +- pkg/fanal/applier/docker_test.go | 120 ++++ pkg/fanal/artifact/vm/vm_test.go | 2 +- .../goldens/packages/alpine-310.json.golden | 42 +- .../goldens/packages/vulnimage.json.golden | 174 +++-- .../vuln-image1.2.3.expectedlibs.golden | 630 +++++++++++------ pkg/fanal/types/artifact.go | 3 +- pkg/rpc/convert.go | 4 +- pkg/rpc/convert_test.go | 12 + rpc/common/service.pb.go | 633 +++++++++--------- rpc/common/service.proto | 1 + 71 files changed, 1531 insertions(+), 874 deletions(-) diff --git a/Dockerfile.protoc b/Dockerfile.protoc index 9b21ed16a55d..f87c333562b9 100644 --- a/Dockerfile.protoc +++ b/Dockerfile.protoc @@ -14,7 +14,7 @@ RUN curl --retry 5 -OL https://github.com/protocolbuffers/protobuf/releases/down # Install Go tools RUN go install github.com/twitchtv/twirp/protoc-gen-twirp@v8.1.0 -RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.1 -RUN go install github.com/magefile/mage@v1.14.0 +RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.0 +RUN go install github.com/magefile/mage@v1.15.0 ENV TRIVY_PROTOC_CONTAINER=true diff --git a/integration/client_server_test.go b/integration/client_server_test.go index f217021658ae..f6b25c6cc3ef 100644 --- a/integration/client_server_test.go +++ b/integration/client_server_test.go @@ -283,7 +283,9 @@ func TestClientServer(t *testing.T) { osArgs = append(osArgs, "--secret-config", tt.args.secretConfig) } - runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{}) + runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{ + override: overrideUID, + }) }) } } @@ -397,7 +399,9 @@ func TestClientServerWithFormat(t *testing.T) { t.Setenv("AWS_ACCOUNT_ID", "123456789012") osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) - runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{}) + runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{ + override: overrideUID, + }) }) } } @@ -475,7 +479,10 @@ func TestClientServerWithToken(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) - runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{wantErr: tt.wantErr}) + runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{ + override: overrideUID, + wantErr: tt.wantErr, + }) }) } } @@ -501,7 +508,9 @@ func TestClientServerWithRedis(t *testing.T) { osArgs := setupClient(t, testArgs, addr, cacheDir, golden) // Run Trivy client - runTest(t, osArgs, golden, "", types.FormatJSON, runOptions{}) + runTest(t, osArgs, golden, "", types.FormatJSON, runOptions{ + override: overrideUID, + }) }) // Terminate the Redis container diff --git a/integration/integration_test.go b/integration/integration_test.go index 43fe3ac8c820..80f0d96119fb 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -192,9 +192,10 @@ func readSpdxJson(t *testing.T, filePath string) *spdx.Document { return bom } +type OverrideFunc func(t *testing.T, want, got *types.Report) type runOptions struct { wantErr string - override func(want, got *types.Report) + override OverrideFunc fakeUUID string } @@ -262,11 +263,11 @@ func compareRawFiles(t *testing.T, wantFile, gotFile string) { assert.EqualValues(t, string(want), string(got)) } -func compareReports(t *testing.T, wantFile, gotFile string, override func(want, got *types.Report)) { +func compareReports(t *testing.T, wantFile, gotFile string, override func(t *testing.T, want, got *types.Report)) { want := readReport(t, wantFile) got := readReport(t, gotFile) if override != nil { - override(&want, &got) + override(t, &want, &got) } assert.Equal(t, want, got) } @@ -307,3 +308,33 @@ func validateReport(t *testing.T, schema string, report any) { assert.True(t, valid, strings.Join(errs, "\n")) } } + +func overrideFuncs(funcs ...OverrideFunc) OverrideFunc { + return func(t *testing.T, want, got *types.Report) { + for _, f := range funcs { + if f == nil { + continue + } + f(t, want, got) + } + } +} + +// overrideUID only checks for the presence of the package UID and clears the UID; +// the UID is calculated from the package metadata, but the UID does not match +// as it varies slightly depending on the mode of scanning, e.g. the digest of the layer. +func overrideUID(t *testing.T, want, got *types.Report) { + for i, result := range got.Results { + for j, vuln := range result.Vulnerabilities { + assert.NotEmptyf(t, vuln.PkgIdentifier.UID, "UID is empty: %s", vuln.VulnerabilityID) + // Do not compare UID as the package metadata is slightly different between the tests, + // causing different UIDs. + got.Results[i].Vulnerabilities[j].PkgIdentifier.UID = "" + } + } + for i, result := range want.Results { + for j := range result.Vulnerabilities { + want.Results[i].Vulnerabilities[j].PkgIdentifier.UID = "" + } + } +} diff --git a/integration/registry_test.go b/integration/registry_test.go index b62865667dc3..9a2570062e56 100644 --- a/integration/registry_test.go +++ b/integration/registry_test.go @@ -202,12 +202,12 @@ func TestRegistry(t *testing.T) { // Run Trivy runTest(t, osArgs, tc.golden, "", types.FormatJSON, runOptions{ wantErr: tc.wantErr, - override: func(_, got *types.Report) { + override: overrideFuncs(overrideUID, func(t *testing.T, _, got *types.Report) { got.ArtifactName = tc.imageName for i := range got.Results { got.Results[i].Target = fmt.Sprintf("%s (alpine 3.10.2)", tc.imageName) } - }, + }), }) }) } diff --git a/integration/repo_test.go b/integration/repo_test.go index 8d787104e63f..3aa2baff521e 100644 --- a/integration/repo_test.go +++ b/integration/repo_test.go @@ -37,7 +37,7 @@ func TestRepository(t *testing.T) { name string args args golden string - override func(want, got *types.Report) + override func(t *testing.T, want, got *types.Report) }{ { name: "gomod", @@ -378,7 +378,7 @@ func TestRepository(t *testing.T) { skipFiles: []string{"testdata/fixtures/repo/gomod/submod2/go.mod"}, }, golden: "testdata/gomod-skip.json.golden", - override: func(want, _ *types.Report) { + override: func(_ *testing.T, want, _ *types.Report) { want.ArtifactType = ftypes.ArtifactFilesystem }, }, @@ -392,7 +392,7 @@ func TestRepository(t *testing.T) { input: "testdata/fixtures/repo/custom-policy", }, golden: "testdata/dockerfile-custom-policies.json.golden", - override: func(want, got *types.Report) { + override: func(_ *testing.T, want, got *types.Report) { want.ArtifactType = ftypes.ArtifactFilesystem }, }, diff --git a/integration/sbom_test.go b/integration/sbom_test.go index 65c99f9e9600..428efe5cfec3 100644 --- a/integration/sbom_test.go +++ b/integration/sbom_test.go @@ -6,11 +6,11 @@ import ( "path/filepath" "testing" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" ) @@ -25,7 +25,7 @@ func TestSBOM(t *testing.T) { name string args args golden string - override types.Report + override OverrideFunc }{ { name: "centos7 cyclonedx", @@ -35,31 +35,17 @@ func TestSBOM(t *testing.T) { artifactType: "cyclonedx", }, golden: "testdata/centos-7.json.golden", - override: types.Report{ - ArtifactName: "testdata/fixtures/sbom/centos-7-cyclonedx.json", - ArtifactType: ftypes.ArtifactType("cyclonedx"), - Results: types.Results{ - { - Target: "testdata/fixtures/sbom/centos-7-cyclonedx.json (centos 7.6.1810)", - Vulnerabilities: []types.DetectedVulnerability{ - { - PkgIdentifier: ftypes.PkgIdentifier{ - BOMRef: "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810", - }, - }, - { - PkgIdentifier: ftypes.PkgIdentifier{ - BOMRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810", - }, - }, - { - PkgIdentifier: ftypes.PkgIdentifier{ - BOMRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810", - }, - }, - }, - }, - }, + override: func(t *testing.T, want, got *types.Report) { + want.ArtifactName = "testdata/fixtures/sbom/centos-7-cyclonedx.json" + want.ArtifactType = ftypes.ArtifactCycloneDX + + require.Len(t, got.Results, 1) + want.Results[0].Target = "testdata/fixtures/sbom/centos-7-cyclonedx.json (centos 7.6.1810)" + + require.Len(t, got.Results[0].Vulnerabilities, 3) + want.Results[0].Vulnerabilities[0].PkgIdentifier.BOMRef = "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810" + want.Results[0].Vulnerabilities[1].PkgIdentifier.BOMRef = "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810" + want.Results[0].Vulnerabilities[2].PkgIdentifier.BOMRef = "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810" }, }, { @@ -88,31 +74,17 @@ func TestSBOM(t *testing.T) { artifactType: "cyclonedx", }, golden: "testdata/centos-7.json.golden", - override: types.Report{ - ArtifactName: "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl", - ArtifactType: ftypes.ArtifactType("cyclonedx"), - Results: types.Results{ - { - Target: "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl (centos 7.6.1810)", - Vulnerabilities: []types.DetectedVulnerability{ - { - PkgIdentifier: ftypes.PkgIdentifier{ - BOMRef: "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810", - }, - }, - { - PkgIdentifier: ftypes.PkgIdentifier{ - BOMRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810", - }, - }, - { - PkgIdentifier: ftypes.PkgIdentifier{ - BOMRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810", - }, - }, - }, - }, - }, + override: func(t *testing.T, want, got *types.Report) { + want.ArtifactName = "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl" + want.ArtifactType = ftypes.ArtifactCycloneDX + + require.Len(t, got.Results, 1) + want.Results[0].Target = "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl (centos 7.6.1810)" + + require.Len(t, got.Results[0].Vulnerabilities, 3) + want.Results[0].Vulnerabilities[0].PkgIdentifier.BOMRef = "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810" + want.Results[0].Vulnerabilities[1].PkgIdentifier.BOMRef = "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810" + want.Results[0].Vulnerabilities[2].PkgIdentifier.BOMRef = "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810" }, }, { @@ -123,14 +95,12 @@ func TestSBOM(t *testing.T) { artifactType: "spdx", }, golden: "testdata/centos-7.json.golden", - override: types.Report{ - ArtifactName: "testdata/fixtures/sbom/centos-7-spdx.txt", - ArtifactType: ftypes.ArtifactType("spdx"), - Results: types.Results{ - { - Target: "testdata/fixtures/sbom/centos-7-spdx.txt (centos 7.6.1810)", - }, - }, + override: func(t *testing.T, want, got *types.Report) { + want.ArtifactName = "testdata/fixtures/sbom/centos-7-spdx.txt" + want.ArtifactType = ftypes.ArtifactSPDX + + require.Len(t, got.Results, 1) + want.Results[0].Target = "testdata/fixtures/sbom/centos-7-spdx.txt (centos 7.6.1810)" }, }, { @@ -141,14 +111,12 @@ func TestSBOM(t *testing.T) { artifactType: "spdx", }, golden: "testdata/centos-7.json.golden", - override: types.Report{ - ArtifactName: "testdata/fixtures/sbom/centos-7-spdx.json", - ArtifactType: ftypes.ArtifactType("spdx"), - Results: types.Results{ - { - Target: "testdata/fixtures/sbom/centos-7-spdx.json (centos 7.6.1810)", - }, - }, + override: func(t *testing.T, want, got *types.Report) { + want.ArtifactName = "testdata/fixtures/sbom/centos-7-spdx.json" + want.ArtifactType = ftypes.ArtifactSPDX + + require.Len(t, got.Results, 1) + want.Results[0].Target = "testdata/fixtures/sbom/centos-7-spdx.json (centos 7.6.1810)" }, }, { @@ -195,20 +163,30 @@ func TestSBOM(t *testing.T) { osArgs = append(osArgs, tt.args.input) // Run "trivy sbom" - err := execute(osArgs) - assert.NoError(t, err) - - // Compare want and got - switch tt.args.format { - case "json": - compareSBOMReports(t, tt.golden, outputFile, tt.override) - default: - require.Fail(t, "invalid format", "format: %s", tt.args.format) - } + runTest(t, osArgs, tt.golden, outputFile, types.Format(tt.args.format), runOptions{ + override: overrideFuncs(overrideSBOMReport, overrideUID, tt.override), + }) }) } } +func overrideSBOMReport(t *testing.T, want, got *types.Report) { + want.Metadata.ImageID = "" + want.Metadata.ImageConfig = v1.ConfigFile{} + want.Metadata.DiffIDs = nil + for i, result := range want.Results { + for j := range result.Vulnerabilities { + want.Results[i].Vulnerabilities[j].Layer.DiffID = "" + } + } + + // when running on Windows FS + got.ArtifactName = filepath.ToSlash(filepath.Clean(got.ArtifactName)) + for i, result := range got.Results { + got.Results[i].Target = filepath.ToSlash(filepath.Clean(result.Target)) + } +} + // TODO(teppei): merge into compareReports func compareSBOMReports(t *testing.T, wantFile, gotFile string, overrideWant types.Report) { want := readReport(t, wantFile) diff --git a/integration/testdata/almalinux-8.json.golden b/integration/testdata/almalinux-8.json.golden index 409e02d6e9bd..3f513e20b9f8 100644 --- a/integration/testdata/almalinux-8.json.golden +++ b/integration/testdata/almalinux-8.json.golden @@ -57,7 +57,8 @@ "PkgID": "openssl-libs@1.1.1k-4.el8.x86_64", "PkgName": "openssl-libs", "PkgIdentifier": { - "PURL": "pkg:rpm/alma/openssl-libs@1.1.1k-4.el8?arch=x86_64\u0026distro=alma-8.5\u0026epoch=1" + "PURL": "pkg:rpm/alma/openssl-libs@1.1.1k-4.el8?arch=x86_64\u0026distro=alma-8.5\u0026epoch=1", + "UID": "3f965238234faa63" }, "InstalledVersion": "1:1.1.1k-4.el8", "FixedVersion": "1:1.1.1k-5.el8_5", diff --git a/integration/testdata/alpine-310.json.golden b/integration/testdata/alpine-310.json.golden index d6b4a7884027..35010bb44794 100644 --- a/integration/testdata/alpine-310.json.golden +++ b/integration/testdata/alpine-310.json.golden @@ -59,7 +59,8 @@ "PkgID": "libcrypto1.1@1.1.1c-r0", "PkgName": "libcrypto1.1", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2", + "UID": "c6c116a4441ec6de" }, "InstalledVersion": "1.1.1c-r0", "FixedVersion": "1.1.1d-r0", @@ -131,7 +132,8 @@ "PkgID": "libcrypto1.1@1.1.1c-r0", "PkgName": "libcrypto1.1", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2", + "UID": "c6c116a4441ec6de" }, "InstalledVersion": "1.1.1c-r0", "FixedVersion": "1.1.1d-r2", @@ -213,7 +215,8 @@ "PkgID": "libssl1.1@1.1.1c-r0", "PkgName": "libssl1.1", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2", + "UID": "e132dcfcc51772ef" }, "InstalledVersion": "1.1.1c-r0", "FixedVersion": "1.1.1d-r0", @@ -285,7 +288,8 @@ "PkgID": "libssl1.1@1.1.1c-r0", "PkgName": "libssl1.1", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2", + "UID": "e132dcfcc51772ef" }, "InstalledVersion": "1.1.1c-r0", "FixedVersion": "1.1.1d-r2", diff --git a/integration/testdata/alpine-39-high-critical.json.golden b/integration/testdata/alpine-39-high-critical.json.golden index 73288f579caa..408cd11d4988 100644 --- a/integration/testdata/alpine-39-high-critical.json.golden +++ b/integration/testdata/alpine-39-high-critical.json.golden @@ -59,7 +59,8 @@ "PkgID": "musl@1.1.20-r4", "PkgName": "musl", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/musl@1.1.20-r4?arch=x86_64\u0026distro=3.9.4" + "PURL": "pkg:apk/alpine/musl@1.1.20-r4?arch=x86_64\u0026distro=3.9.4", + "UID": "d6abd271e71d3ce2" }, "InstalledVersion": "1.1.20-r4", "FixedVersion": "1.1.20-r5", @@ -104,7 +105,8 @@ "PkgID": "musl-utils@1.1.20-r4", "PkgName": "musl-utils", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/musl-utils@1.1.20-r4?arch=x86_64\u0026distro=3.9.4" + "PURL": "pkg:apk/alpine/musl-utils@1.1.20-r4?arch=x86_64\u0026distro=3.9.4", + "UID": "8c341199f4077fc8" }, "InstalledVersion": "1.1.20-r4", "FixedVersion": "1.1.20-r5", diff --git a/integration/testdata/alpine-39-ignore-cveids.json.golden b/integration/testdata/alpine-39-ignore-cveids.json.golden index f11198c2364e..14cda282c1aa 100644 --- a/integration/testdata/alpine-39-ignore-cveids.json.golden +++ b/integration/testdata/alpine-39-ignore-cveids.json.golden @@ -59,7 +59,8 @@ "PkgID": "libcrypto1.1@1.1.1b-r1", "PkgName": "libcrypto1.1", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4" + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4", + "UID": "d2c46e721bca75d3" }, "InstalledVersion": "1.1.1b-r1", "FixedVersion": "1.1.1d-r2", @@ -141,7 +142,8 @@ "PkgID": "libssl1.1@1.1.1b-r1", "PkgName": "libssl1.1", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libssl1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4" + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4", + "UID": "e39a91b0fefcbb1d" }, "InstalledVersion": "1.1.1b-r1", "FixedVersion": "1.1.1d-r2", diff --git a/integration/testdata/alpine-39.json.golden b/integration/testdata/alpine-39.json.golden index 303f7a3c7277..3e1089f3e7cb 100644 --- a/integration/testdata/alpine-39.json.golden +++ b/integration/testdata/alpine-39.json.golden @@ -59,7 +59,8 @@ "PkgID": "libcrypto1.1@1.1.1b-r1", "PkgName": "libcrypto1.1", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4" + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4", + "UID": "d2c46e721bca75d3" }, "InstalledVersion": "1.1.1b-r1", "FixedVersion": "1.1.1d-r0", @@ -131,7 +132,8 @@ "PkgID": "libcrypto1.1@1.1.1b-r1", "PkgName": "libcrypto1.1", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4" + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4", + "UID": "d2c46e721bca75d3" }, "InstalledVersion": "1.1.1b-r1", "FixedVersion": "1.1.1d-r2", @@ -213,7 +215,8 @@ "PkgID": "libssl1.1@1.1.1b-r1", "PkgName": "libssl1.1", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libssl1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4" + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4", + "UID": "e39a91b0fefcbb1d" }, "InstalledVersion": "1.1.1b-r1", "FixedVersion": "1.1.1d-r0", @@ -285,7 +288,8 @@ "PkgID": "libssl1.1@1.1.1b-r1", "PkgName": "libssl1.1", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/libssl1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4" + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1b-r1?arch=x86_64\u0026distro=3.9.4", + "UID": "e39a91b0fefcbb1d" }, "InstalledVersion": "1.1.1b-r1", "FixedVersion": "1.1.1d-r2", @@ -367,7 +371,8 @@ "PkgID": "musl@1.1.20-r4", "PkgName": "musl", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/musl@1.1.20-r4?arch=x86_64\u0026distro=3.9.4" + "PURL": "pkg:apk/alpine/musl@1.1.20-r4?arch=x86_64\u0026distro=3.9.4", + "UID": "d6abd271e71d3ce2" }, "InstalledVersion": "1.1.20-r4", "FixedVersion": "1.1.20-r5", @@ -412,7 +417,8 @@ "PkgID": "musl-utils@1.1.20-r4", "PkgName": "musl-utils", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/musl-utils@1.1.20-r4?arch=x86_64\u0026distro=3.9.4" + "PURL": "pkg:apk/alpine/musl-utils@1.1.20-r4?arch=x86_64\u0026distro=3.9.4", + "UID": "8c341199f4077fc8" }, "InstalledVersion": "1.1.20-r4", "FixedVersion": "1.1.20-r5", diff --git a/integration/testdata/alpine-distroless.json.golden b/integration/testdata/alpine-distroless.json.golden index 8f79cba7610c..4ba010f0eea0 100644 --- a/integration/testdata/alpine-distroless.json.golden +++ b/integration/testdata/alpine-distroless.json.golden @@ -54,7 +54,8 @@ "PkgID": "git@2.35.1-r2", "PkgName": "git", "PkgIdentifier": { - "PURL": "pkg:apk/alpine/git@2.35.1-r2?arch=x86_64\u0026distro=3.16" + "PURL": "pkg:apk/alpine/git@2.35.1-r2?arch=x86_64\u0026distro=3.16", + "UID": "d44ac4666246b919" }, "InstalledVersion": "2.35.1-r2", "FixedVersion": "2.35.2-r0", diff --git a/integration/testdata/amazon-1.json.golden b/integration/testdata/amazon-1.json.golden index 6be4b41ffe3b..6472eb1859d2 100644 --- a/integration/testdata/amazon-1.json.golden +++ b/integration/testdata/amazon-1.json.golden @@ -58,7 +58,8 @@ "PkgID": "curl@7.61.1-11.91.amzn1.x86_64", "PkgName": "curl", "PkgIdentifier": { - "PURL": "pkg:rpm/amazon/curl@7.61.1-11.91.amzn1?arch=x86_64\u0026distro=amazon-AMI+release+2018.03" + "PURL": "pkg:rpm/amazon/curl@7.61.1-11.91.amzn1?arch=x86_64\u0026distro=amazon-AMI+release+2018.03", + "UID": "9fafb1be522b1e7" }, "InstalledVersion": "7.61.1-11.91.amzn1", "FixedVersion": "7.61.1-12.93.amzn1", diff --git a/integration/testdata/amazon-2.json.golden b/integration/testdata/amazon-2.json.golden index 1f20d8d37b68..530f2b6daf69 100644 --- a/integration/testdata/amazon-2.json.golden +++ b/integration/testdata/amazon-2.json.golden @@ -58,7 +58,8 @@ "PkgID": "curl@7.61.1-9.amzn2.0.1.x86_64", "PkgName": "curl", "PkgIdentifier": { - "PURL": "pkg:rpm/amazon/curl@7.61.1-9.amzn2.0.1?arch=x86_64\u0026distro=amazon-2+%28Karoo%29" + "PURL": "pkg:rpm/amazon/curl@7.61.1-9.amzn2.0.1?arch=x86_64\u0026distro=amazon-2+%28Karoo%29", + "UID": "c5998529d683c5c3" }, "InstalledVersion": "7.61.1-9.amzn2.0.1", "FixedVersion": "7.61.1-12.amzn2.0.1", @@ -129,7 +130,8 @@ "PkgID": "curl@7.61.1-9.amzn2.0.1.x86_64", "PkgName": "curl", "PkgIdentifier": { - "PURL": "pkg:rpm/amazon/curl@7.61.1-9.amzn2.0.1?arch=x86_64\u0026distro=amazon-2+%28Karoo%29" + "PURL": "pkg:rpm/amazon/curl@7.61.1-9.amzn2.0.1?arch=x86_64\u0026distro=amazon-2+%28Karoo%29", + "UID": "c5998529d683c5c3" }, "InstalledVersion": "7.61.1-9.amzn2.0.1", "FixedVersion": "7.61.1-11.amzn2.0.2", diff --git a/integration/testdata/busybox-with-lockfile.json.golden b/integration/testdata/busybox-with-lockfile.json.golden index 520afe2de928..1420c166d012 100644 --- a/integration/testdata/busybox-with-lockfile.json.golden +++ b/integration/testdata/busybox-with-lockfile.json.golden @@ -58,7 +58,8 @@ "PkgID": "ammonia@1.9.0", "PkgName": "ammonia", "PkgIdentifier": { - "PURL": "pkg:cargo/ammonia@1.9.0" + "PURL": "pkg:cargo/ammonia@1.9.0", + "UID": "fa518cac41270ffe" }, "InstalledVersion": "1.9.0", "FixedVersion": "\u003e= 2.1.0", @@ -103,7 +104,8 @@ "PkgID": "ammonia@1.9.0", "PkgName": "ammonia", "PkgIdentifier": { - "PURL": "pkg:cargo/ammonia@1.9.0" + "PURL": "pkg:cargo/ammonia@1.9.0", + "UID": "fa518cac41270ffe" }, "InstalledVersion": "1.9.0", "FixedVersion": "\u003e= 3.1.0, \u003e= 2.1.3, \u003c 3.0.0", diff --git a/integration/testdata/centos-6.json.golden b/integration/testdata/centos-6.json.golden index c1791c58a486..aefd6f2652e0 100644 --- a/integration/testdata/centos-6.json.golden +++ b/integration/testdata/centos-6.json.golden @@ -80,7 +80,8 @@ "PkgID": "glibc@2.12-1.212.el6.x86_64", "PkgName": "glibc", "PkgIdentifier": { - "PURL": "pkg:rpm/centos/glibc@2.12-1.212.el6?arch=x86_64\u0026distro=centos-6.10" + "PURL": "pkg:rpm/centos/glibc@2.12-1.212.el6?arch=x86_64\u0026distro=centos-6.10", + "UID": "24b11591bb7262c4" }, "InstalledVersion": "2.12-1.212.el6", "Status": "end_of_life", @@ -136,7 +137,8 @@ "PkgID": "openssl@1.0.1e-57.el6.x86_64", "PkgName": "openssl", "PkgIdentifier": { - "PURL": "pkg:rpm/centos/openssl@1.0.1e-57.el6?arch=x86_64\u0026distro=centos-6.10" + "PURL": "pkg:rpm/centos/openssl@1.0.1e-57.el6?arch=x86_64\u0026distro=centos-6.10", + "UID": "935959fd0ed81eb9" }, "InstalledVersion": "1.0.1e-57.el6", "FixedVersion": "1.0.1e-58.el6_10", diff --git a/integration/testdata/centos-7-ignore-unfixed.json.golden b/integration/testdata/centos-7-ignore-unfixed.json.golden index ad8379a5d80e..9a5deaa4e5fa 100644 --- a/integration/testdata/centos-7-ignore-unfixed.json.golden +++ b/integration/testdata/centos-7-ignore-unfixed.json.golden @@ -73,7 +73,8 @@ "PkgID": "openssl-libs@1.0.2k-16.el7.x86_64", "PkgName": "openssl-libs", "PkgIdentifier": { - "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1" + "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1", + "UID": "20f09cdcea6545a2" }, "InstalledVersion": "1:1.0.2k-16.el7", "FixedVersion": "1:1.0.2k-19.el7", @@ -166,7 +167,8 @@ "PkgID": "openssl-libs@1.0.2k-16.el7.x86_64", "PkgName": "openssl-libs", "PkgIdentifier": { - "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1" + "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1", + "UID": "20f09cdcea6545a2" }, "InstalledVersion": "1:1.0.2k-16.el7", "FixedVersion": "1:1.0.2k-19.el7", diff --git a/integration/testdata/centos-7-medium.json.golden b/integration/testdata/centos-7-medium.json.golden index ef4a44d2bbe5..479640858f19 100644 --- a/integration/testdata/centos-7-medium.json.golden +++ b/integration/testdata/centos-7-medium.json.golden @@ -73,7 +73,8 @@ "PkgID": "openssl-libs@1.0.2k-16.el7.x86_64", "PkgName": "openssl-libs", "PkgIdentifier": { - "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1" + "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1", + "UID": "20f09cdcea6545a2" }, "InstalledVersion": "1:1.0.2k-16.el7", "FixedVersion": "1:1.0.2k-19.el7", diff --git a/integration/testdata/centos-7.json.golden b/integration/testdata/centos-7.json.golden index 55ea768c99a8..d130399fcd53 100644 --- a/integration/testdata/centos-7.json.golden +++ b/integration/testdata/centos-7.json.golden @@ -70,7 +70,8 @@ "PkgID": "bash@4.2.46-31.el7.x86_64", "PkgName": "bash", "PkgIdentifier": { - "PURL": "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64\u0026distro=centos-7.6.1810" + "PURL": "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64\u0026distro=centos-7.6.1810", + "UID": "64aff37eb11b9c25" }, "InstalledVersion": "4.2.46-31.el7", "Status": "will_not_fix", @@ -130,7 +131,8 @@ "PkgID": "openssl-libs@1.0.2k-16.el7.x86_64", "PkgName": "openssl-libs", "PkgIdentifier": { - "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1" + "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1", + "UID": "20f09cdcea6545a2" }, "InstalledVersion": "1:1.0.2k-16.el7", "FixedVersion": "1:1.0.2k-19.el7", @@ -223,7 +225,8 @@ "PkgID": "openssl-libs@1.0.2k-16.el7.x86_64", "PkgName": "openssl-libs", "PkgIdentifier": { - "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1" + "PURL": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026distro=centos-7.6.1810\u0026epoch=1", + "UID": "20f09cdcea6545a2" }, "InstalledVersion": "1:1.0.2k-16.el7", "FixedVersion": "1:1.0.2k-19.el7", diff --git a/integration/testdata/cocoapods.json.golden b/integration/testdata/cocoapods.json.golden index 9553f0624286..7b8af93265b0 100644 --- a/integration/testdata/cocoapods.json.golden +++ b/integration/testdata/cocoapods.json.golden @@ -25,7 +25,8 @@ "ID": "_NIODataStructures@2.41.0", "Name": "_NIODataStructures", "Identifier": { - "PURL": "pkg:cocoapods/_NIODataStructures@2.41.0" + "PURL": "pkg:cocoapods/_NIODataStructures@2.41.0", + "UID": "ddc948d6b5e15241" }, "Version": "2.41.0", "Layer": {} @@ -37,7 +38,8 @@ "PkgID": "_NIODataStructures@2.41.0", "PkgName": "_NIODataStructures", "PkgIdentifier": { - "PURL": "pkg:cocoapods/_NIODataStructures@2.41.0" + "PURL": "pkg:cocoapods/_NIODataStructures@2.41.0", + "UID": "ddc948d6b5e15241" }, "InstalledVersion": "2.41.0", "FixedVersion": "2.29.1, 2.39.1, 2.42.0", diff --git a/integration/testdata/composer.lock.json.golden b/integration/testdata/composer.lock.json.golden index b2a341f96e31..5e2e49d8b965 100644 --- a/integration/testdata/composer.lock.json.golden +++ b/integration/testdata/composer.lock.json.golden @@ -25,7 +25,8 @@ "ID": "guzzlehttp/guzzle@7.4.4", "Name": "guzzlehttp/guzzle", "Identifier": { - "PURL": "pkg:composer/guzzlehttp/guzzle@7.4.4" + "PURL": "pkg:composer/guzzlehttp/guzzle@7.4.4", + "UID": "c26bf8868607a91c" }, "Version": "7.4.4", "Licenses": [ @@ -47,7 +48,8 @@ "ID": "guzzlehttp/psr7@1.8.3", "Name": "guzzlehttp/psr7", "Identifier": { - "PURL": "pkg:composer/guzzlehttp/psr7@1.8.3" + "PURL": "pkg:composer/guzzlehttp/psr7@1.8.3", + "UID": "1730859e3ff83ab9" }, "Version": "1.8.3", "Licenses": [ @@ -70,7 +72,8 @@ "PkgID": "guzzlehttp/psr7@1.8.3", "PkgName": "guzzlehttp/psr7", "PkgIdentifier": { - "PURL": "pkg:composer/guzzlehttp/psr7@1.8.3" + "PURL": "pkg:composer/guzzlehttp/psr7@1.8.3", + "UID": "1730859e3ff83ab9" }, "InstalledVersion": "1.8.3", "FixedVersion": "1.8.4", diff --git a/integration/testdata/conan.json.golden b/integration/testdata/conan.json.golden index 4de31b4c3a87..8cee1572ba19 100644 --- a/integration/testdata/conan.json.golden +++ b/integration/testdata/conan.json.golden @@ -25,7 +25,8 @@ "ID": "bzip2/1.0.8", "Name": "bzip2", "Identifier": { - "PURL": "pkg:conan/bzip2@1.0.8" + "PURL": "pkg:conan/bzip2@1.0.8", + "UID": "6e2ff993df2d9107" }, "Version": "1.0.8", "Indirect": true, @@ -42,7 +43,8 @@ "ID": "expat/2.4.8", "Name": "expat", "Identifier": { - "PURL": "pkg:conan/expat@2.4.8" + "PURL": "pkg:conan/expat@2.4.8", + "UID": "71c2d92d60f7f21c" }, "Version": "2.4.8", "Indirect": true, @@ -59,7 +61,8 @@ "ID": "openssl/1.1.1q", "Name": "openssl", "Identifier": { - "PURL": "pkg:conan/openssl@1.1.1q" + "PURL": "pkg:conan/openssl@1.1.1q", + "UID": "13c605db6afa69dd" }, "Version": "1.1.1q", "Indirect": true, @@ -76,7 +79,8 @@ "ID": "pcre/8.43", "Name": "pcre", "Identifier": { - "PURL": "pkg:conan/pcre@8.43" + "PURL": "pkg:conan/pcre@8.43", + "UID": "4e01c692a67e12e4" }, "Version": "8.43", "Indirect": true, @@ -97,7 +101,8 @@ "ID": "poco/1.9.4", "Name": "poco", "Identifier": { - "PURL": "pkg:conan/poco@1.9.4" + "PURL": "pkg:conan/poco@1.9.4", + "UID": "312753cebe80c0eb" }, "Version": "1.9.4", "Relationship": "direct", @@ -120,7 +125,8 @@ "ID": "sqlite3/3.39.2", "Name": "sqlite3", "Identifier": { - "PURL": "pkg:conan/sqlite3@3.39.2" + "PURL": "pkg:conan/sqlite3@3.39.2", + "UID": "43bc9c58092c7c9e" }, "Version": "3.39.2", "Indirect": true, @@ -137,7 +143,8 @@ "ID": "zlib/1.2.12", "Name": "zlib", "Identifier": { - "PURL": "pkg:conan/zlib@1.2.12" + "PURL": "pkg:conan/zlib@1.2.12", + "UID": "d6faf8d6dfd1985" }, "Version": "1.2.12", "Indirect": true, @@ -157,7 +164,8 @@ "PkgID": "pcre/8.43", "PkgName": "pcre", "PkgIdentifier": { - "PURL": "pkg:conan/pcre@8.43" + "PURL": "pkg:conan/pcre@8.43", + "UID": "4e01c692a67e12e4" }, "InstalledVersion": "8.43", "FixedVersion": "8.45", diff --git a/integration/testdata/debian-buster-ignore-unfixed.json.golden b/integration/testdata/debian-buster-ignore-unfixed.json.golden index 0d387db18fbf..cb5a606e3a94 100644 --- a/integration/testdata/debian-buster-ignore-unfixed.json.golden +++ b/integration/testdata/debian-buster-ignore-unfixed.json.golden @@ -61,7 +61,8 @@ "PkgID": "libidn2-0@2.0.5-1", "PkgName": "libidn2-0", "PkgIdentifier": { - "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64\u0026distro=debian-10.1" + "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64\u0026distro=debian-10.1", + "UID": "473f5eb9e3d4a2f2" }, "InstalledVersion": "2.0.5-1", "FixedVersion": "2.0.5-1+deb10u1", diff --git a/integration/testdata/debian-buster.json.golden b/integration/testdata/debian-buster.json.golden index 739158b5fe6e..4c9d64fc8347 100644 --- a/integration/testdata/debian-buster.json.golden +++ b/integration/testdata/debian-buster.json.golden @@ -58,7 +58,8 @@ "PkgID": "bash@5.0-4", "PkgName": "bash", "PkgIdentifier": { - "PURL": "pkg:deb/debian/bash@5.0-4?arch=amd64\u0026distro=debian-10.1" + "PURL": "pkg:deb/debian/bash@5.0-4?arch=amd64\u0026distro=debian-10.1", + "UID": "d45ab8ae65ffe67" }, "InstalledVersion": "5.0-4", "Status": "affected", @@ -124,7 +125,8 @@ "PkgID": "libidn2-0@2.0.5-1", "PkgName": "libidn2-0", "PkgIdentifier": { - "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64\u0026distro=debian-10.1" + "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64\u0026distro=debian-10.1", + "UID": "473f5eb9e3d4a2f2" }, "InstalledVersion": "2.0.5-1", "FixedVersion": "2.0.5-1+deb10u1", diff --git a/integration/testdata/debian-stretch.json.golden b/integration/testdata/debian-stretch.json.golden index ed15dd42381f..e4be6f91f1f7 100644 --- a/integration/testdata/debian-stretch.json.golden +++ b/integration/testdata/debian-stretch.json.golden @@ -58,7 +58,8 @@ "PkgID": "bash@4.4-5", "PkgName": "bash", "PkgIdentifier": { - "PURL": "pkg:deb/debian/bash@4.4-5?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/bash@4.4-5?arch=amd64\u0026distro=debian-9.9", + "UID": "6100d09336f565a0" }, "InstalledVersion": "4.4-5", "Status": "end_of_life", @@ -124,7 +125,8 @@ "PkgID": "e2fslibs@1.43.4-2", "PkgName": "e2fslibs", "PkgIdentifier": { - "PURL": "pkg:deb/debian/e2fslibs@1.43.4-2?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/e2fslibs@1.43.4-2?arch=amd64\u0026distro=debian-9.9", + "UID": "656652ce5818f7b6" }, "InstalledVersion": "1.43.4-2", "FixedVersion": "1.43.4-2+deb9u1", @@ -197,7 +199,8 @@ "PkgID": "e2fsprogs@1.43.4-2", "PkgName": "e2fsprogs", "PkgIdentifier": { - "PURL": "pkg:deb/debian/e2fsprogs@1.43.4-2?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/e2fsprogs@1.43.4-2?arch=amd64\u0026distro=debian-9.9", + "UID": "3d19fd957338dc06" }, "InstalledVersion": "1.43.4-2", "FixedVersion": "1.43.4-2+deb9u1", @@ -270,7 +273,8 @@ "PkgID": "libcomerr2@1.43.4-2", "PkgName": "libcomerr2", "PkgIdentifier": { - "PURL": "pkg:deb/debian/libcomerr2@1.43.4-2?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/libcomerr2@1.43.4-2?arch=amd64\u0026distro=debian-9.9", + "UID": "6ba1fac685a0c068" }, "InstalledVersion": "1.43.4-2", "FixedVersion": "1.43.4-2+deb9u1", @@ -343,7 +347,8 @@ "PkgID": "libss2@1.43.4-2", "PkgName": "libss2", "PkgIdentifier": { - "PURL": "pkg:deb/debian/libss2@1.43.4-2?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/libss2@1.43.4-2?arch=amd64\u0026distro=debian-9.9", + "UID": "e507c185f61cd2e8" }, "InstalledVersion": "1.43.4-2", "FixedVersion": "1.43.4-2+deb9u1", diff --git a/integration/testdata/distroless-base.json.golden b/integration/testdata/distroless-base.json.golden index 0bd390a36f54..c5872a901494 100644 --- a/integration/testdata/distroless-base.json.golden +++ b/integration/testdata/distroless-base.json.golden @@ -56,7 +56,8 @@ "PkgID": "libssl1.1@1.1.0k-1~deb9u1", "PkgName": "libssl1.1", "PkgIdentifier": { - "PURL": "pkg:deb/debian/libssl1.1@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/libssl1.1@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9", + "UID": "96b92444b87304a5" }, "InstalledVersion": "1.1.0k-1~deb9u1", "Status": "affected", @@ -140,7 +141,8 @@ "PkgID": "libssl1.1@1.1.0k-1~deb9u1", "PkgName": "libssl1.1", "PkgIdentifier": { - "PURL": "pkg:deb/debian/libssl1.1@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/libssl1.1@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9", + "UID": "96b92444b87304a5" }, "InstalledVersion": "1.1.0k-1~deb9u1", "FixedVersion": "1.1.0l-1~deb9u1", @@ -230,7 +232,8 @@ "PkgID": "openssl@1.1.0k-1~deb9u1", "PkgName": "openssl", "PkgIdentifier": { - "PURL": "pkg:deb/debian/openssl@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/openssl@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9", + "UID": "ed86402b9a8c2be6" }, "InstalledVersion": "1.1.0k-1~deb9u1", "Status": "affected", @@ -314,7 +317,8 @@ "PkgID": "openssl@1.1.0k-1~deb9u1", "PkgName": "openssl", "PkgIdentifier": { - "PURL": "pkg:deb/debian/openssl@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/openssl@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9", + "UID": "ed86402b9a8c2be6" }, "InstalledVersion": "1.1.0k-1~deb9u1", "FixedVersion": "1.1.0l-1~deb9u1", diff --git a/integration/testdata/distroless-python27.json.golden b/integration/testdata/distroless-python27.json.golden index 8c0657976b97..403b34c2dba8 100644 --- a/integration/testdata/distroless-python27.json.golden +++ b/integration/testdata/distroless-python27.json.golden @@ -73,7 +73,8 @@ "PkgID": "libssl1.1@1.1.0k-1~deb9u1", "PkgName": "libssl1.1", "PkgIdentifier": { - "PURL": "pkg:deb/debian/libssl1.1@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/libssl1.1@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9", + "UID": "96b92444b87304a5" }, "InstalledVersion": "1.1.0k-1~deb9u1", "Status": "affected", @@ -157,7 +158,8 @@ "PkgID": "libssl1.1@1.1.0k-1~deb9u1", "PkgName": "libssl1.1", "PkgIdentifier": { - "PURL": "pkg:deb/debian/libssl1.1@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/libssl1.1@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9", + "UID": "96b92444b87304a5" }, "InstalledVersion": "1.1.0k-1~deb9u1", "FixedVersion": "1.1.0l-1~deb9u1", @@ -247,7 +249,8 @@ "PkgID": "openssl@1.1.0k-1~deb9u1", "PkgName": "openssl", "PkgIdentifier": { - "PURL": "pkg:deb/debian/openssl@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/openssl@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9", + "UID": "ed86402b9a8c2be6" }, "InstalledVersion": "1.1.0k-1~deb9u1", "Status": "affected", @@ -331,7 +334,8 @@ "PkgID": "openssl@1.1.0k-1~deb9u1", "PkgName": "openssl", "PkgIdentifier": { - "PURL": "pkg:deb/debian/openssl@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9" + "PURL": "pkg:deb/debian/openssl@1.1.0k-1~deb9u1?arch=amd64\u0026distro=debian-9.9", + "UID": "ed86402b9a8c2be6" }, "InstalledVersion": "1.1.0k-1~deb9u1", "FixedVersion": "1.1.0l-1~deb9u1", diff --git a/integration/testdata/dotnet.json.golden b/integration/testdata/dotnet.json.golden index 264b28d7534d..778b1270fcf2 100644 --- a/integration/testdata/dotnet.json.golden +++ b/integration/testdata/dotnet.json.golden @@ -24,7 +24,8 @@ { "Name": "Newtonsoft.Json", "Identifier": { - "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1" + "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1", + "UID": "19955f480b8a6340" }, "Version": "9.0.1", "Layer": {}, @@ -41,7 +42,8 @@ "VulnerabilityID": "GHSA-5crp-9r3c-p9vr", "PkgName": "Newtonsoft.Json", "PkgIdentifier": { - "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1" + "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1", + "UID": "19955f480b8a6340" }, "InstalledVersion": "9.0.1", "FixedVersion": "13.0.1", diff --git a/integration/testdata/fluentd-gems.json.golden b/integration/testdata/fluentd-gems.json.golden index 2072e0c4bf8d..ef3a04bb7a50 100644 --- a/integration/testdata/fluentd-gems.json.golden +++ b/integration/testdata/fluentd-gems.json.golden @@ -114,7 +114,8 @@ "PkgID": "libidn2-0@2.0.5-1", "PkgName": "libidn2-0", "PkgIdentifier": { - "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64\u0026distro=debian-10.2" + "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64\u0026distro=debian-10.2", + "UID": "14f80a7091a08e71" }, "InstalledVersion": "2.0.5-1", "FixedVersion": "2.0.5-1+deb10u1", @@ -185,7 +186,8 @@ "PkgName": "activesupport", "PkgPath": "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", "PkgIdentifier": { - "PURL": "pkg:gem/activesupport@6.0.2.1" + "PURL": "pkg:gem/activesupport@6.0.2.1", + "UID": "dedd4bd33ed812a3" }, "InstalledVersion": "6.0.2.1", "FixedVersion": "6.0.3.1, 5.2.4.3", diff --git a/integration/testdata/fluentd-multiple-lockfiles.json.golden b/integration/testdata/fluentd-multiple-lockfiles.json.golden index 701c0262753d..fec0e1a39a0d 100644 --- a/integration/testdata/fluentd-multiple-lockfiles.json.golden +++ b/integration/testdata/fluentd-multiple-lockfiles.json.golden @@ -31,6 +31,7 @@ "PkgName": "bash", "PkgIdentifier": { "PURL": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", + "UID": "8ca99d0ea2f4b0a3", "BOMRef": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2" }, "InstalledVersion": "5.0-4", @@ -95,6 +96,7 @@ "PkgName": "libidn2-0", "PkgIdentifier": { "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", + "UID": "bd31ad93af9a5d2", "BOMRef": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2" }, "InstalledVersion": "2.0.5-1", @@ -165,6 +167,7 @@ "PkgPath": "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", "PkgIdentifier": { "PURL": "pkg:gem/activesupport@6.0.2.1", + "UID": "66a6de64809697cd", "BOMRef": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec" }, "InstalledVersion": "6.0.2.1", diff --git a/integration/testdata/gomod-skip.json.golden b/integration/testdata/gomod-skip.json.golden index ce748cc19823..69ab998ec9dd 100644 --- a/integration/testdata/gomod-skip.json.golden +++ b/integration/testdata/gomod-skip.json.golden @@ -26,7 +26,8 @@ "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", "PkgName": "github.com/docker/distribution", "PkgIdentifier": { - "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible" + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible", + "UID": "de19cd663ca047a8" }, "InstalledVersion": "2.7.1+incompatible", "FixedVersion": "v2.8.0", @@ -52,7 +53,8 @@ "PkgID": "github.com/open-policy-agent/opa@v0.35.0", "PkgName": "github.com/open-policy-agent/opa", "PkgIdentifier": { - "PURL": "pkg:golang/github.com/open-policy-agent/opa@0.35.0" + "PURL": "pkg:golang/github.com/open-policy-agent/opa@0.35.0", + "UID": "6b685002e082ffc5" }, "InstalledVersion": "0.35.0", "FixedVersion": "0.37.0", @@ -98,7 +100,8 @@ "PkgID": "golang.org/x/text@v0.3.6", "PkgName": "golang.org/x/text", "PkgIdentifier": { - "PURL": "pkg:golang/golang.org/x/text@0.3.6" + "PURL": "pkg:golang/golang.org/x/text@0.3.6", + "UID": "825dc613c0f39d45" }, "InstalledVersion": "0.3.6", "FixedVersion": "0.3.7", @@ -130,7 +133,8 @@ "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", "PkgName": "github.com/docker/distribution", "PkgIdentifier": { - "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible" + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible", + "UID": "94376dc37054a7e8" }, "InstalledVersion": "2.7.1+incompatible", "FixedVersion": "v2.8.0", diff --git a/integration/testdata/gomod.json.golden b/integration/testdata/gomod.json.golden index 5009b9d3bf81..627088188285 100644 --- a/integration/testdata/gomod.json.golden +++ b/integration/testdata/gomod.json.golden @@ -26,7 +26,8 @@ "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", "PkgName": "github.com/docker/distribution", "PkgIdentifier": { - "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible" + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible", + "UID": "de19cd663ca047a8" }, "InstalledVersion": "2.7.1+incompatible", "FixedVersion": "v2.8.0", @@ -52,7 +53,8 @@ "PkgID": "github.com/open-policy-agent/opa@v0.35.0", "PkgName": "github.com/open-policy-agent/opa", "PkgIdentifier": { - "PURL": "pkg:golang/github.com/open-policy-agent/opa@0.35.0" + "PURL": "pkg:golang/github.com/open-policy-agent/opa@0.35.0", + "UID": "6b685002e082ffc5" }, "InstalledVersion": "0.35.0", "FixedVersion": "0.37.0", @@ -98,7 +100,8 @@ "PkgID": "golang.org/x/text@v0.3.6", "PkgName": "golang.org/x/text", "PkgIdentifier": { - "PURL": "pkg:golang/golang.org/x/text@0.3.6" + "PURL": "pkg:golang/golang.org/x/text@0.3.6", + "UID": "825dc613c0f39d45" }, "InstalledVersion": "0.3.6", "FixedVersion": "0.3.7", @@ -130,7 +133,8 @@ "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", "PkgName": "github.com/docker/distribution", "PkgIdentifier": { - "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible" + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible", + "UID": "94376dc37054a7e8" }, "InstalledVersion": "2.7.1+incompatible", "FixedVersion": "v2.8.0", @@ -163,7 +167,8 @@ "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", "PkgName": "github.com/docker/distribution", "PkgIdentifier": { - "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible" + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible", + "UID": "94306cdcf85fb50a" }, "InstalledVersion": "2.7.1+incompatible", "FixedVersion": "v2.8.0", diff --git a/integration/testdata/gradle.json.golden b/integration/testdata/gradle.json.golden index 86822d526e3b..4f546add29a5 100644 --- a/integration/testdata/gradle.json.golden +++ b/integration/testdata/gradle.json.golden @@ -26,7 +26,8 @@ "PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1", "PkgName": "com.fasterxml.jackson.core:jackson-databind", "PkgIdentifier": { - "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1" + "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "UID": "7014f907b756006b" }, "InstalledVersion": "2.9.1", "FixedVersion": "2.9.10.4", @@ -91,7 +92,8 @@ "PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1", "PkgName": "com.fasterxml.jackson.core:jackson-databind", "PkgIdentifier": { - "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1" + "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "UID": "7014f907b756006b" }, "InstalledVersion": "2.9.1", "FixedVersion": "2.9.10.7", diff --git a/integration/testdata/mariner-1.0.json.golden b/integration/testdata/mariner-1.0.json.golden index 435dd5bdf7a6..1d549e1ef188 100644 --- a/integration/testdata/mariner-1.0.json.golden +++ b/integration/testdata/mariner-1.0.json.golden @@ -42,7 +42,8 @@ "VulnerabilityID": "CVE-2022-0261", "PkgName": "vim", "PkgIdentifier": { - "PURL": "pkg:cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64" + "PURL": "pkg:cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64", + "UID": "3f08cd76fa5ba73d" }, "InstalledVersion": "8.2.4081-1.cm1", "Status": "affected", @@ -78,7 +79,8 @@ "VulnerabilityID": "CVE-2022-0158", "PkgName": "vim", "PkgIdentifier": { - "PURL": "pkg:cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64" + "PURL": "pkg:cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64", + "UID": "3f08cd76fa5ba73d" }, "InstalledVersion": "8.2.4081-1.cm1", "FixedVersion": "8.2.4082-1.cm1", diff --git a/integration/testdata/minikube-kbom.json.golden b/integration/testdata/minikube-kbom.json.golden index fd5b6b24718f..c80807eb9c99 100644 --- a/integration/testdata/minikube-kbom.json.golden +++ b/integration/testdata/minikube-kbom.json.golden @@ -36,6 +36,7 @@ "PkgName": "k8s.io/kubelet", "PkgIdentifier": { "PURL": "pkg:k8s/k8s.io%2Fkubelet@1.27.0", + "UID": "4cb15d0a98eeae67", "BOMRef": "pkg:k8s/k8s.io%2Fkubelet@1.27.0" }, "InstalledVersion": "1.27.0", diff --git a/integration/testdata/mix.lock.json.golden b/integration/testdata/mix.lock.json.golden index 54445fbf9cfa..259337374490 100644 --- a/integration/testdata/mix.lock.json.golden +++ b/integration/testdata/mix.lock.json.golden @@ -25,7 +25,8 @@ "ID": "castore@0.1.18", "Name": "castore", "Identifier": { - "PURL": "pkg:hex/castore@0.1.18" + "PURL": "pkg:hex/castore@0.1.18", + "UID": "92fd0f5d45735c7c" }, "Version": "0.1.18", "Layer": {}, @@ -40,7 +41,8 @@ "ID": "jason@1.4.0", "Name": "jason", "Identifier": { - "PURL": "pkg:hex/jason@1.4.0" + "PURL": "pkg:hex/jason@1.4.0", + "UID": "b9cff6ce54a65dae" }, "Version": "1.4.0", "Layer": {}, @@ -55,7 +57,8 @@ "ID": "phoenix@1.6.13", "Name": "phoenix", "Identifier": { - "PURL": "pkg:hex/phoenix@1.6.13" + "PURL": "pkg:hex/phoenix@1.6.13", + "UID": "5b0d3fb75bef47e3" }, "Version": "1.6.13", "Layer": {}, @@ -70,7 +73,8 @@ "ID": "phoenix_html@3.2.0", "Name": "phoenix_html", "Identifier": { - "PURL": "pkg:hex/phoenix_html@3.2.0" + "PURL": "pkg:hex/phoenix_html@3.2.0", + "UID": "8c18e24394b53ab" }, "Version": "3.2.0", "Layer": {}, @@ -85,7 +89,8 @@ "ID": "phoenix_pubsub@2.1.1", "Name": "phoenix_pubsub", "Identifier": { - "PURL": "pkg:hex/phoenix_pubsub@2.1.1" + "PURL": "pkg:hex/phoenix_pubsub@2.1.1", + "UID": "89226dc20d54eb50" }, "Version": "2.1.1", "Layer": {}, @@ -100,7 +105,8 @@ "ID": "phoenix_template@1.0.0", "Name": "phoenix_template", "Identifier": { - "PURL": "pkg:hex/phoenix_template@1.0.0" + "PURL": "pkg:hex/phoenix_template@1.0.0", + "UID": "5cd9afe7111a31b7" }, "Version": "1.0.0", "Layer": {}, @@ -115,7 +121,8 @@ "ID": "phoenix_view@2.0.1", "Name": "phoenix_view", "Identifier": { - "PURL": "pkg:hex/phoenix_view@2.0.1" + "PURL": "pkg:hex/phoenix_view@2.0.1", + "UID": "2f4485f9653589ad" }, "Version": "2.0.1", "Layer": {}, @@ -130,7 +137,8 @@ "ID": "plug@1.14.0", "Name": "plug", "Identifier": { - "PURL": "pkg:hex/plug@1.14.0" + "PURL": "pkg:hex/plug@1.14.0", + "UID": "2390188ac1142ded" }, "Version": "1.14.0", "Layer": {}, @@ -145,7 +153,8 @@ "ID": "plug_crypto@1.2.3", "Name": "plug_crypto", "Identifier": { - "PURL": "pkg:hex/plug_crypto@1.2.3" + "PURL": "pkg:hex/plug_crypto@1.2.3", + "UID": "912b06dac071654" }, "Version": "1.2.3", "Layer": {}, @@ -160,7 +169,8 @@ "ID": "telemetry@1.1.0", "Name": "telemetry", "Identifier": { - "PURL": "pkg:hex/telemetry@1.1.0" + "PURL": "pkg:hex/telemetry@1.1.0", + "UID": "15879b8627da74b9" }, "Version": "1.1.0", "Layer": {}, @@ -178,7 +188,8 @@ "PkgID": "phoenix@1.6.13", "PkgName": "phoenix", "PkgIdentifier": { - "PURL": "pkg:hex/phoenix@1.6.13" + "PURL": "pkg:hex/phoenix@1.6.13", + "UID": "5b0d3fb75bef47e3" }, "InstalledVersion": "1.6.13", "FixedVersion": "1.6.14", diff --git a/integration/testdata/npm-with-dev.json.golden b/integration/testdata/npm-with-dev.json.golden index fb1ecb091209..eb2d03fa58c1 100644 --- a/integration/testdata/npm-with-dev.json.golden +++ b/integration/testdata/npm-with-dev.json.golden @@ -25,7 +25,8 @@ "ID": "asap@2.0.6", "Name": "asap", "Identifier": { - "PURL": "pkg:npm/asap@2.0.6" + "PURL": "pkg:npm/asap@2.0.6", + "UID": "199d95f873330bd3" }, "Version": "2.0.6", "Layer": {}, @@ -40,7 +41,8 @@ "ID": "jquery@3.3.9", "Name": "jquery", "Identifier": { - "PURL": "pkg:npm/jquery@3.3.9" + "PURL": "pkg:npm/jquery@3.3.9", + "UID": "e19e84d31f72b60c" }, "Version": "3.3.9", "Licenses": [ @@ -58,7 +60,8 @@ "ID": "js-tokens@4.0.0", "Name": "js-tokens", "Identifier": { - "PURL": "pkg:npm/js-tokens@4.0.0" + "PURL": "pkg:npm/js-tokens@4.0.0", + "UID": "605df7770562762" }, "Version": "4.0.0", "Layer": {}, @@ -73,7 +76,8 @@ "ID": "loose-envify@1.4.0", "Name": "loose-envify", "Identifier": { - "PURL": "pkg:npm/loose-envify@1.4.0" + "PURL": "pkg:npm/loose-envify@1.4.0", + "UID": "a40682339e264167" }, "Version": "1.4.0", "DependsOn": [ @@ -91,7 +95,8 @@ "ID": "object-assign@4.1.1", "Name": "object-assign", "Identifier": { - "PURL": "pkg:npm/object-assign@4.1.1" + "PURL": "pkg:npm/object-assign@4.1.1", + "UID": "ec3b70276c206ac2" }, "Version": "4.1.1", "Layer": {}, @@ -106,7 +111,8 @@ "ID": "promise@8.0.3", "Name": "promise", "Identifier": { - "PURL": "pkg:npm/promise@8.0.3" + "PURL": "pkg:npm/promise@8.0.3", + "UID": "b60f9aaa4e3cba8f" }, "Version": "8.0.3", "Licenses": [ @@ -127,7 +133,8 @@ "ID": "prop-types@15.7.2", "Name": "prop-types", "Identifier": { - "PURL": "pkg:npm/prop-types@15.7.2" + "PURL": "pkg:npm/prop-types@15.7.2", + "UID": "5a0c427e953b2a24" }, "Version": "15.7.2", "DependsOn": [ @@ -147,7 +154,8 @@ "ID": "react@16.8.6", "Name": "react", "Identifier": { - "PURL": "pkg:npm/react@16.8.6" + "PURL": "pkg:npm/react@16.8.6", + "UID": "da9140320b70dc57" }, "Version": "16.8.6", "Licenses": [ @@ -171,7 +179,8 @@ "ID": "react-is@16.8.6", "Name": "react-is", "Identifier": { - "PURL": "pkg:npm/react-is@16.8.6" + "PURL": "pkg:npm/react-is@16.8.6", + "UID": "f50b67a44460b362" }, "Version": "16.8.6", "Licenses": [ @@ -189,7 +198,8 @@ "ID": "redux@4.0.1", "Name": "redux", "Identifier": { - "PURL": "pkg:npm/redux@4.0.1" + "PURL": "pkg:npm/redux@4.0.1", + "UID": "fbb7d7c45dbba492" }, "Version": "4.0.1", "Licenses": [ @@ -211,7 +221,8 @@ "ID": "scheduler@0.13.6", "Name": "scheduler", "Identifier": { - "PURL": "pkg:npm/scheduler@0.13.6" + "PURL": "pkg:npm/scheduler@0.13.6", + "UID": "9738f8ac302a0bb" }, "Version": "0.13.6", "DependsOn": [ @@ -230,7 +241,8 @@ "ID": "symbol-observable@1.2.0", "Name": "symbol-observable", "Identifier": { - "PURL": "pkg:npm/symbol-observable@1.2.0" + "PURL": "pkg:npm/symbol-observable@1.2.0", + "UID": "b14a083f8b9e59bc" }, "Version": "1.2.0", "Layer": {}, @@ -245,7 +257,8 @@ "ID": "z-lock@1.0.0", "Name": "z-lock", "Identifier": { - "PURL": "pkg:npm/z-lock@1.0.0" + "PURL": "pkg:npm/z-lock@1.0.0", + "UID": "f6ba8a4be50ce713" }, "Version": "1.0.0", "Dev": true, @@ -267,7 +280,8 @@ "PkgID": "jquery@3.3.9", "PkgName": "jquery", "PkgIdentifier": { - "PURL": "pkg:npm/jquery@3.3.9" + "PURL": "pkg:npm/jquery@3.3.9", + "UID": "e19e84d31f72b60c" }, "InstalledVersion": "3.3.9", "FixedVersion": "3.4.0", diff --git a/integration/testdata/npm.json.golden b/integration/testdata/npm.json.golden index a576da82c72e..88ca91b48044 100644 --- a/integration/testdata/npm.json.golden +++ b/integration/testdata/npm.json.golden @@ -25,7 +25,8 @@ "ID": "asap@2.0.6", "Name": "asap", "Identifier": { - "PURL": "pkg:npm/asap@2.0.6" + "PURL": "pkg:npm/asap@2.0.6", + "UID": "199d95f873330bd3" }, "Version": "2.0.6", "Layer": {}, @@ -40,7 +41,8 @@ "ID": "jquery@3.3.9", "Name": "jquery", "Identifier": { - "PURL": "pkg:npm/jquery@3.3.9" + "PURL": "pkg:npm/jquery@3.3.9", + "UID": "e19e84d31f72b60c" }, "Version": "3.3.9", "Licenses": [ @@ -58,7 +60,8 @@ "ID": "js-tokens@4.0.0", "Name": "js-tokens", "Identifier": { - "PURL": "pkg:npm/js-tokens@4.0.0" + "PURL": "pkg:npm/js-tokens@4.0.0", + "UID": "605df7770562762" }, "Version": "4.0.0", "Layer": {}, @@ -73,7 +76,8 @@ "ID": "loose-envify@1.4.0", "Name": "loose-envify", "Identifier": { - "PURL": "pkg:npm/loose-envify@1.4.0" + "PURL": "pkg:npm/loose-envify@1.4.0", + "UID": "a40682339e264167" }, "Version": "1.4.0", "DependsOn": [ @@ -91,7 +95,8 @@ "ID": "object-assign@4.1.1", "Name": "object-assign", "Identifier": { - "PURL": "pkg:npm/object-assign@4.1.1" + "PURL": "pkg:npm/object-assign@4.1.1", + "UID": "ec3b70276c206ac2" }, "Version": "4.1.1", "Layer": {}, @@ -106,7 +111,8 @@ "ID": "promise@8.0.3", "Name": "promise", "Identifier": { - "PURL": "pkg:npm/promise@8.0.3" + "PURL": "pkg:npm/promise@8.0.3", + "UID": "b60f9aaa4e3cba8f" }, "Version": "8.0.3", "Licenses": [ @@ -127,7 +133,8 @@ "ID": "prop-types@15.7.2", "Name": "prop-types", "Identifier": { - "PURL": "pkg:npm/prop-types@15.7.2" + "PURL": "pkg:npm/prop-types@15.7.2", + "UID": "5a0c427e953b2a24" }, "Version": "15.7.2", "DependsOn": [ @@ -147,7 +154,8 @@ "ID": "react@16.8.6", "Name": "react", "Identifier": { - "PURL": "pkg:npm/react@16.8.6" + "PURL": "pkg:npm/react@16.8.6", + "UID": "da9140320b70dc57" }, "Version": "16.8.6", "Licenses": [ @@ -171,7 +179,8 @@ "ID": "react-is@16.8.6", "Name": "react-is", "Identifier": { - "PURL": "pkg:npm/react-is@16.8.6" + "PURL": "pkg:npm/react-is@16.8.6", + "UID": "f50b67a44460b362" }, "Version": "16.8.6", "Licenses": [ @@ -189,7 +198,8 @@ "ID": "redux@4.0.1", "Name": "redux", "Identifier": { - "PURL": "pkg:npm/redux@4.0.1" + "PURL": "pkg:npm/redux@4.0.1", + "UID": "fbb7d7c45dbba492" }, "Version": "4.0.1", "Licenses": [ @@ -211,7 +221,8 @@ "ID": "scheduler@0.13.6", "Name": "scheduler", "Identifier": { - "PURL": "pkg:npm/scheduler@0.13.6" + "PURL": "pkg:npm/scheduler@0.13.6", + "UID": "9738f8ac302a0bb" }, "Version": "0.13.6", "DependsOn": [ @@ -230,7 +241,8 @@ "ID": "symbol-observable@1.2.0", "Name": "symbol-observable", "Identifier": { - "PURL": "pkg:npm/symbol-observable@1.2.0" + "PURL": "pkg:npm/symbol-observable@1.2.0", + "UID": "b14a083f8b9e59bc" }, "Version": "1.2.0", "Layer": {}, @@ -248,7 +260,8 @@ "PkgID": "jquery@3.3.9", "PkgName": "jquery", "PkgIdentifier": { - "PURL": "pkg:npm/jquery@3.3.9" + "PURL": "pkg:npm/jquery@3.3.9", + "UID": "e19e84d31f72b60c" }, "InstalledVersion": "3.3.9", "FixedVersion": "3.4.0", diff --git a/integration/testdata/nuget.json.golden b/integration/testdata/nuget.json.golden index 6c5a2f19b9ac..b4ec872a839c 100644 --- a/integration/testdata/nuget.json.golden +++ b/integration/testdata/nuget.json.golden @@ -25,7 +25,8 @@ "ID": "Newtonsoft.Json@12.0.3", "Name": "Newtonsoft.Json", "Identifier": { - "PURL": "pkg:nuget/Newtonsoft.Json@12.0.3" + "PURL": "pkg:nuget/Newtonsoft.Json@12.0.3", + "UID": "d4249b2442e303e9" }, "Version": "12.0.3", "Relationship": "direct", @@ -41,7 +42,8 @@ "ID": "NuGet.Frameworks@5.7.0", "Name": "NuGet.Frameworks", "Identifier": { - "PURL": "pkg:nuget/NuGet.Frameworks@5.7.0" + "PURL": "pkg:nuget/NuGet.Frameworks@5.7.0", + "UID": "6fa0c117039de82a" }, "Version": "5.7.0", "Relationship": "direct", @@ -63,7 +65,8 @@ "PkgID": "Newtonsoft.Json@12.0.3", "PkgName": "Newtonsoft.Json", "PkgIdentifier": { - "PURL": "pkg:nuget/Newtonsoft.Json@12.0.3" + "PURL": "pkg:nuget/Newtonsoft.Json@12.0.3", + "UID": "d4249b2442e303e9" }, "InstalledVersion": "12.0.3", "FixedVersion": "13.0.1", diff --git a/integration/testdata/opensuse-leap-151.json.golden b/integration/testdata/opensuse-leap-151.json.golden index 9ca650d3dbb3..77b0148604bb 100644 --- a/integration/testdata/opensuse-leap-151.json.golden +++ b/integration/testdata/opensuse-leap-151.json.golden @@ -66,7 +66,8 @@ "PkgID": "libopenssl1_1@1.1.0i-lp151.8.3.1.x86_64", "PkgName": "libopenssl1_1", "PkgIdentifier": { - "PURL": "pkg:rpm/opensuse.leap/libopenssl1_1@1.1.0i-lp151.8.3.1?arch=x86_64\u0026distro=opensuse.leap-15.1" + "PURL": "pkg:rpm/opensuse.leap/libopenssl1_1@1.1.0i-lp151.8.3.1?arch=x86_64\u0026distro=opensuse.leap-15.1", + "UID": "898b73ddd0412f57" }, "InstalledVersion": "1.1.0i-lp151.8.3.1", "FixedVersion": "1.1.0i-lp151.8.6.1", @@ -98,7 +99,8 @@ "PkgID": "openssl-1_1@1.1.0i-lp151.8.3.1.x86_64", "PkgName": "openssl-1_1", "PkgIdentifier": { - "PURL": "pkg:rpm/opensuse.leap/openssl-1_1@1.1.0i-lp151.8.3.1?arch=x86_64\u0026distro=opensuse.leap-15.1" + "PURL": "pkg:rpm/opensuse.leap/openssl-1_1@1.1.0i-lp151.8.3.1?arch=x86_64\u0026distro=opensuse.leap-15.1", + "UID": "58980d005de43f54" }, "InstalledVersion": "1.1.0i-lp151.8.3.1", "FixedVersion": "1.1.0i-lp151.8.6.1", diff --git a/integration/testdata/oraclelinux-8.json.golden b/integration/testdata/oraclelinux-8.json.golden index 6629e7fe0e55..71a0411cba93 100644 --- a/integration/testdata/oraclelinux-8.json.golden +++ b/integration/testdata/oraclelinux-8.json.golden @@ -67,7 +67,8 @@ "PkgID": "curl@7.61.1-8.el8.x86_64", "PkgName": "curl", "PkgIdentifier": { - "PURL": "pkg:rpm/oracle/curl@7.61.1-8.el8?arch=x86_64\u0026distro=oracle-8.0" + "PURL": "pkg:rpm/oracle/curl@7.61.1-8.el8?arch=x86_64\u0026distro=oracle-8.0", + "UID": "6837a94bd82971ac" }, "InstalledVersion": "7.61.1-8.el8", "FixedVersion": "7.61.1-11.el8", @@ -137,7 +138,8 @@ "PkgID": "curl@7.61.1-8.el8.x86_64", "PkgName": "curl", "PkgIdentifier": { - "PURL": "pkg:rpm/oracle/curl@7.61.1-8.el8?arch=x86_64\u0026distro=oracle-8.0" + "PURL": "pkg:rpm/oracle/curl@7.61.1-8.el8?arch=x86_64\u0026distro=oracle-8.0", + "UID": "6837a94bd82971ac" }, "InstalledVersion": "7.61.1-8.el8", "FixedVersion": "7.61.1-12.el8", diff --git a/integration/testdata/packagesprops.json.golden b/integration/testdata/packagesprops.json.golden index 5cce23a7c754..6eba4413ccad 100644 --- a/integration/testdata/packagesprops.json.golden +++ b/integration/testdata/packagesprops.json.golden @@ -25,7 +25,8 @@ "ID": "Newtonsoft.Json@9.0.1", "Name": "Newtonsoft.Json", "Identifier": { - "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1" + "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1", + "UID": "a391c576ea549d63" }, "Version": "9.0.1", "Layer": {} @@ -37,7 +38,8 @@ "PkgID": "Newtonsoft.Json@9.0.1", "PkgName": "Newtonsoft.Json", "PkgIdentifier": { - "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1" + "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1", + "UID": "a391c576ea549d63" }, "InstalledVersion": "9.0.1", "FixedVersion": "13.0.1", diff --git a/integration/testdata/photon-30.json.golden b/integration/testdata/photon-30.json.golden index 4e4c8d793faa..08597fd526ce 100644 --- a/integration/testdata/photon-30.json.golden +++ b/integration/testdata/photon-30.json.golden @@ -68,7 +68,8 @@ "PkgID": "bash@4.4.18-1.ph3.x86_64", "PkgName": "bash", "PkgIdentifier": { - "PURL": "pkg:rpm/photon/bash@4.4.18-1.ph3?arch=x86_64\u0026distro=photon-3.0" + "PURL": "pkg:rpm/photon/bash@4.4.18-1.ph3?arch=x86_64\u0026distro=photon-3.0", + "UID": "a092142482df7886" }, "InstalledVersion": "4.4.18-1.ph3", "FixedVersion": "4.4.18-2.ph3", @@ -131,7 +132,8 @@ "PkgID": "curl@7.61.1-4.ph3.x86_64", "PkgName": "curl", "PkgIdentifier": { - "PURL": "pkg:rpm/photon/curl@7.61.1-4.ph3?arch=x86_64\u0026distro=photon-3.0" + "PURL": "pkg:rpm/photon/curl@7.61.1-4.ph3?arch=x86_64\u0026distro=photon-3.0", + "UID": "1f44492024a630e8" }, "InstalledVersion": "7.61.1-4.ph3", "FixedVersion": "7.61.1-5.ph3", @@ -202,7 +204,8 @@ "PkgID": "curl-libs@7.61.1-4.ph3.x86_64", "PkgName": "curl-libs", "PkgIdentifier": { - "PURL": "pkg:rpm/photon/curl-libs@7.61.1-4.ph3?arch=x86_64\u0026distro=photon-3.0" + "PURL": "pkg:rpm/photon/curl-libs@7.61.1-4.ph3?arch=x86_64\u0026distro=photon-3.0", + "UID": "434cc417a46529a9" }, "InstalledVersion": "7.61.1-4.ph3", "FixedVersion": "7.61.1-5.ph3", diff --git a/integration/testdata/pip.json.golden b/integration/testdata/pip.json.golden index 29873d943d55..cc715f05357d 100644 --- a/integration/testdata/pip.json.golden +++ b/integration/testdata/pip.json.golden @@ -24,7 +24,8 @@ { "Name": "Flask", "Identifier": { - "PURL": "pkg:pypi/flask@2.0.0" + "PURL": "pkg:pypi/flask@2.0.0", + "UID": "301ccf5fd90d6082" }, "Version": "2.0.0", "Layer": {} @@ -32,7 +33,8 @@ { "Name": "Jinja2", "Identifier": { - "PURL": "pkg:pypi/jinja2@3.0.0" + "PURL": "pkg:pypi/jinja2@3.0.0", + "UID": "212193e1595e68cc" }, "Version": "3.0.0", "Layer": {} @@ -40,7 +42,8 @@ { "Name": "Werkzeug", "Identifier": { - "PURL": "pkg:pypi/werkzeug@0.11" + "PURL": "pkg:pypi/werkzeug@0.11", + "UID": "56b919b561299a48" }, "Version": "0.11", "Layer": {} @@ -48,7 +51,8 @@ { "Name": "click", "Identifier": { - "PURL": "pkg:pypi/click@8.0.0" + "PURL": "pkg:pypi/click@8.0.0", + "UID": "d58cb56b4e8b1ffd" }, "Version": "8.0.0", "Layer": {} @@ -56,7 +60,8 @@ { "Name": "itsdangerous", "Identifier": { - "PURL": "pkg:pypi/itsdangerous@2.0.0" + "PURL": "pkg:pypi/itsdangerous@2.0.0", + "UID": "9bf39d440e409733" }, "Version": "2.0.0", "Layer": {} @@ -64,7 +69,8 @@ { "Name": "oauth2-client", "Identifier": { - "PURL": "pkg:pypi/oauth2-client@4.0.0" + "PURL": "pkg:pypi/oauth2-client@4.0.0", + "UID": "ffc67df5ef686f77" }, "Version": "4.0.0", "Layer": {} @@ -72,7 +78,8 @@ { "Name": "python-gitlab", "Identifier": { - "PURL": "pkg:pypi/python-gitlab@2.0.0" + "PURL": "pkg:pypi/python-gitlab@2.0.0", + "UID": "f9cbb9736717c4d4" }, "Version": "2.0.0", "Layer": {} @@ -83,7 +90,8 @@ "VulnerabilityID": "CVE-2019-14806", "PkgName": "Werkzeug", "PkgIdentifier": { - "PURL": "pkg:pypi/werkzeug@0.11" + "PURL": "pkg:pypi/werkzeug@0.11", + "UID": "56b919b561299a48" }, "InstalledVersion": "0.11", "FixedVersion": "0.15.3", @@ -139,7 +147,8 @@ "VulnerabilityID": "CVE-2020-28724", "PkgName": "Werkzeug", "PkgIdentifier": { - "PURL": "pkg:pypi/werkzeug@0.11" + "PURL": "pkg:pypi/werkzeug@0.11", + "UID": "56b919b561299a48" }, "InstalledVersion": "0.11", "FixedVersion": "0.11.6", diff --git a/integration/testdata/pipenv.json.golden b/integration/testdata/pipenv.json.golden index e5076aa4571b..c8a317515ae6 100644 --- a/integration/testdata/pipenv.json.golden +++ b/integration/testdata/pipenv.json.golden @@ -24,7 +24,8 @@ { "Name": "werkzeug", "Identifier": { - "PURL": "pkg:pypi/werkzeug@0.11.1" + "PURL": "pkg:pypi/werkzeug@0.11.1", + "UID": "390fc5ac777dc4e0" }, "Version": "0.11.1", "Layer": {}, @@ -41,7 +42,8 @@ "VulnerabilityID": "CVE-2019-14806", "PkgName": "werkzeug", "PkgIdentifier": { - "PURL": "pkg:pypi/werkzeug@0.11.1" + "PURL": "pkg:pypi/werkzeug@0.11.1", + "UID": "390fc5ac777dc4e0" }, "InstalledVersion": "0.11.1", "FixedVersion": "0.15.3", @@ -97,7 +99,8 @@ "VulnerabilityID": "CVE-2020-28724", "PkgName": "werkzeug", "PkgIdentifier": { - "PURL": "pkg:pypi/werkzeug@0.11.1" + "PURL": "pkg:pypi/werkzeug@0.11.1", + "UID": "390fc5ac777dc4e0" }, "InstalledVersion": "0.11.1", "FixedVersion": "0.11.6", diff --git a/integration/testdata/pnpm.json.golden b/integration/testdata/pnpm.json.golden index 305552bf1f2a..1ae0c6400db0 100644 --- a/integration/testdata/pnpm.json.golden +++ b/integration/testdata/pnpm.json.golden @@ -26,7 +26,8 @@ "PkgID": "jquery@3.3.9", "PkgName": "jquery", "PkgIdentifier": { - "PURL": "pkg:npm/jquery@3.3.9" + "PURL": "pkg:npm/jquery@3.3.9", + "UID": "d002d4ebac4ee286" }, "InstalledVersion": "3.3.9", "FixedVersion": "3.4.0", @@ -158,7 +159,8 @@ "PkgID": "lodash@4.17.4", "PkgName": "lodash", "PkgIdentifier": { - "PURL": "pkg:npm/lodash@4.17.4" + "PURL": "pkg:npm/lodash@4.17.4", + "UID": "68507e8301071074" }, "InstalledVersion": "4.17.4", "FixedVersion": "4.17.12", diff --git a/integration/testdata/poetry.json.golden b/integration/testdata/poetry.json.golden index a17528965df9..f1ddf6802143 100644 --- a/integration/testdata/poetry.json.golden +++ b/integration/testdata/poetry.json.golden @@ -25,7 +25,8 @@ "ID": "click@8.1.3", "Name": "click", "Identifier": { - "PURL": "pkg:pypi/click@8.1.3" + "PURL": "pkg:pypi/click@8.1.3", + "UID": "37edb5c90a97272e" }, "Version": "8.1.3", "Relationship": "direct", @@ -38,7 +39,8 @@ "ID": "colorama@0.4.6", "Name": "colorama", "Identifier": { - "PURL": "pkg:pypi/colorama@0.4.6" + "PURL": "pkg:pypi/colorama@0.4.6", + "UID": "895013c17f373da3" }, "Version": "0.4.6", "Indirect": true, @@ -49,7 +51,8 @@ "ID": "werkzeug@0.14", "Name": "werkzeug", "Identifier": { - "PURL": "pkg:pypi/werkzeug@0.14" + "PURL": "pkg:pypi/werkzeug@0.14", + "UID": "4176be111ad01070" }, "Version": "0.14", "Relationship": "direct", @@ -62,7 +65,8 @@ "PkgID": "werkzeug@0.14", "PkgName": "werkzeug", "PkgIdentifier": { - "PURL": "pkg:pypi/werkzeug@0.14" + "PURL": "pkg:pypi/werkzeug@0.14", + "UID": "4176be111ad01070" }, "InstalledVersion": "0.14", "FixedVersion": "0.15.3", diff --git a/integration/testdata/pom.json.golden b/integration/testdata/pom.json.golden index 244817f1e4c7..a583f02ce8e9 100644 --- a/integration/testdata/pom.json.golden +++ b/integration/testdata/pom.json.golden @@ -26,7 +26,8 @@ "PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1", "PkgName": "com.fasterxml.jackson.core:jackson-databind", "PkgIdentifier": { - "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1" + "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "UID": "a704d87fd1c0b0e1" }, "InstalledVersion": "2.9.1", "FixedVersion": "2.9.10.4", @@ -91,7 +92,8 @@ "PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1", "PkgName": "com.fasterxml.jackson.core:jackson-databind", "PkgIdentifier": { - "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1" + "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "UID": "a704d87fd1c0b0e1" }, "InstalledVersion": "2.9.1", "FixedVersion": "2.9.10.7", diff --git a/integration/testdata/pubspec.lock.json.golden b/integration/testdata/pubspec.lock.json.golden index cd941a66652f..a96211ac9119 100644 --- a/integration/testdata/pubspec.lock.json.golden +++ b/integration/testdata/pubspec.lock.json.golden @@ -25,7 +25,8 @@ "ID": "http@0.13.2", "Name": "http", "Identifier": { - "PURL": "pkg:pub/http@0.13.2" + "PURL": "pkg:pub/http@0.13.2", + "UID": "b2aa9906ee615d4c" }, "Version": "0.13.2", "Relationship": "direct", @@ -35,7 +36,8 @@ "ID": "shelf@1.3.1", "Name": "shelf", "Identifier": { - "PURL": "pkg:pub/shelf@1.3.1" + "PURL": "pkg:pub/shelf@1.3.1", + "UID": "8367a65de5b4cda0" }, "Version": "1.3.1", "Indirect": true, @@ -49,7 +51,8 @@ "PkgID": "http@0.13.2", "PkgName": "http", "PkgIdentifier": { - "PURL": "pkg:pub/http@0.13.2" + "PURL": "pkg:pub/http@0.13.2", + "UID": "b2aa9906ee615d4c" }, "InstalledVersion": "0.13.2", "FixedVersion": "0.13.3", diff --git a/integration/testdata/rockylinux-8.json.golden b/integration/testdata/rockylinux-8.json.golden index 6d8b3b7a29a8..f6e377a49ca0 100644 --- a/integration/testdata/rockylinux-8.json.golden +++ b/integration/testdata/rockylinux-8.json.golden @@ -57,7 +57,8 @@ "PkgID": "openssl-libs@1.1.1k-4.el8.x86_64", "PkgName": "openssl-libs", "PkgIdentifier": { - "PURL": "pkg:rpm/rocky/openssl-libs@1.1.1k-4.el8?arch=x86_64\u0026distro=rocky-8.5\u0026epoch=1" + "PURL": "pkg:rpm/rocky/openssl-libs@1.1.1k-4.el8?arch=x86_64\u0026distro=rocky-8.5\u0026epoch=1", + "UID": "2a2f49f9bf5fc512" }, "InstalledVersion": "1:1.1.1k-4.el8", "FixedVersion": "1:1.1.1k-5.el8_5", diff --git a/integration/testdata/spring4shell-jre11.json.golden b/integration/testdata/spring4shell-jre11.json.golden index 7db6bae72bfa..98c49376cf53 100644 --- a/integration/testdata/spring4shell-jre11.json.golden +++ b/integration/testdata/spring4shell-jre11.json.golden @@ -199,7 +199,8 @@ "PkgName": "org.springframework:spring-beans", "PkgPath": "usr/local/tomcat/webapps/helloworld.war/WEB-INF/lib/spring-beans-5.3.15.jar", "PkgIdentifier": { - "PURL": "pkg:maven/org.springframework/spring-beans@5.3.15" + "PURL": "pkg:maven/org.springframework/spring-beans@5.3.15", + "UID": "9d9e0a303e263760" }, "InstalledVersion": "5.3.15", "FixedVersion": "5.3.18", diff --git a/integration/testdata/spring4shell-jre8.json.golden b/integration/testdata/spring4shell-jre8.json.golden index e1a0e9ee0457..45da22c7f39c 100644 --- a/integration/testdata/spring4shell-jre8.json.golden +++ b/integration/testdata/spring4shell-jre8.json.golden @@ -199,7 +199,8 @@ "PkgName": "org.springframework:spring-beans", "PkgPath": "usr/local/tomcat/webapps/helloworld.war/WEB-INF/lib/spring-beans-5.3.15.jar", "PkgIdentifier": { - "PURL": "pkg:maven/org.springframework/spring-beans@5.3.15" + "PURL": "pkg:maven/org.springframework/spring-beans@5.3.15", + "UID": "9d9e0a303e263760" }, "InstalledVersion": "5.3.15", "FixedVersion": "5.3.18", diff --git a/integration/testdata/swift.json.golden b/integration/testdata/swift.json.golden index 0a9d1ebffb21..bbd94884bf95 100644 --- a/integration/testdata/swift.json.golden +++ b/integration/testdata/swift.json.golden @@ -25,7 +25,8 @@ "ID": "github.com/apple/swift-atomics@1.1.0", "Name": "github.com/apple/swift-atomics", "Identifier": { - "PURL": "pkg:swift/github.com/apple/swift-atomics@1.1.0" + "PURL": "pkg:swift/github.com/apple/swift-atomics@1.1.0", + "UID": "863921f724d081f4" }, "Version": "1.1.0", "Layer": {}, @@ -40,7 +41,8 @@ "ID": "github.com/apple/swift-nio@2.41.0", "Name": "github.com/apple/swift-nio", "Identifier": { - "PURL": "pkg:swift/github.com/apple/swift-nio@2.41.0" + "PURL": "pkg:swift/github.com/apple/swift-nio@2.41.0", + "UID": "e98d348b22b3c01" }, "Version": "2.41.0", "Layer": {}, @@ -58,7 +60,8 @@ "PkgID": "github.com/apple/swift-nio@2.41.0", "PkgName": "github.com/apple/swift-nio", "PkgIdentifier": { - "PURL": "pkg:swift/github.com/apple/swift-nio@2.41.0" + "PURL": "pkg:swift/github.com/apple/swift-nio@2.41.0", + "UID": "e98d348b22b3c01" }, "InstalledVersion": "2.41.0", "FixedVersion": "2.29.1, 2.39.1, 2.42.0", diff --git a/integration/testdata/test-repo.json.golden b/integration/testdata/test-repo.json.golden index e1ebd91418f7..360ba1f3f097 100644 --- a/integration/testdata/test-repo.json.golden +++ b/integration/testdata/test-repo.json.golden @@ -26,7 +26,8 @@ "PkgID": "ammonia@1.9.0", "PkgName": "ammonia", "PkgIdentifier": { - "PURL": "pkg:cargo/ammonia@1.9.0" + "PURL": "pkg:cargo/ammonia@1.9.0", + "UID": "fa518cac41270ffe" }, "InstalledVersion": "1.9.0", "FixedVersion": "\u003e= 2.1.0", @@ -68,7 +69,8 @@ "PkgID": "ammonia@1.9.0", "PkgName": "ammonia", "PkgIdentifier": { - "PURL": "pkg:cargo/ammonia@1.9.0" + "PURL": "pkg:cargo/ammonia@1.9.0", + "UID": "fa518cac41270ffe" }, "InstalledVersion": "1.9.0", "FixedVersion": "\u003e= 3.1.0, \u003e= 2.1.3, \u003c 3.0.0", diff --git a/integration/testdata/ubi-7.json.golden b/integration/testdata/ubi-7.json.golden index 36062495c5a9..41d78d2be351 100644 --- a/integration/testdata/ubi-7.json.golden +++ b/integration/testdata/ubi-7.json.golden @@ -81,7 +81,8 @@ "PkgID": "bash@4.2.46-33.el7.x86_64", "PkgName": "bash", "PkgIdentifier": { - "PURL": "pkg:rpm/redhat/bash@4.2.46-33.el7?arch=x86_64\u0026distro=redhat-7.7" + "PURL": "pkg:rpm/redhat/bash@4.2.46-33.el7?arch=x86_64\u0026distro=redhat-7.7", + "UID": "f5b786381193ad1b" }, "InstalledVersion": "4.2.46-33.el7", "Status": "will_not_fix", diff --git a/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden b/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden index f2a38510385b..5ec55a3fc397 100644 --- a/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden +++ b/integration/testdata/ubuntu-1804-ignore-unfixed.json.golden @@ -76,7 +76,8 @@ "PkgID": "e2fsprogs@1.44.1-1ubuntu1.1", "PkgName": "e2fsprogs", "PkgIdentifier": { - "PURL": "pkg:deb/ubuntu/e2fsprogs@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + "PURL": "pkg:deb/ubuntu/e2fsprogs@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", + "UID": "ae80ec86b8816b6c" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", @@ -146,7 +147,8 @@ "PkgID": "libcom-err2@1.44.1-1ubuntu1.1", "PkgName": "libcom-err2", "PkgIdentifier": { - "PURL": "pkg:deb/ubuntu/libcom-err2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + "PURL": "pkg:deb/ubuntu/libcom-err2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", + "UID": "3c28244e063693a2" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", @@ -216,7 +218,8 @@ "PkgID": "libext2fs2@1.44.1-1ubuntu1.1", "PkgName": "libext2fs2", "PkgIdentifier": { - "PURL": "pkg:deb/ubuntu/libext2fs2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + "PURL": "pkg:deb/ubuntu/libext2fs2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", + "UID": "937ce6e3021ed568" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", @@ -286,7 +289,8 @@ "PkgID": "libss2@1.44.1-1ubuntu1.1", "PkgName": "libss2", "PkgIdentifier": { - "PURL": "pkg:deb/ubuntu/libss2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + "PURL": "pkg:deb/ubuntu/libss2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", + "UID": "7a50c6bc4279c93b" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", diff --git a/integration/testdata/ubuntu-1804.json.golden b/integration/testdata/ubuntu-1804.json.golden index 5fc21dba6a6e..7ae275b7e5a6 100644 --- a/integration/testdata/ubuntu-1804.json.golden +++ b/integration/testdata/ubuntu-1804.json.golden @@ -76,7 +76,8 @@ "PkgID": "bash@4.4.18-2ubuntu1.2", "PkgName": "bash", "PkgIdentifier": { - "PURL": "pkg:deb/ubuntu/bash@4.4.18-2ubuntu1.2?arch=amd64\u0026distro=ubuntu-18.04" + "PURL": "pkg:deb/ubuntu/bash@4.4.18-2ubuntu1.2?arch=amd64\u0026distro=ubuntu-18.04", + "UID": "da318bd19a304cc0" }, "InstalledVersion": "4.4.18-2ubuntu1.2", "Status": "affected", @@ -138,7 +139,8 @@ "PkgID": "e2fsprogs@1.44.1-1ubuntu1.1", "PkgName": "e2fsprogs", "PkgIdentifier": { - "PURL": "pkg:deb/ubuntu/e2fsprogs@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + "PURL": "pkg:deb/ubuntu/e2fsprogs@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", + "UID": "ae80ec86b8816b6c" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", @@ -208,7 +210,8 @@ "PkgID": "libcom-err2@1.44.1-1ubuntu1.1", "PkgName": "libcom-err2", "PkgIdentifier": { - "PURL": "pkg:deb/ubuntu/libcom-err2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + "PURL": "pkg:deb/ubuntu/libcom-err2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", + "UID": "3c28244e063693a2" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", @@ -278,7 +281,8 @@ "PkgID": "libext2fs2@1.44.1-1ubuntu1.1", "PkgName": "libext2fs2", "PkgIdentifier": { - "PURL": "pkg:deb/ubuntu/libext2fs2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + "PURL": "pkg:deb/ubuntu/libext2fs2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", + "UID": "937ce6e3021ed568" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", @@ -348,7 +352,8 @@ "PkgID": "libss2@1.44.1-1ubuntu1.1", "PkgName": "libss2", "PkgIdentifier": { - "PURL": "pkg:deb/ubuntu/libss2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04" + "PURL": "pkg:deb/ubuntu/libss2@1.44.1-1ubuntu1.1?arch=amd64\u0026distro=ubuntu-18.04", + "UID": "7a50c6bc4279c93b" }, "InstalledVersion": "1.44.1-1ubuntu1.1", "FixedVersion": "1.44.1-1ubuntu1.2", diff --git a/integration/testdata/yarn.json.golden b/integration/testdata/yarn.json.golden index 452dcd0172a6..16b6c37584a1 100644 --- a/integration/testdata/yarn.json.golden +++ b/integration/testdata/yarn.json.golden @@ -25,7 +25,8 @@ "ID": "jquery@3.2.1", "Name": "jquery", "Identifier": { - "PURL": "pkg:npm/jquery@3.2.1" + "PURL": "pkg:npm/jquery@3.2.1", + "UID": "c7da6fd622178ea0" }, "Version": "3.2.1", "Licenses": [ @@ -47,7 +48,8 @@ "PkgID": "jquery@3.2.1", "PkgName": "jquery", "PkgIdentifier": { - "PURL": "pkg:npm/jquery@3.2.1" + "PURL": "pkg:npm/jquery@3.2.1", + "UID": "c7da6fd622178ea0" }, "InstalledVersion": "3.2.1", "FixedVersion": "3.4.0", diff --git a/integration/vm_test.go b/integration/vm_test.go index 7ccc85f03994..78de5741e682 100644 --- a/integration/vm_test.go +++ b/integration/vm_test.go @@ -88,13 +88,13 @@ func TestVM(t *testing.T) { // Run "trivy vm" runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{ - override: func(_, got *types.Report) { + override: overrideFuncs(overrideUID, func(t *testing.T, _, got *types.Report) { got.ArtifactName = "disk.img" for i := range got.Results { lastIndex := strings.LastIndex(got.Results[i].Target, "/") got.Results[i].Target = got.Results[i].Target[lastIndex+1:] } - }, + }), }) }) } diff --git a/pkg/fanal/applier/applier_test.go b/pkg/fanal/applier/applier_test.go index 8037e0bee922..bea3f30fa0c5 100644 --- a/pkg/fanal/applier/applier_test.go +++ b/pkg/fanal/applier/applier_test.go @@ -151,6 +151,7 @@ func TestApplier_ApplyLayers(t *testing.T) { SrcName: "glibc", SrcVersion: "2.24-11+deb9u4", Identifier: types.PkgIdentifier{ + UID: "1565c6a375877d3d", PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Namespace: "debian", @@ -175,6 +176,7 @@ func TestApplier_ApplyLayers(t *testing.T) { SrcName: "tzdata", SrcVersion: "2019a-0+deb9u1", Identifier: types.PkgIdentifier{ + UID: "15974c575bfa26a7", PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Namespace: "debian", @@ -207,6 +209,7 @@ func TestApplier_ApplyLayers(t *testing.T) { DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", }, Identifier: types.PkgIdentifier{ + UID: "38462330435c69bc", PURL: &packageurl.PackageURL{ Type: packageurl.TypeComposer, Namespace: "guzzlehttp", @@ -223,6 +226,7 @@ func TestApplier_ApplyLayers(t *testing.T) { DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", }, Identifier: types.PkgIdentifier{ + UID: "ef7e3567678854cb", PURL: &packageurl.PackageURL{ Type: packageurl.TypeComposer, Namespace: "symfony", @@ -345,6 +349,7 @@ func TestApplier_ApplyLayers(t *testing.T) { Name: "busybox", Version: "1.30.1-r3", Identifier: types.PkgIdentifier{ + UID: "3bfef897b9fcc058", PURL: &packageurl.PackageURL{ Type: packageurl.TypeApk, Namespace: "alpine", @@ -367,6 +372,7 @@ func TestApplier_ApplyLayers(t *testing.T) { Name: "libcrypto1.1", Version: "1.1.1d-r2", Identifier: types.PkgIdentifier{ + UID: "a4495e1af163f55a", PURL: &packageurl.PackageURL{ Type: packageurl.TypeApk, Namespace: "alpine", @@ -389,6 +395,7 @@ func TestApplier_ApplyLayers(t *testing.T) { Name: "libssl1.1", Version: "1.1.1d-r2", Identifier: types.PkgIdentifier{ + UID: "4c683a33e3b7899c", PURL: &packageurl.PackageURL{ Type: packageurl.TypeApk, Namespace: "alpine", @@ -411,6 +418,7 @@ func TestApplier_ApplyLayers(t *testing.T) { Name: "musl", Version: "1.1.22-r3", Identifier: types.PkgIdentifier{ + UID: "bb9bd4dfce8858bf", PURL: &packageurl.PackageURL{ Type: packageurl.TypeApk, Namespace: "alpine", @@ -433,6 +441,7 @@ func TestApplier_ApplyLayers(t *testing.T) { Name: "openssl", Version: "1.1.1d-r2", Identifier: types.PkgIdentifier{ + UID: "3f6c865591e06595", //PURL: "pkg:apk/alpine/openssl@1.1.1d-r2?distro=3.10.4", PURL: &packageurl.PackageURL{ Type: packageurl.TypeApk, @@ -641,6 +650,9 @@ func TestApplier_ApplyLayers(t *testing.T) { Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", }, + Identifier: types.PkgIdentifier{ + UID: "1565c6a375877d3d", + }, }, { Name: "tzdata", @@ -651,6 +663,9 @@ func TestApplier_ApplyLayers(t *testing.T) { Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", }, + Identifier: types.PkgIdentifier{ + UID: "15974c575bfa26a7", + }, }, }, Applications: []types.Application{ @@ -666,6 +681,7 @@ func TestApplier_ApplyLayers(t *testing.T) { DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", }, Identifier: types.PkgIdentifier{ + UID: "38462330435c69bc", PURL: &packageurl.PackageURL{ Type: packageurl.TypeComposer, Namespace: "guzzlehttp", @@ -682,6 +698,7 @@ func TestApplier_ApplyLayers(t *testing.T) { DiffID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", }, Identifier: types.PkgIdentifier{ + UID: "ef7e3567678854cb", PURL: &packageurl.PackageURL{ Type: packageurl.TypeComposer, Namespace: "symfony", @@ -859,6 +876,9 @@ func TestApplier_ApplyLayers(t *testing.T) { Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", }, + Identifier: types.PkgIdentifier{ + UID: "15974c575bfa26a7", + }, }, }, Applications: []types.Application{ @@ -874,6 +894,7 @@ func TestApplier_ApplyLayers(t *testing.T) { DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", }, Identifier: types.PkgIdentifier{ + UID: "38462330435c69bc", PURL: &packageurl.PackageURL{ Type: packageurl.TypeComposer, Namespace: "guzzlehttp", @@ -890,6 +911,7 @@ func TestApplier_ApplyLayers(t *testing.T) { DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", }, Identifier: types.PkgIdentifier{ + UID: "ef7e3567678854cb", PURL: &packageurl.PackageURL{ Type: packageurl.TypeComposer, Namespace: "symfony", diff --git a/pkg/fanal/applier/docker.go b/pkg/fanal/applier/docker.go index 0e9781edbca6..7640cfadaec6 100644 --- a/pkg/fanal/applier/docker.go +++ b/pkg/fanal/applier/docker.go @@ -6,6 +6,7 @@ import ( "time" "github.com/knqyf263/nested" + "github.com/mitchellh/hashstructure/v2" "github.com/package-url/packageurl-go" "github.com/samber/lo" @@ -208,18 +209,19 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { for i, pkg := range mergedLayer.Packages { // Skip lookup for SBOM - if !lo.IsEmpty(pkg.Layer) { - continue - } - originLayerDigest, originLayerDiffID, buildInfo := lookupOriginLayerForPkg(pkg, layers) - mergedLayer.Packages[i].Layer = ftypes.Layer{ - Digest: originLayerDigest, - DiffID: originLayerDiffID, + if lo.IsEmpty(pkg.Layer) { + originLayerDigest, originLayerDiffID, buildInfo := lookupOriginLayerForPkg(pkg, layers) + mergedLayer.Packages[i].Layer = ftypes.Layer{ + Digest: originLayerDigest, + DiffID: originLayerDiffID, + } + mergedLayer.Packages[i].BuildInfo = buildInfo } - mergedLayer.Packages[i].BuildInfo = buildInfo + if mergedLayer.OS.Family != "" { mergedLayer.Packages[i].Identifier.PURL = newPURL(mergedLayer.OS.Family, types.Metadata{OS: &mergedLayer.OS}, pkg) } + mergedLayer.Packages[i].Identifier.UID = calcPkgUID("", pkg) // Only debian packages if licenses, ok := dpkgLicenses[pkg.Name]; ok { @@ -230,17 +232,17 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { for _, app := range mergedLayer.Applications { for i, lib := range app.Libraries { // Skip lookup for SBOM - if !lo.IsEmpty(lib.Layer) { - continue - } - originLayerDigest, originLayerDiffID := lookupOriginLayerForLib(app.FilePath, lib, layers) - app.Libraries[i].Layer = ftypes.Layer{ - Digest: originLayerDigest, - DiffID: originLayerDiffID, + if lo.IsEmpty(lib.Layer) { + originLayerDigest, originLayerDiffID := lookupOriginLayerForLib(app.FilePath, lib, layers) + app.Libraries[i].Layer = ftypes.Layer{ + Digest: originLayerDigest, + DiffID: originLayerDiffID, + } } if lib.Identifier.PURL == nil { app.Libraries[i].Identifier.PURL = newPURL(app.Type, types.Metadata{}, lib) } + app.Libraries[i].Identifier.UID = calcPkgUID(app.FilePath, lib) } } @@ -259,6 +261,22 @@ func newPURL(pkgType ftypes.TargetType, metadata types.Metadata, pkg ftypes.Pack return p.Unwrap() } +// calcPkgUID calculates the hash of the package for the unique ID +func calcPkgUID(filePath string, pkg ftypes.Package) string { + v := map[string]any{ + "filePath": filePath, // To differentiate the hash of the same package but different file path + "pkg": pkg, + } + hash, err := hashstructure.Hash(v, hashstructure.FormatV2, &hashstructure.HashOptions{ + ZeroNil: true, + IgnoreZeroValue: true, + }) + if err != nil { + log.Warn("Failed to calculate the package hash", log.String("pkg", pkg.Name), log.Err(err)) + } + return fmt.Sprintf("%x", hash) +} + // aggregate merges all packages installed by pip/gem/npm/jar/conda into each application func aggregate(detail *ftypes.ArtifactDetail) { var apps []ftypes.Application diff --git a/pkg/fanal/applier/docker_test.go b/pkg/fanal/applier/docker_test.go index 425930b9ba2e..9a5f043c6e3c 100644 --- a/pkg/fanal/applier/docker_test.go +++ b/pkg/fanal/applier/docker_test.go @@ -145,6 +145,7 @@ func TestApplyLayers(t *testing.T) { Version: "1.2.4", Release: "4.5.8", Identifier: types.PkgIdentifier{ + UID: "108c0f3943d7bc9", PURL: &packageurl.PackageURL{ Type: packageurl.TypeApk, Namespace: "alpine", @@ -168,6 +169,7 @@ func TestApplyLayers(t *testing.T) { Version: "1.2.3", Release: "4.5.6", Identifier: types.PkgIdentifier{ + UID: "9d77cb17d1fc8736", PURL: &packageurl.PackageURL{ Type: packageurl.TypeApk, Namespace: "alpine", @@ -200,6 +202,7 @@ func TestApplyLayers(t *testing.T) { DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", }, Identifier: types.PkgIdentifier{ + UID: "b3549e98a3094a66", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGem, Name: "activesupport", @@ -216,6 +219,7 @@ func TestApplyLayers(t *testing.T) { DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", }, Identifier: types.PkgIdentifier{ + UID: "f27f3b46e09fc2e2", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGem, Name: "gon", @@ -237,6 +241,7 @@ func TestApplyLayers(t *testing.T) { DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", }, Identifier: types.PkgIdentifier{ + UID: "a3363562b587cfa2", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGem, Name: "gemlibrary1", @@ -310,6 +315,25 @@ func TestApplyLayers(t *testing.T) { Digest: "sha256:e67fdae3559346105027c63e7fb032bba57e62b1fe9f2da23e6fdfb56384e00b", DiffID: "sha256:633f5bf471f7595b236a21e62dc60beef321db45916363a02ad5af02d794d497", }, + Identifier: types.PkgIdentifier{ + UID: "e984be704d7e13ef", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "debian", + Name: "adduser", + Version: "3.118+deb11u1", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "all", + }, + { + Key: "distro", + Value: "debian-11.8", + }, + }, + }, + }, }, }, Applications: []types.Application{ @@ -323,6 +347,14 @@ func TestApplyLayers(t *testing.T) { Layer: types.Layer{ DiffID: "sha256:1def056a3160854c9395aa76282dd62172ec08c18a5fa03bb7d50a777c15ba99", }, + Identifier: types.PkgIdentifier{ + UID: "8d8c54cecea3dd33", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypePyPi, + Name: "pip", + Version: "23.0.1", + }, + }, }, }, }, @@ -462,6 +494,7 @@ func TestApplyLayers(t *testing.T) { DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", }, Identifier: types.PkgIdentifier{ + UID: "9744e21755aea0ef", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGem, Name: "rack", @@ -477,6 +510,7 @@ func TestApplyLayers(t *testing.T) { DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", }, Identifier: types.PkgIdentifier{ + UID: "7e9712137f044ffe", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGem, Name: "rails", @@ -498,6 +532,7 @@ func TestApplyLayers(t *testing.T) { DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", }, Identifier: types.PkgIdentifier{ + UID: "940351428c1fed49", PURL: &packageurl.PackageURL{ Type: packageurl.TypeComposer, Name: "phplibrary1", @@ -761,6 +796,7 @@ func TestApplyLayers(t *testing.T) { Release: "4.5.7", Licenses: []string{"GPL-2"}, Identifier: types.PkgIdentifier{ + UID: "c3c9ea1442ead294", PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Namespace: "debian", @@ -785,6 +821,7 @@ func TestApplyLayers(t *testing.T) { Release: "4.5.6", Licenses: []string{"OpenSSL"}, Identifier: types.PkgIdentifier{ + UID: "9d77cb17d1fc8736", PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Namespace: "debian", @@ -935,6 +972,7 @@ func TestApplyLayers(t *testing.T) { Version: "5.6.7", Release: "8", Identifier: types.PkgIdentifier{ + UID: "3982c06acacff066", PURL: &packageurl.PackageURL{ Type: packageurl.TypeRPM, Namespace: "redhat", @@ -962,6 +1000,7 @@ func TestApplyLayers(t *testing.T) { Version: "1.2.4", Release: "5", Identifier: types.PkgIdentifier{ + UID: "8a72001605297eac", PURL: &packageurl.PackageURL{ Type: packageurl.TypeRPM, Namespace: "redhat", @@ -991,6 +1030,7 @@ func TestApplyLayers(t *testing.T) { Version: "1.2.3", Release: "4", Identifier: types.PkgIdentifier{ + UID: "8de1ca1c33881bac", PURL: &packageurl.PackageURL{ Type: packageurl.TypeRPM, Namespace: "redhat", @@ -1018,6 +1058,86 @@ func TestApplyLayers(t *testing.T) { }, }, }, + { + name: "same package but different file path", // different hashes + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 1, + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + Applications: []types.Application{ + { + Type: types.Bundler, + FilePath: "app1/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "gemlibrary1", + Version: "1.2.3", + }, + }, + }, + { + Type: types.Bundler, + FilePath: "app2/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "gemlibrary1", + Version: "1.2.3", + }, + }, + }, + }, + }, + }, + want: types.ArtifactDetail{ + Applications: []types.Application{ + { + Type: types.Bundler, + FilePath: "app1/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "gemlibrary1", + Version: "1.2.3", + Layer: types.Layer{ + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + Identifier: types.PkgIdentifier{ + UID: "176111c6c0c6488", // different hash + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "gemlibrary1", + Version: "1.2.3", + }, + }, + }, + }, + }, + { + Type: types.Bundler, + FilePath: "app2/Gemfile.lock", + Libraries: types.Packages{ + { + Name: "gemlibrary1", + Version: "1.2.3", + Layer: types.Layer{ + Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", + }, + Identifier: types.PkgIdentifier{ + UID: "e1416731a0829253", // different hash + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "gemlibrary1", + Version: "1.2.3", + }, + }, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { diff --git a/pkg/fanal/artifact/vm/vm_test.go b/pkg/fanal/artifact/vm/vm_test.go index 445707c8739b..b510662a2dbf 100644 --- a/pkg/fanal/artifact/vm/vm_test.go +++ b/pkg/fanal/artifact/vm/vm_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - xio "github.com/aquasecurity/trivy/pkg/x/io" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact/vm" @@ -22,6 +21,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" "github.com/aquasecurity/trivy/pkg/misconf" + xio "github.com/aquasecurity/trivy/pkg/x/io" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/pkg/apk" diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden index ff6de7ec239e..32513f398b9f 100644 --- a/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden +++ b/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden @@ -3,7 +3,8 @@ "ID": "alpine-baselayout@3.1.2-r0", "Name": "alpine-baselayout", "Identifier": { - "PURL": "pkg:apk/alpine/alpine-baselayout@3.1.2-r0?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/alpine-baselayout@3.1.2-r0?arch=x86_64\u0026distro=3.10.2", + "UID": "3ca42aecf84bfa9" }, "Version": "3.1.2-r0", "Arch": "x86_64", @@ -53,7 +54,8 @@ "ID": "alpine-keys@2.1-r2", "Name": "alpine-keys", "Identifier": { - "PURL": "pkg:apk/alpine/alpine-keys@2.1-r2?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/alpine-keys@2.1-r2?arch=x86_64\u0026distro=3.10.2", + "UID": "c8139a25abdfb636" }, "Version": "2.1-r2", "Arch": "x86_64", @@ -92,7 +94,8 @@ "ID": "apk-tools@2.10.4-r2", "Name": "apk-tools", "Identifier": { - "PURL": "pkg:apk/alpine/apk-tools@2.10.4-r2?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/apk-tools@2.10.4-r2?arch=x86_64\u0026distro=3.10.2", + "UID": "fa7ca40ce236844e" }, "Version": "2.10.4-r2", "Arch": "x86_64", @@ -120,7 +123,8 @@ "ID": "busybox@1.30.1-r2", "Name": "busybox", "Identifier": { - "PURL": "pkg:apk/alpine/busybox@1.30.1-r2?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/busybox@1.30.1-r2?arch=x86_64\u0026distro=3.10.2", + "UID": "1941d9acbebe7f44" }, "Version": "1.30.1-r2", "Arch": "x86_64", @@ -150,7 +154,8 @@ "ID": "ca-certificates-cacert@20190108-r0", "Name": "ca-certificates-cacert", "Identifier": { - "PURL": "pkg:apk/alpine/ca-certificates-cacert@20190108-r0?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/ca-certificates-cacert@20190108-r0?arch=x86_64\u0026distro=3.10.2", + "UID": "9b1db91ad655d76c" }, "Version": "20190108-r0", "Arch": "x86_64", @@ -173,7 +178,8 @@ "ID": "libc-utils@0.7.1-r0", "Name": "libc-utils", "Identifier": { - "PURL": "pkg:apk/alpine/libc-utils@0.7.1-r0?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/libc-utils@0.7.1-r0?arch=x86_64\u0026distro=3.10.2", + "UID": "53fe480444a44c6e" }, "Version": "0.7.1-r0", "Arch": "x86_64", @@ -195,7 +201,8 @@ "ID": "libcrypto1.1@1.1.1c-r0", "Name": "libcrypto1.1", "Identifier": { - "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2", + "UID": "c6c116a4441ec6de" }, "Version": "1.1.1c-r0", "Arch": "x86_64", @@ -231,7 +238,8 @@ "ID": "libssl1.1@1.1.1c-r0", "Name": "libssl1.1", "Identifier": { - "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2", + "UID": "e132dcfcc51772ef" }, "Version": "1.1.1c-r0", "Arch": "x86_64", @@ -258,7 +266,8 @@ "ID": "libtls-standalone@2.9.1-r0", "Name": "libtls-standalone", "Identifier": { - "PURL": "pkg:apk/alpine/libtls-standalone@2.9.1-r0?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/libtls-standalone@2.9.1-r0?arch=x86_64\u0026distro=3.10.2", + "UID": "578e2c441f445479" }, "Version": "2.9.1-r0", "Arch": "x86_64", @@ -287,7 +296,8 @@ "ID": "musl@1.1.22-r3", "Name": "musl", "Identifier": { - "PURL": "pkg:apk/alpine/musl@1.1.22-r3?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/musl@1.1.22-r3?arch=x86_64\u0026distro=3.10.2", + "UID": "796d455bf42e5034" }, "Version": "1.1.22-r3", "Arch": "x86_64", @@ -310,7 +320,8 @@ "ID": "musl-utils@1.1.22-r3", "Name": "musl-utils", "Identifier": { - "PURL": "pkg:apk/alpine/musl-utils@1.1.22-r3?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/musl-utils@1.1.22-r3?arch=x86_64\u0026distro=3.10.2", + "UID": "40edadcb74964baa" }, "Version": "1.1.22-r3", "Arch": "x86_64", @@ -342,7 +353,8 @@ "ID": "scanelf@1.2.3-r0", "Name": "scanelf", "Identifier": { - "PURL": "pkg:apk/alpine/scanelf@1.2.3-r0?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/scanelf@1.2.3-r0?arch=x86_64\u0026distro=3.10.2", + "UID": "6e5cf642f47e44d0" }, "Version": "1.2.3-r0", "Arch": "x86_64", @@ -367,7 +379,8 @@ "ID": "ssl_client@1.30.1-r2", "Name": "ssl_client", "Identifier": { - "PURL": "pkg:apk/alpine/ssl_client@1.30.1-r2?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/ssl_client@1.30.1-r2?arch=x86_64\u0026distro=3.10.2", + "UID": "3338164dd993d2c8" }, "Version": "1.30.1-r2", "Arch": "x86_64", @@ -393,7 +406,8 @@ "ID": "zlib@1.2.11-r1", "Name": "zlib", "Identifier": { - "PURL": "pkg:apk/alpine/zlib@1.2.11-r1?arch=x86_64\u0026distro=3.10.2" + "PURL": "pkg:apk/alpine/zlib@1.2.11-r1?arch=x86_64\u0026distro=3.10.2", + "UID": "73011a6749e0754a" }, "Version": "1.2.11-r1", "Arch": "x86_64", diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden index d6e7df5676d6..4bfbb16a3dff 100644 --- a/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden +++ b/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden @@ -3,7 +3,8 @@ "ID": ".composer-phpext-rundeps@0", "Name": ".composer-phpext-rundeps", "Identifier": { - "PURL": "pkg:apk/alpine/.composer-phpext-rundeps@0?arch=noarch\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/.composer-phpext-rundeps@0?arch=noarch\u0026distro=3.7.1", + "UID": "6a71fef044f9139b" }, "Version": "0", "Arch": "noarch", @@ -22,7 +23,8 @@ "ID": ".persistent-deps@0", "Name": ".persistent-deps", "Identifier": { - "PURL": "pkg:apk/alpine/.persistent-deps@0?arch=noarch\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/.persistent-deps@0?arch=noarch\u0026distro=3.7.1", + "UID": "6f77911c02034886" }, "Version": "0", "Arch": "noarch", @@ -43,7 +45,8 @@ "ID": ".php-rundeps@0", "Name": ".php-rundeps", "Identifier": { - "PURL": "pkg:apk/alpine/.php-rundeps@0?arch=noarch\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/.php-rundeps@0?arch=noarch\u0026distro=3.7.1", + "UID": "5aa1e73ffa97555a" }, "Version": "0", "Arch": "noarch", @@ -67,7 +70,8 @@ "ID": "alpine-baselayout@3.0.5-r2", "Name": "alpine-baselayout", "Identifier": { - "PURL": "pkg:apk/alpine/alpine-baselayout@3.0.5-r2?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/alpine-baselayout@3.0.5-r2?arch=x86_64\u0026distro=3.7.1", + "UID": "fb3e0109c30ad75d" }, "Version": "3.0.5-r2", "Arch": "x86_64", @@ -118,7 +122,8 @@ "ID": "alpine-keys@2.1-r1", "Name": "alpine-keys", "Identifier": { - "PURL": "pkg:apk/alpine/alpine-keys@2.1-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/alpine-keys@2.1-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "6a7b3011335f9352" }, "Version": "2.1-r1", "Arch": "x86_64", @@ -157,7 +162,8 @@ "ID": "apk-tools@2.10.1-r0", "Name": "apk-tools", "Identifier": { - "PURL": "pkg:apk/alpine/apk-tools@2.10.1-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/apk-tools@2.10.1-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "44e4204bb4dc2b2a" }, "Version": "2.10.1-r0", "Arch": "x86_64", @@ -185,7 +191,8 @@ "ID": "apr@1.6.3-r0", "Name": "apr", "Identifier": { - "PURL": "pkg:apk/alpine/apr@1.6.3-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/apr@1.6.3-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "715dd499dcec48f9" }, "Version": "1.6.3-r0", "Arch": "x86_64", @@ -213,7 +220,8 @@ "ID": "apr-util@1.6.1-r1", "Name": "apr-util", "Identifier": { - "PURL": "pkg:apk/alpine/apr-util@1.6.1-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/apr-util@1.6.1-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "2aa1dd25c68a60eb" }, "Version": "1.6.1-r1", "Arch": "x86_64", @@ -244,7 +252,8 @@ "ID": "bash@4.4.19-r1", "Name": "bash", "Identifier": { - "PURL": "pkg:apk/alpine/bash@4.4.19-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/bash@4.4.19-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "32e82b55d6afc293" }, "Version": "4.4.19-r1", "Arch": "x86_64", @@ -358,7 +367,8 @@ "ID": "busybox@1.27.2-r11", "Name": "busybox", "Identifier": { - "PURL": "pkg:apk/alpine/busybox@1.27.2-r11?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/busybox@1.27.2-r11?arch=x86_64\u0026distro=3.7.1", + "UID": "e32c8aeae1daa385" }, "Version": "1.27.2-r11", "Arch": "x86_64", @@ -388,7 +398,8 @@ "ID": "ca-certificates@20171114-r0", "Name": "ca-certificates", "Identifier": { - "PURL": "pkg:apk/alpine/ca-certificates@20171114-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/ca-certificates@20171114-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "fc49474e56f2cdca" }, "Version": "20171114-r0", "Arch": "x86_64", @@ -571,7 +582,8 @@ "ID": "curl@7.61.0-r0", "Name": "curl", "Identifier": { - "PURL": "pkg:apk/alpine/curl@7.61.0-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/curl@7.61.0-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "9161a384fc3be35a" }, "Version": "7.61.0-r0", "Arch": "x86_64", @@ -599,7 +611,8 @@ "ID": "db@5.3.28-r0", "Name": "db", "Identifier": { - "PURL": "pkg:apk/alpine/db@5.3.28-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/db@5.3.28-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "fb760539eab5fe62" }, "Version": "5.3.28-r0", "Arch": "x86_64", @@ -624,7 +637,8 @@ "ID": "expat@2.2.5-r0", "Name": "expat", "Identifier": { - "PURL": "pkg:apk/alpine/expat@2.2.5-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/expat@2.2.5-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "617bea3266e3407" }, "Version": "2.2.5-r0", "Arch": "x86_64", @@ -651,7 +665,8 @@ "ID": "gdbm@1.13-r1", "Name": "gdbm", "Identifier": { - "PURL": "pkg:apk/alpine/gdbm@1.13-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/gdbm@1.13-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "eb4b3efac8d4d73f" }, "Version": "1.13-r1", "Arch": "x86_64", @@ -682,7 +697,8 @@ "ID": "git@2.15.2-r0", "Name": "git", "Identifier": { - "PURL": "pkg:apk/alpine/git@2.15.2-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/git@2.15.2-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "845b6214d48ea80e" }, "Version": "2.15.2-r0", "Arch": "x86_64", @@ -901,7 +917,8 @@ "ID": "libbz2@1.0.6-r6", "Name": "libbz2", "Identifier": { - "PURL": "pkg:apk/alpine/libbz2@1.0.6-r6?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libbz2@1.0.6-r6?arch=x86_64\u0026distro=3.7.1", + "UID": "bc5027058e147d99" }, "Version": "1.0.6-r6", "Arch": "x86_64", @@ -927,7 +944,8 @@ "ID": "libc-utils@0.7.1-r0", "Name": "libc-utils", "Identifier": { - "PURL": "pkg:apk/alpine/libc-utils@0.7.1-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libc-utils@0.7.1-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "486e6fc636a30805" }, "Version": "0.7.1-r0", "Arch": "x86_64", @@ -949,7 +967,8 @@ "ID": "libcurl@7.61.1-r0", "Name": "libcurl", "Identifier": { - "PURL": "pkg:apk/alpine/libcurl@7.61.1-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libcurl@7.61.1-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "39cb881b68d4d86" }, "Version": "7.61.1-r0", "Arch": "x86_64", @@ -980,7 +999,8 @@ "ID": "libedit@20170329.3.1-r3", "Name": "libedit", "Identifier": { - "PURL": "pkg:apk/alpine/libedit@20170329.3.1-r3?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libedit@20170329.3.1-r3?arch=x86_64\u0026distro=3.7.1", + "UID": "d579817be3cf172e" }, "Version": "20170329.3.1-r3", "Arch": "x86_64", @@ -1007,7 +1027,8 @@ "ID": "libffi@3.2.1-r4", "Name": "libffi", "Identifier": { - "PURL": "pkg:apk/alpine/libffi@3.2.1-r4?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libffi@3.2.1-r4?arch=x86_64\u0026distro=3.7.1", + "UID": "742cc1cdc4bc5c97" }, "Version": "3.2.1-r4", "Arch": "x86_64", @@ -1033,7 +1054,8 @@ "ID": "libressl@2.6.5-r0", "Name": "libressl", "Identifier": { - "PURL": "pkg:apk/alpine/libressl@2.6.5-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libressl@2.6.5-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "af5b603971965a1d" }, "Version": "2.6.5-r0", "Arch": "x86_64", @@ -1062,7 +1084,8 @@ "ID": "libressl2.6-libcrypto@2.6.5-r0", "Name": "libressl2.6-libcrypto", "Identifier": { - "PURL": "pkg:apk/alpine/libressl2.6-libcrypto@2.6.5-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libressl2.6-libcrypto@2.6.5-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "da55f7ea92a187ed" }, "Version": "2.6.5-r0", "Arch": "x86_64", @@ -1093,7 +1116,8 @@ "ID": "libressl2.6-libssl@2.6.5-r0", "Name": "libressl2.6-libssl", "Identifier": { - "PURL": "pkg:apk/alpine/libressl2.6-libssl@2.6.5-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libressl2.6-libssl@2.6.5-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "e29c3e48f6adbae8" }, "Version": "2.6.5-r0", "Arch": "x86_64", @@ -1122,7 +1146,8 @@ "ID": "libressl2.6-libtls@2.6.5-r0", "Name": "libressl2.6-libtls", "Identifier": { - "PURL": "pkg:apk/alpine/libressl2.6-libtls@2.6.5-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libressl2.6-libtls@2.6.5-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "43debd6f8f000ce9" }, "Version": "2.6.5-r0", "Arch": "x86_64", @@ -1152,7 +1177,8 @@ "ID": "libsasl@2.1.26-r11", "Name": "libsasl", "Identifier": { - "PURL": "pkg:apk/alpine/libsasl@2.1.26-r11?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libsasl@2.1.26-r11?arch=x86_64\u0026distro=3.7.1", + "UID": "e3a28098ef8cd138" }, "Version": "2.1.26-r11", "Arch": "x86_64", @@ -1188,7 +1214,8 @@ "ID": "libsodium@1.0.15-r0", "Name": "libsodium", "Identifier": { - "PURL": "pkg:apk/alpine/libsodium@1.0.15-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libsodium@1.0.15-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "c69c7ea7978a25e3" }, "Version": "1.0.15-r0", "Arch": "x86_64", @@ -1214,7 +1241,8 @@ "ID": "libssh2@1.8.0-r2", "Name": "libssh2", "Identifier": { - "PURL": "pkg:apk/alpine/libssh2@1.8.0-r2?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libssh2@1.8.0-r2?arch=x86_64\u0026distro=3.7.1", + "UID": "3bffc81dc8d84ee0" }, "Version": "1.8.0-r2", "Arch": "x86_64", @@ -1242,7 +1270,8 @@ "ID": "libuuid@2.31-r0", "Name": "libuuid", "Identifier": { - "PURL": "pkg:apk/alpine/libuuid@2.31-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libuuid@2.31-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "ec8a9d1580eb93d7" }, "Version": "2.31-r0", "Arch": "x86_64", @@ -1273,7 +1302,8 @@ "ID": "libxml2@2.9.7-r0", "Name": "libxml2", "Identifier": { - "PURL": "pkg:apk/alpine/libxml2@2.9.7-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/libxml2@2.9.7-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "fb90495b3dc37892" }, "Version": "2.9.7-r0", "Arch": "x86_64", @@ -1300,7 +1330,8 @@ "ID": "mercurial@4.5.2-r0", "Name": "mercurial", "Identifier": { - "PURL": "pkg:apk/alpine/mercurial@4.5.2-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/mercurial@4.5.2-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "28d12c554475942a" }, "Version": "4.5.2-r0", "Arch": "x86_64", @@ -2044,7 +2075,8 @@ "ID": "musl@1.1.18-r3", "Name": "musl", "Identifier": { - "PURL": "pkg:apk/alpine/musl@1.1.18-r3?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/musl@1.1.18-r3?arch=x86_64\u0026distro=3.7.1", + "UID": "2888024f672bbd23" }, "Version": "1.1.18-r3", "Arch": "x86_64", @@ -2067,7 +2099,8 @@ "ID": "musl-utils@1.1.18-r3", "Name": "musl-utils", "Identifier": { - "PURL": "pkg:apk/alpine/musl-utils@1.1.18-r3?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/musl-utils@1.1.18-r3?arch=x86_64\u0026distro=3.7.1", + "UID": "c2765ad0d8dd83f9" }, "Version": "1.1.18-r3", "Arch": "x86_64", @@ -2099,7 +2132,8 @@ "ID": "ncurses-libs@6.0_p20171125-r1", "Name": "ncurses-libs", "Identifier": { - "PURL": "pkg:apk/alpine/ncurses-libs@6.0_p20171125-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/ncurses-libs@6.0_p20171125-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "d5320ac17433cd9a" }, "Version": "6.0_p20171125-r1", "Arch": "x86_64", @@ -2134,7 +2168,8 @@ "ID": "ncurses-terminfo@6.0_p20171125-r1", "Name": "ncurses-terminfo", "Identifier": { - "PURL": "pkg:apk/alpine/ncurses-terminfo@6.0_p20171125-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/ncurses-terminfo@6.0_p20171125-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "45885ee497b379d2" }, "Version": "6.0_p20171125-r1", "Arch": "x86_64", @@ -4887,7 +4922,8 @@ "ID": "ncurses-terminfo-base@6.0_p20171125-r1", "Name": "ncurses-terminfo-base", "Identifier": { - "PURL": "pkg:apk/alpine/ncurses-terminfo-base@6.0_p20171125-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/ncurses-terminfo-base@6.0_p20171125-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "4679519d850307b7" }, "Version": "6.0_p20171125-r1", "Arch": "x86_64", @@ -4922,7 +4958,8 @@ "ID": "openssh@7.5_p1-r9", "Name": "openssh", "Identifier": { - "PURL": "pkg:apk/alpine/openssh@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/openssh@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1", + "UID": "fa7d7dc4eb75b48a" }, "Version": "7.5_p1-r9", "Arch": "x86_64", @@ -4951,7 +4988,8 @@ "ID": "openssh-client@7.5_p1-r9", "Name": "openssh-client", "Identifier": { - "PURL": "pkg:apk/alpine/openssh-client@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/openssh-client@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1", + "UID": "f2d105c566e0ac48" }, "Version": "7.5_p1-r9", "Arch": "x86_64", @@ -4989,7 +5027,8 @@ "ID": "openssh-keygen@7.5_p1-r9", "Name": "openssh-keygen", "Identifier": { - "PURL": "pkg:apk/alpine/openssh-keygen@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/openssh-keygen@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1", + "UID": "2c95ef90d057e6b4" }, "Version": "7.5_p1-r9", "Arch": "x86_64", @@ -5015,7 +5054,8 @@ "ID": "openssh-server@7.5_p1-r9", "Name": "openssh-server", "Identifier": { - "PURL": "pkg:apk/alpine/openssh-server@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/openssh-server@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1", + "UID": "8c21a4ada527dd06" }, "Version": "7.5_p1-r9", "Arch": "x86_64", @@ -5044,7 +5084,8 @@ "ID": "openssh-server-common@7.5_p1-r9", "Name": "openssh-server-common", "Identifier": { - "PURL": "pkg:apk/alpine/openssh-server-common@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/openssh-server-common@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1", + "UID": "4c8ce137b360c8f3" }, "Version": "7.5_p1-r9", "Arch": "x86_64", @@ -5068,7 +5109,8 @@ "ID": "openssh-sftp-server@7.5_p1-r9", "Name": "openssh-sftp-server", "Identifier": { - "PURL": "pkg:apk/alpine/openssh-sftp-server@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/openssh-sftp-server@7.5_p1-r9?arch=x86_64\u0026distro=3.7.1", + "UID": "2335817fb194e000" }, "Version": "7.5_p1-r9", "Arch": "x86_64", @@ -5093,7 +5135,8 @@ "ID": "patch@2.7.5-r2", "Name": "patch", "Identifier": { - "PURL": "pkg:apk/alpine/patch@2.7.5-r2?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/patch@2.7.5-r2?arch=x86_64\u0026distro=3.7.1", + "UID": "c3945b28493e2842" }, "Version": "2.7.5-r2", "Arch": "x86_64", @@ -5118,7 +5161,8 @@ "ID": "pcre2@10.30-r0", "Name": "pcre2", "Identifier": { - "PURL": "pkg:apk/alpine/pcre2@10.30-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/pcre2@10.30-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "68c6dfe1e3d664b9" }, "Version": "10.30-r0", "Arch": "x86_64", @@ -5146,7 +5190,8 @@ "ID": "pkgconf@1.3.10-r0", "Name": "pkgconf", "Identifier": { - "PURL": "pkg:apk/alpine/pkgconf@1.3.10-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/pkgconf@1.3.10-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "6fc191337a8d3ba1" }, "Version": "1.3.10-r0", "Arch": "x86_64", @@ -5175,7 +5220,8 @@ "ID": "python2@2.7.15-r2", "Name": "python2", "Identifier": { - "PURL": "pkg:apk/alpine/python2@2.7.15-r2?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/python2@2.7.15-r2?arch=x86_64\u0026distro=3.7.1", + "UID": "e7105320e999ebd6" }, "Version": "2.7.15-r2", "Arch": "x86_64", @@ -7620,7 +7666,8 @@ "ID": "readline@7.0.003-r0", "Name": "readline", "Identifier": { - "PURL": "pkg:apk/alpine/readline@7.0.003-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/readline@7.0.003-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "2cb2c7047f5911f5" }, "Version": "7.0.003-r0", "Arch": "x86_64", @@ -7647,7 +7694,8 @@ "ID": "scanelf@1.2.2-r1", "Name": "scanelf", "Identifier": { - "PURL": "pkg:apk/alpine/scanelf@1.2.2-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/scanelf@1.2.2-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "88086fdb5a997cfd" }, "Version": "1.2.2-r1", "Arch": "x86_64", @@ -7672,7 +7720,8 @@ "ID": "serf@1.3.9-r3", "Name": "serf", "Identifier": { - "PURL": "pkg:apk/alpine/serf@1.3.9-r3?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/serf@1.3.9-r3?arch=x86_64\u0026distro=3.7.1", + "UID": "34d5052ef0071707" }, "Version": "1.3.9-r3", "Arch": "x86_64", @@ -7703,7 +7752,8 @@ "ID": "sqlite-libs@3.21.0-r1", "Name": "sqlite-libs", "Identifier": { - "PURL": "pkg:apk/alpine/sqlite-libs@3.21.0-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/sqlite-libs@3.21.0-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "206767891b55f1ff" }, "Version": "3.21.0-r1", "Arch": "x86_64", @@ -7729,7 +7779,8 @@ "ID": "ssl_client@1.27.2-r11", "Name": "ssl_client", "Identifier": { - "PURL": "pkg:apk/alpine/ssl_client@1.27.2-r11?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/ssl_client@1.27.2-r11?arch=x86_64\u0026distro=3.7.1", + "UID": "eab2a79208d922b" }, "Version": "1.27.2-r11", "Arch": "x86_64", @@ -7755,7 +7806,8 @@ "ID": "subversion@1.9.7-r0", "Name": "subversion", "Identifier": { - "PURL": "pkg:apk/alpine/subversion@1.9.7-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/subversion@1.9.7-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "b8bc1bc573cbdaf7" }, "Version": "1.9.7-r0", "Arch": "x86_64", @@ -7813,7 +7865,8 @@ "ID": "subversion-libs@1.9.7-r0", "Name": "subversion-libs", "Identifier": { - "PURL": "pkg:apk/alpine/subversion-libs@1.9.7-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/subversion-libs@1.9.7-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "61329cde7aca5119" }, "Version": "1.9.7-r0", "Arch": "x86_64", @@ -7876,7 +7929,8 @@ "ID": "tar@1.29-r1", "Name": "tar", "Identifier": { - "PURL": "pkg:apk/alpine/tar@1.29-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/tar@1.29-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "636f4bd512a19da9" }, "Version": "1.29-r1", "Arch": "x86_64", @@ -7903,7 +7957,8 @@ "ID": "tini@0.16.1-r0", "Name": "tini", "Identifier": { - "PURL": "pkg:apk/alpine/tini@0.16.1-r0?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/tini@0.16.1-r0?arch=x86_64\u0026distro=3.7.1", + "UID": "eafb8d9cee8b473c" }, "Version": "0.16.1-r0", "Arch": "x86_64", @@ -7928,7 +7983,8 @@ "ID": "xz@5.2.3-r1", "Name": "xz", "Identifier": { - "PURL": "pkg:apk/alpine/xz@5.2.3-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/xz@5.2.3-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "ccae1ee63a106adf" }, "Version": "5.2.3-r1", "Arch": "x86_64", @@ -7976,7 +8032,8 @@ "ID": "xz-libs@5.2.3-r1", "Name": "xz-libs", "Identifier": { - "PURL": "pkg:apk/alpine/xz-libs@5.2.3-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/xz-libs@5.2.3-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "d1e50abea889c8e6" }, "Version": "5.2.3-r1", "Arch": "x86_64", @@ -8002,7 +8059,8 @@ "ID": "zlib@1.2.11-r1", "Name": "zlib", "Identifier": { - "PURL": "pkg:apk/alpine/zlib@1.2.11-r1?arch=x86_64\u0026distro=3.7.1" + "PURL": "pkg:apk/alpine/zlib@1.2.11-r1?arch=x86_64\u0026distro=3.7.1", + "UID": "1367b2e4f6864f4c" }, "Version": "1.2.11-r1", "Arch": "x86_64", diff --git a/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden b/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden index 0b85bf333243..58abc9c33585 100644 --- a/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden +++ b/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden @@ -7,7 +7,8 @@ "ID": "actioncable@5.2.3", "Name": "actioncable", "Identifier": { - "PURL": "pkg:gem/actioncable@5.2.3" + "PURL": "pkg:gem/actioncable@5.2.3", + "UID": "a2f0009734c47a50" }, "Version": "5.2.3", "Indirect": true, @@ -29,7 +30,8 @@ "ID": "actionmailer@5.2.3", "Name": "actionmailer", "Identifier": { - "PURL": "pkg:gem/actionmailer@5.2.3" + "PURL": "pkg:gem/actionmailer@5.2.3", + "UID": "957dd2cc6d970211" }, "Version": "5.2.3", "Indirect": true, @@ -53,7 +55,8 @@ "ID": "actionpack@5.2.3", "Name": "actionpack", "Identifier": { - "PURL": "pkg:gem/actionpack@5.2.3" + "PURL": "pkg:gem/actionpack@5.2.3", + "UID": "b4e4b4e5f7967b8a" }, "Version": "5.2.3", "Indirect": true, @@ -78,7 +81,8 @@ "ID": "actionview@5.2.3", "Name": "actionview", "Identifier": { - "PURL": "pkg:gem/actionview@5.2.3" + "PURL": "pkg:gem/actionview@5.2.3", + "UID": "e303cfc150443baf" }, "Version": "5.2.3", "Indirect": true, @@ -102,7 +106,8 @@ "ID": "activejob@5.2.3", "Name": "activejob", "Identifier": { - "PURL": "pkg:gem/activejob@5.2.3" + "PURL": "pkg:gem/activejob@5.2.3", + "UID": "107f18a1d47c926e" }, "Version": "5.2.3", "Indirect": true, @@ -123,7 +128,8 @@ "ID": "activemodel@5.2.3", "Name": "activemodel", "Identifier": { - "PURL": "pkg:gem/activemodel@5.2.3" + "PURL": "pkg:gem/activemodel@5.2.3", + "UID": "22b9bacf4efb92d7" }, "Version": "5.2.3", "Indirect": true, @@ -143,7 +149,8 @@ "ID": "activerecord@5.2.3", "Name": "activerecord", "Identifier": { - "PURL": "pkg:gem/activerecord@5.2.3" + "PURL": "pkg:gem/activerecord@5.2.3", + "UID": "a30ec4dd4f1cad0d" }, "Version": "5.2.3", "Indirect": true, @@ -165,7 +172,8 @@ "ID": "activestorage@5.2.3", "Name": "activestorage", "Identifier": { - "PURL": "pkg:gem/activestorage@5.2.3" + "PURL": "pkg:gem/activestorage@5.2.3", + "UID": "4e3f0e59a70b38a9" }, "Version": "5.2.3", "Indirect": true, @@ -187,7 +195,8 @@ "ID": "activesupport@5.2.3", "Name": "activesupport", "Identifier": { - "PURL": "pkg:gem/activesupport@5.2.3" + "PURL": "pkg:gem/activesupport@5.2.3", + "UID": "b83d5718f12b7df1" }, "Version": "5.2.3", "Indirect": true, @@ -210,7 +219,8 @@ "ID": "arel@9.0.0", "Name": "arel", "Identifier": { - "PURL": "pkg:gem/arel@9.0.0" + "PURL": "pkg:gem/arel@9.0.0", + "UID": "9a68b51c670c5d28" }, "Version": "9.0.0", "Indirect": true, @@ -227,7 +237,8 @@ "ID": "ast@2.4.0", "Name": "ast", "Identifier": { - "PURL": "pkg:gem/ast@2.4.0" + "PURL": "pkg:gem/ast@2.4.0", + "UID": "fad54f55dc524edc" }, "Version": "2.4.0", "Indirect": true, @@ -244,7 +255,8 @@ "ID": "builder@3.2.3", "Name": "builder", "Identifier": { - "PURL": "pkg:gem/builder@3.2.3" + "PURL": "pkg:gem/builder@3.2.3", + "UID": "4998ad08ac24b078" }, "Version": "3.2.3", "Indirect": true, @@ -261,7 +273,8 @@ "ID": "coderay@1.1.2", "Name": "coderay", "Identifier": { - "PURL": "pkg:gem/coderay@1.1.2" + "PURL": "pkg:gem/coderay@1.1.2", + "UID": "a48fa0679d10835e" }, "Version": "1.1.2", "Indirect": true, @@ -278,7 +291,8 @@ "ID": "concurrent-ruby@1.1.5", "Name": "concurrent-ruby", "Identifier": { - "PURL": "pkg:gem/concurrent-ruby@1.1.5" + "PURL": "pkg:gem/concurrent-ruby@1.1.5", + "UID": "bca3d9447a1c8032" }, "Version": "1.1.5", "Indirect": true, @@ -295,7 +309,8 @@ "ID": "crass@1.0.4", "Name": "crass", "Identifier": { - "PURL": "pkg:gem/crass@1.0.4" + "PURL": "pkg:gem/crass@1.0.4", + "UID": "63d9762f8a29e52" }, "Version": "1.0.4", "Indirect": true, @@ -312,7 +327,8 @@ "ID": "dotenv@2.7.2", "Name": "dotenv", "Identifier": { - "PURL": "pkg:gem/dotenv@2.7.2" + "PURL": "pkg:gem/dotenv@2.7.2", + "UID": "bf323ef200ea177a" }, "Version": "2.7.2", "Relationship": "direct", @@ -328,7 +344,8 @@ "ID": "erubi@1.8.0", "Name": "erubi", "Identifier": { - "PURL": "pkg:gem/erubi@1.8.0" + "PURL": "pkg:gem/erubi@1.8.0", + "UID": "de475f0ffcba457d" }, "Version": "1.8.0", "Indirect": true, @@ -345,7 +362,8 @@ "ID": "faker@1.9.3", "Name": "faker", "Identifier": { - "PURL": "pkg:gem/faker@1.9.3" + "PURL": "pkg:gem/faker@1.9.3", + "UID": "b5ac01b24ab4ed39" }, "Version": "1.9.3", "Relationship": "direct", @@ -364,7 +382,8 @@ "ID": "globalid@0.4.2", "Name": "globalid", "Identifier": { - "PURL": "pkg:gem/globalid@0.4.2" + "PURL": "pkg:gem/globalid@0.4.2", + "UID": "daff7877b36bd14b" }, "Version": "0.4.2", "Indirect": true, @@ -384,7 +403,8 @@ "ID": "i18n@1.6.0", "Name": "i18n", "Identifier": { - "PURL": "pkg:gem/i18n@1.6.0" + "PURL": "pkg:gem/i18n@1.6.0", + "UID": "66065208a829ff94" }, "Version": "1.6.0", "Indirect": true, @@ -404,7 +424,8 @@ "ID": "jaro_winkler@1.5.2", "Name": "jaro_winkler", "Identifier": { - "PURL": "pkg:gem/jaro_winkler@1.5.2" + "PURL": "pkg:gem/jaro_winkler@1.5.2", + "UID": "9a5b22728e40960b" }, "Version": "1.5.2", "Indirect": true, @@ -421,7 +442,8 @@ "ID": "json@2.2.0", "Name": "json", "Identifier": { - "PURL": "pkg:gem/json@2.2.0" + "PURL": "pkg:gem/json@2.2.0", + "UID": "cb6c1eb54f8c6e9d" }, "Version": "2.2.0", "Relationship": "direct", @@ -437,7 +459,8 @@ "ID": "loofah@2.2.3", "Name": "loofah", "Identifier": { - "PURL": "pkg:gem/loofah@2.2.3" + "PURL": "pkg:gem/loofah@2.2.3", + "UID": "61fc41b2f5521a64" }, "Version": "2.2.3", "Indirect": true, @@ -458,7 +481,8 @@ "ID": "mail@2.7.1", "Name": "mail", "Identifier": { - "PURL": "pkg:gem/mail@2.7.1" + "PURL": "pkg:gem/mail@2.7.1", + "UID": "edee06a4b6e79f92" }, "Version": "2.7.1", "Indirect": true, @@ -478,7 +502,8 @@ "ID": "marcel@0.3.3", "Name": "marcel", "Identifier": { - "PURL": "pkg:gem/marcel@0.3.3" + "PURL": "pkg:gem/marcel@0.3.3", + "UID": "4695d691e0d7ac17" }, "Version": "0.3.3", "Indirect": true, @@ -498,7 +523,8 @@ "ID": "method_source@0.9.2", "Name": "method_source", "Identifier": { - "PURL": "pkg:gem/method_source@0.9.2" + "PURL": "pkg:gem/method_source@0.9.2", + "UID": "b96872ad7feeedb2" }, "Version": "0.9.2", "Indirect": true, @@ -515,7 +541,8 @@ "ID": "mimemagic@0.3.3", "Name": "mimemagic", "Identifier": { - "PURL": "pkg:gem/mimemagic@0.3.3" + "PURL": "pkg:gem/mimemagic@0.3.3", + "UID": "54089ae8801a3144" }, "Version": "0.3.3", "Indirect": true, @@ -532,7 +559,8 @@ "ID": "mini_mime@1.0.1", "Name": "mini_mime", "Identifier": { - "PURL": "pkg:gem/mini_mime@1.0.1" + "PURL": "pkg:gem/mini_mime@1.0.1", + "UID": "ea53b65af3f9c07c" }, "Version": "1.0.1", "Indirect": true, @@ -549,7 +577,8 @@ "ID": "mini_portile2@2.4.0", "Name": "mini_portile2", "Identifier": { - "PURL": "pkg:gem/mini_portile2@2.4.0" + "PURL": "pkg:gem/mini_portile2@2.4.0", + "UID": "503f9a3009616732" }, "Version": "2.4.0", "Indirect": true, @@ -566,7 +595,8 @@ "ID": "minitest@5.11.3", "Name": "minitest", "Identifier": { - "PURL": "pkg:gem/minitest@5.11.3" + "PURL": "pkg:gem/minitest@5.11.3", + "UID": "1d1c12205a5ea0c1" }, "Version": "5.11.3", "Indirect": true, @@ -583,7 +613,8 @@ "ID": "nio4r@2.3.1", "Name": "nio4r", "Identifier": { - "PURL": "pkg:gem/nio4r@2.3.1" + "PURL": "pkg:gem/nio4r@2.3.1", + "UID": "1f214c910b2d6498" }, "Version": "2.3.1", "Indirect": true, @@ -600,7 +631,8 @@ "ID": "nokogiri@1.10.3", "Name": "nokogiri", "Identifier": { - "PURL": "pkg:gem/nokogiri@1.10.3" + "PURL": "pkg:gem/nokogiri@1.10.3", + "UID": "4cadfe4426354933" }, "Version": "1.10.3", "Indirect": true, @@ -620,7 +652,8 @@ "ID": "parallel@1.17.0", "Name": "parallel", "Identifier": { - "PURL": "pkg:gem/parallel@1.17.0" + "PURL": "pkg:gem/parallel@1.17.0", + "UID": "f31aa3a8599717a1" }, "Version": "1.17.0", "Indirect": true, @@ -637,7 +670,8 @@ "ID": "parser@2.6.3.0", "Name": "parser", "Identifier": { - "PURL": "pkg:gem/parser@2.6.3.0" + "PURL": "pkg:gem/parser@2.6.3.0", + "UID": "fa2f74869e82d572" }, "Version": "2.6.3.0", "Indirect": true, @@ -657,7 +691,8 @@ "ID": "pry@0.12.2", "Name": "pry", "Identifier": { - "PURL": "pkg:gem/pry@0.12.2" + "PURL": "pkg:gem/pry@0.12.2", + "UID": "1349a1afdbbc80bc" }, "Version": "0.12.2", "Relationship": "direct", @@ -677,7 +712,8 @@ "ID": "psych@3.1.0", "Name": "psych", "Identifier": { - "PURL": "pkg:gem/psych@3.1.0" + "PURL": "pkg:gem/psych@3.1.0", + "UID": "34bd03308f382650" }, "Version": "3.1.0", "Indirect": true, @@ -694,7 +730,8 @@ "ID": "rack@2.0.7", "Name": "rack", "Identifier": { - "PURL": "pkg:gem/rack@2.0.7" + "PURL": "pkg:gem/rack@2.0.7", + "UID": "d9f77162d5199665" }, "Version": "2.0.7", "Indirect": true, @@ -711,7 +748,8 @@ "ID": "rack-test@1.1.0", "Name": "rack-test", "Identifier": { - "PURL": "pkg:gem/rack-test@1.1.0" + "PURL": "pkg:gem/rack-test@1.1.0", + "UID": "ce77144d56bbfcc7" }, "Version": "1.1.0", "Indirect": true, @@ -731,7 +769,8 @@ "ID": "rails@5.2.0", "Name": "rails", "Identifier": { - "PURL": "pkg:gem/rails@5.2.0" + "PURL": "pkg:gem/rails@5.2.0", + "UID": "edef417a0d4c73e" }, "Version": "5.2.0", "Relationship": "direct", @@ -760,7 +799,8 @@ "ID": "rails-dom-testing@2.0.3", "Name": "rails-dom-testing", "Identifier": { - "PURL": "pkg:gem/rails-dom-testing@2.0.3" + "PURL": "pkg:gem/rails-dom-testing@2.0.3", + "UID": "f86753ba2b01f92d" }, "Version": "2.0.3", "Indirect": true, @@ -781,7 +821,8 @@ "ID": "rails-html-sanitizer@1.0.3", "Name": "rails-html-sanitizer", "Identifier": { - "PURL": "pkg:gem/rails-html-sanitizer@1.0.3" + "PURL": "pkg:gem/rails-html-sanitizer@1.0.3", + "UID": "ca9d0e53040b92a7" }, "Version": "1.0.3", "Indirect": true, @@ -801,7 +842,8 @@ "ID": "railties@5.2.3", "Name": "railties", "Identifier": { - "PURL": "pkg:gem/railties@5.2.3" + "PURL": "pkg:gem/railties@5.2.3", + "UID": "79238ef7535ec96f" }, "Version": "5.2.3", "Indirect": true, @@ -825,7 +867,8 @@ "ID": "rainbow@3.0.0", "Name": "rainbow", "Identifier": { - "PURL": "pkg:gem/rainbow@3.0.0" + "PURL": "pkg:gem/rainbow@3.0.0", + "UID": "b370837d83c44cb7" }, "Version": "3.0.0", "Indirect": true, @@ -842,7 +885,8 @@ "ID": "rake@12.3.2", "Name": "rake", "Identifier": { - "PURL": "pkg:gem/rake@12.3.2" + "PURL": "pkg:gem/rake@12.3.2", + "UID": "4f4252bdb5e59b57" }, "Version": "12.3.2", "Indirect": true, @@ -859,7 +903,8 @@ "ID": "rubocop@0.67.2", "Name": "rubocop", "Identifier": { - "PURL": "pkg:gem/rubocop@0.67.2" + "PURL": "pkg:gem/rubocop@0.67.2", + "UID": "68a7b25b67dfb858" }, "Version": "0.67.2", "Relationship": "direct", @@ -884,7 +929,8 @@ "ID": "ruby-progressbar@1.10.0", "Name": "ruby-progressbar", "Identifier": { - "PURL": "pkg:gem/ruby-progressbar@1.10.0" + "PURL": "pkg:gem/ruby-progressbar@1.10.0", + "UID": "3955293b43d2fd52" }, "Version": "1.10.0", "Indirect": true, @@ -901,7 +947,8 @@ "ID": "sprockets@3.7.2", "Name": "sprockets", "Identifier": { - "PURL": "pkg:gem/sprockets@3.7.2" + "PURL": "pkg:gem/sprockets@3.7.2", + "UID": "116e2747536e43f5" }, "Version": "3.7.2", "Indirect": true, @@ -922,7 +969,8 @@ "ID": "sprockets-rails@3.2.1", "Name": "sprockets-rails", "Identifier": { - "PURL": "pkg:gem/sprockets-rails@3.2.1" + "PURL": "pkg:gem/sprockets-rails@3.2.1", + "UID": "24b0c150e29c714e" }, "Version": "3.2.1", "Indirect": true, @@ -944,7 +992,8 @@ "ID": "thor@0.20.3", "Name": "thor", "Identifier": { - "PURL": "pkg:gem/thor@0.20.3" + "PURL": "pkg:gem/thor@0.20.3", + "UID": "85877345b4057dc4" }, "Version": "0.20.3", "Indirect": true, @@ -961,7 +1010,8 @@ "ID": "thread_safe@0.3.6", "Name": "thread_safe", "Identifier": { - "PURL": "pkg:gem/thread_safe@0.3.6" + "PURL": "pkg:gem/thread_safe@0.3.6", + "UID": "2851ab474c97947e" }, "Version": "0.3.6", "Indirect": true, @@ -978,7 +1028,8 @@ "ID": "tzinfo@1.2.5", "Name": "tzinfo", "Identifier": { - "PURL": "pkg:gem/tzinfo@1.2.5" + "PURL": "pkg:gem/tzinfo@1.2.5", + "UID": "1ef6e099466a3dd" }, "Version": "1.2.5", "Indirect": true, @@ -998,7 +1049,8 @@ "ID": "unicode-display_width@1.5.0", "Name": "unicode-display_width", "Identifier": { - "PURL": "pkg:gem/unicode-display_width@1.5.0" + "PURL": "pkg:gem/unicode-display_width@1.5.0", + "UID": "5212ef154d81feeb" }, "Version": "1.5.0", "Indirect": true, @@ -1015,7 +1067,8 @@ "ID": "websocket-driver@0.7.0", "Name": "websocket-driver", "Identifier": { - "PURL": "pkg:gem/websocket-driver@0.7.0" + "PURL": "pkg:gem/websocket-driver@0.7.0", + "UID": "6d4862fcfa71d8db" }, "Version": "0.7.0", "Indirect": true, @@ -1035,7 +1088,8 @@ "ID": "websocket-extensions@0.1.3", "Name": "websocket-extensions", "Identifier": { - "PURL": "pkg:gem/websocket-extensions@0.1.3" + "PURL": "pkg:gem/websocket-extensions@0.1.3", + "UID": "84dd4f30cc911d14" }, "Version": "0.1.3", "Indirect": true, @@ -1058,7 +1112,8 @@ "ID": "ammonia@1.9.0", "Name": "ammonia", "Identifier": { - "PURL": "pkg:cargo/ammonia@1.9.0" + "PURL": "pkg:cargo/ammonia@1.9.0", + "UID": "3f782b3907f82c00" }, "Version": "1.9.0", "DependsOn": [ @@ -1081,7 +1136,8 @@ "ID": "autocfg@0.1.2", "Name": "autocfg", "Identifier": { - "PURL": "pkg:cargo/autocfg@0.1.2" + "PURL": "pkg:cargo/autocfg@0.1.2", + "UID": "527fae74cf16ee7b" }, "Version": "0.1.2", "Layer": {}, @@ -1096,7 +1152,8 @@ "ID": "bitflags@0.7.0", "Name": "bitflags", "Identifier": { - "PURL": "pkg:cargo/bitflags@0.7.0" + "PURL": "pkg:cargo/bitflags@0.7.0", + "UID": "7ed30462ad4d3f73" }, "Version": "0.7.0", "Layer": {}, @@ -1111,7 +1168,8 @@ "ID": "bitflags@1.0.4", "Name": "bitflags", "Identifier": { - "PURL": "pkg:cargo/bitflags@1.0.4" + "PURL": "pkg:cargo/bitflags@1.0.4", + "UID": "923d02b9471117a4" }, "Version": "1.0.4", "Layer": {}, @@ -1126,7 +1184,8 @@ "ID": "cfg-if@0.1.7", "Name": "cfg-if", "Identifier": { - "PURL": "pkg:cargo/cfg-if@0.1.7" + "PURL": "pkg:cargo/cfg-if@0.1.7", + "UID": "534d592a586af917" }, "Version": "0.1.7", "Layer": {}, @@ -1141,7 +1200,8 @@ "ID": "cloudabi@0.0.3", "Name": "cloudabi", "Identifier": { - "PURL": "pkg:cargo/cloudabi@0.0.3" + "PURL": "pkg:cargo/cloudabi@0.0.3", + "UID": "7d95ca530080060e" }, "Version": "0.0.3", "DependsOn": [ @@ -1159,7 +1219,8 @@ "ID": "fuchsia-cprng@0.1.1", "Name": "fuchsia-cprng", "Identifier": { - "PURL": "pkg:cargo/fuchsia-cprng@0.1.1" + "PURL": "pkg:cargo/fuchsia-cprng@0.1.1", + "UID": "31513de656860d15" }, "Version": "0.1.1", "Layer": {}, @@ -1174,7 +1235,8 @@ "ID": "futf@0.1.4", "Name": "futf", "Identifier": { - "PURL": "pkg:cargo/futf@0.1.4" + "PURL": "pkg:cargo/futf@0.1.4", + "UID": "d94fed27065b26e6" }, "Version": "0.1.4", "DependsOn": [ @@ -1193,7 +1255,8 @@ "ID": "gdi32-sys@0.2.0", "Name": "gdi32-sys", "Identifier": { - "PURL": "pkg:cargo/gdi32-sys@0.2.0" + "PURL": "pkg:cargo/gdi32-sys@0.2.0", + "UID": "f26b4cb6a1dc0271" }, "Version": "0.2.0", "DependsOn": [ @@ -1212,7 +1275,8 @@ "ID": "html5ever@0.23.0", "Name": "html5ever", "Identifier": { - "PURL": "pkg:cargo/html5ever@0.23.0" + "PURL": "pkg:cargo/html5ever@0.23.0", + "UID": "1da853b746306a1b" }, "Version": "0.23.0", "DependsOn": [ @@ -1235,7 +1299,8 @@ "ID": "idna@0.1.5", "Name": "idna", "Identifier": { - "PURL": "pkg:cargo/idna@0.1.5" + "PURL": "pkg:cargo/idna@0.1.5", + "UID": "f128a40f0ba385a8" }, "Version": "0.1.5", "DependsOn": [ @@ -1255,7 +1320,8 @@ "ID": "itoa@0.4.4", "Name": "itoa", "Identifier": { - "PURL": "pkg:cargo/itoa@0.4.4" + "PURL": "pkg:cargo/itoa@0.4.4", + "UID": "4aff7ab01356bada" }, "Version": "0.4.4", "Layer": {}, @@ -1270,7 +1336,8 @@ "ID": "kernel32-sys@0.2.2", "Name": "kernel32-sys", "Identifier": { - "PURL": "pkg:cargo/kernel32-sys@0.2.2" + "PURL": "pkg:cargo/kernel32-sys@0.2.2", + "UID": "2d5abd219ee22df7" }, "Version": "0.2.2", "DependsOn": [ @@ -1289,7 +1356,8 @@ "ID": "lazy_static@0.2.11", "Name": "lazy_static", "Identifier": { - "PURL": "pkg:cargo/lazy_static@0.2.11" + "PURL": "pkg:cargo/lazy_static@0.2.11", + "UID": "dc8cd0220cdde21c" }, "Version": "0.2.11", "Layer": {}, @@ -1304,7 +1372,8 @@ "ID": "lazy_static@1.3.0", "Name": "lazy_static", "Identifier": { - "PURL": "pkg:cargo/lazy_static@1.3.0" + "PURL": "pkg:cargo/lazy_static@1.3.0", + "UID": "2e4334f512c2da87" }, "Version": "1.3.0", "Layer": {}, @@ -1319,7 +1388,8 @@ "ID": "libc@0.2.54", "Name": "libc", "Identifier": { - "PURL": "pkg:cargo/libc@0.2.54" + "PURL": "pkg:cargo/libc@0.2.54", + "UID": "269b1cde2d08166e" }, "Version": "0.2.54", "Layer": {}, @@ -1334,7 +1404,8 @@ "ID": "libressl-pnacl-sys@2.1.6", "Name": "libressl-pnacl-sys", "Identifier": { - "PURL": "pkg:cargo/libressl-pnacl-sys@2.1.6" + "PURL": "pkg:cargo/libressl-pnacl-sys@2.1.6", + "UID": "8c256bb7e4378d69" }, "Version": "2.1.6", "DependsOn": [ @@ -1352,7 +1423,8 @@ "ID": "log@0.4.6", "Name": "log", "Identifier": { - "PURL": "pkg:cargo/log@0.4.6" + "PURL": "pkg:cargo/log@0.4.6", + "UID": "e8bdfac27c5210ae" }, "Version": "0.4.6", "DependsOn": [ @@ -1370,7 +1442,8 @@ "ID": "mac@0.1.1", "Name": "mac", "Identifier": { - "PURL": "pkg:cargo/mac@0.1.1" + "PURL": "pkg:cargo/mac@0.1.1", + "UID": "1185b2b0745c221" }, "Version": "0.1.1", "Layer": {}, @@ -1385,7 +1458,8 @@ "ID": "maplit@1.0.1", "Name": "maplit", "Identifier": { - "PURL": "pkg:cargo/maplit@1.0.1" + "PURL": "pkg:cargo/maplit@1.0.1", + "UID": "8c23f27efaff6301" }, "Version": "1.0.1", "Layer": {}, @@ -1400,7 +1474,8 @@ "ID": "markup5ever@0.8.1", "Name": "markup5ever", "Identifier": { - "PURL": "pkg:cargo/markup5ever@0.8.1" + "PURL": "pkg:cargo/markup5ever@0.8.1", + "UID": "f55027c499fea0c9" }, "Version": "0.8.1", "DependsOn": [ @@ -1426,7 +1501,8 @@ "ID": "matches@0.1.8", "Name": "matches", "Identifier": { - "PURL": "pkg:cargo/matches@0.1.8" + "PURL": "pkg:cargo/matches@0.1.8", + "UID": "81da4fade350ea84" }, "Version": "0.1.8", "Layer": {}, @@ -1441,7 +1517,8 @@ "ID": "new_debug_unreachable@1.0.3", "Name": "new_debug_unreachable", "Identifier": { - "PURL": "pkg:cargo/new_debug_unreachable@1.0.3" + "PURL": "pkg:cargo/new_debug_unreachable@1.0.3", + "UID": "a4666c488ce7b5cb" }, "Version": "1.0.3", "Layer": {}, @@ -1456,7 +1533,8 @@ "ID": "normal@0.1.0", "Name": "normal", "Identifier": { - "PURL": "pkg:cargo/normal@0.1.0" + "PURL": "pkg:cargo/normal@0.1.0", + "UID": "7553ca394bde0964" }, "Version": "0.1.0", "DependsOn": [ @@ -1476,7 +1554,8 @@ "ID": "openssl@0.8.3", "Name": "openssl", "Identifier": { - "PURL": "pkg:cargo/openssl@0.8.3" + "PURL": "pkg:cargo/openssl@0.8.3", + "UID": "4b76d62a38b7e8be" }, "Version": "0.8.3", "DependsOn": [ @@ -1497,7 +1576,8 @@ "ID": "openssl-sys@0.7.17", "Name": "openssl-sys", "Identifier": { - "PURL": "pkg:cargo/openssl-sys@0.7.17" + "PURL": "pkg:cargo/openssl-sys@0.7.17", + "UID": "c53e41230020f3" }, "Version": "0.7.17", "DependsOn": [ @@ -1519,7 +1599,8 @@ "ID": "percent-encoding@1.0.1", "Name": "percent-encoding", "Identifier": { - "PURL": "pkg:cargo/percent-encoding@1.0.1" + "PURL": "pkg:cargo/percent-encoding@1.0.1", + "UID": "c442dfa6d6be550d" }, "Version": "1.0.1", "Layer": {}, @@ -1534,7 +1615,8 @@ "ID": "phf@0.7.24", "Name": "phf", "Identifier": { - "PURL": "pkg:cargo/phf@0.7.24" + "PURL": "pkg:cargo/phf@0.7.24", + "UID": "af388c2916670a2b" }, "Version": "0.7.24", "DependsOn": [ @@ -1552,7 +1634,8 @@ "ID": "phf_codegen@0.7.24", "Name": "phf_codegen", "Identifier": { - "PURL": "pkg:cargo/phf_codegen@0.7.24" + "PURL": "pkg:cargo/phf_codegen@0.7.24", + "UID": "b5a34c00896c8a2d" }, "Version": "0.7.24", "DependsOn": [ @@ -1571,7 +1654,8 @@ "ID": "phf_generator@0.7.24", "Name": "phf_generator", "Identifier": { - "PURL": "pkg:cargo/phf_generator@0.7.24" + "PURL": "pkg:cargo/phf_generator@0.7.24", + "UID": "2ddb8ab0af22fc74" }, "Version": "0.7.24", "DependsOn": [ @@ -1590,7 +1674,8 @@ "ID": "phf_shared@0.7.24", "Name": "phf_shared", "Identifier": { - "PURL": "pkg:cargo/phf_shared@0.7.24" + "PURL": "pkg:cargo/phf_shared@0.7.24", + "UID": "afb775d34fd007ad" }, "Version": "0.7.24", "DependsOn": [ @@ -1608,7 +1693,8 @@ "ID": "pkg-config@0.3.14", "Name": "pkg-config", "Identifier": { - "PURL": "pkg:cargo/pkg-config@0.3.14" + "PURL": "pkg:cargo/pkg-config@0.3.14", + "UID": "5cf1d9ec06ce2165" }, "Version": "0.3.14", "Layer": {}, @@ -1623,7 +1709,8 @@ "ID": "pnacl-build-helper@1.4.11", "Name": "pnacl-build-helper", "Identifier": { - "PURL": "pkg:cargo/pnacl-build-helper@1.4.11" + "PURL": "pkg:cargo/pnacl-build-helper@1.4.11", + "UID": "86af875650c6f361" }, "Version": "1.4.11", "DependsOn": [ @@ -1642,7 +1729,8 @@ "ID": "precomputed-hash@0.1.1", "Name": "precomputed-hash", "Identifier": { - "PURL": "pkg:cargo/precomputed-hash@0.1.1" + "PURL": "pkg:cargo/precomputed-hash@0.1.1", + "UID": "3324b417bef2a649" }, "Version": "0.1.1", "Layer": {}, @@ -1657,7 +1745,8 @@ "ID": "proc-macro2@0.4.30", "Name": "proc-macro2", "Identifier": { - "PURL": "pkg:cargo/proc-macro2@0.4.30" + "PURL": "pkg:cargo/proc-macro2@0.4.30", + "UID": "743ee84514cbfaa2" }, "Version": "0.4.30", "DependsOn": [ @@ -1675,7 +1764,8 @@ "ID": "quote@0.6.12", "Name": "quote", "Identifier": { - "PURL": "pkg:cargo/quote@0.6.12" + "PURL": "pkg:cargo/quote@0.6.12", + "UID": "616e047380490e91" }, "Version": "0.6.12", "DependsOn": [ @@ -1693,7 +1783,8 @@ "ID": "rand@0.4.6", "Name": "rand", "Identifier": { - "PURL": "pkg:cargo/rand@0.4.6" + "PURL": "pkg:cargo/rand@0.4.6", + "UID": "36ea3ab1d79f2eff" }, "Version": "0.4.6", "DependsOn": [ @@ -1715,7 +1806,8 @@ "ID": "rand@0.6.5", "Name": "rand", "Identifier": { - "PURL": "pkg:cargo/rand@0.6.5" + "PURL": "pkg:cargo/rand@0.6.5", + "UID": "189ce5e3b97b9086" }, "Version": "0.6.5", "DependsOn": [ @@ -1743,7 +1835,8 @@ "ID": "rand_chacha@0.1.1", "Name": "rand_chacha", "Identifier": { - "PURL": "pkg:cargo/rand_chacha@0.1.1" + "PURL": "pkg:cargo/rand_chacha@0.1.1", + "UID": "1898cad64a773a50" }, "Version": "0.1.1", "DependsOn": [ @@ -1762,7 +1855,8 @@ "ID": "rand_core@0.3.1", "Name": "rand_core", "Identifier": { - "PURL": "pkg:cargo/rand_core@0.3.1" + "PURL": "pkg:cargo/rand_core@0.3.1", + "UID": "b89466bd93821e2c" }, "Version": "0.3.1", "DependsOn": [ @@ -1780,7 +1874,8 @@ "ID": "rand_core@0.4.0", "Name": "rand_core", "Identifier": { - "PURL": "pkg:cargo/rand_core@0.4.0" + "PURL": "pkg:cargo/rand_core@0.4.0", + "UID": "a17be2d5af86e701" }, "Version": "0.4.0", "Layer": {}, @@ -1795,7 +1890,8 @@ "ID": "rand_hc@0.1.0", "Name": "rand_hc", "Identifier": { - "PURL": "pkg:cargo/rand_hc@0.1.0" + "PURL": "pkg:cargo/rand_hc@0.1.0", + "UID": "876ebd10af20419d" }, "Version": "0.1.0", "DependsOn": [ @@ -1813,7 +1909,8 @@ "ID": "rand_isaac@0.1.1", "Name": "rand_isaac", "Identifier": { - "PURL": "pkg:cargo/rand_isaac@0.1.1" + "PURL": "pkg:cargo/rand_isaac@0.1.1", + "UID": "c82e2542dd4c548b" }, "Version": "0.1.1", "DependsOn": [ @@ -1831,7 +1928,8 @@ "ID": "rand_jitter@0.1.4", "Name": "rand_jitter", "Identifier": { - "PURL": "pkg:cargo/rand_jitter@0.1.4" + "PURL": "pkg:cargo/rand_jitter@0.1.4", + "UID": "f2fd4d5475b3e46" }, "Version": "0.1.4", "DependsOn": [ @@ -1851,7 +1949,8 @@ "ID": "rand_os@0.1.3", "Name": "rand_os", "Identifier": { - "PURL": "pkg:cargo/rand_os@0.1.3" + "PURL": "pkg:cargo/rand_os@0.1.3", + "UID": "f18975abcf140a0d" }, "Version": "0.1.3", "DependsOn": [ @@ -1874,7 +1973,8 @@ "ID": "rand_pcg@0.1.2", "Name": "rand_pcg", "Identifier": { - "PURL": "pkg:cargo/rand_pcg@0.1.2" + "PURL": "pkg:cargo/rand_pcg@0.1.2", + "UID": "2643a3fc473cf1e8" }, "Version": "0.1.2", "DependsOn": [ @@ -1893,7 +1993,8 @@ "ID": "rand_xorshift@0.1.1", "Name": "rand_xorshift", "Identifier": { - "PURL": "pkg:cargo/rand_xorshift@0.1.1" + "PURL": "pkg:cargo/rand_xorshift@0.1.1", + "UID": "d196ca24b2df097a" }, "Version": "0.1.1", "DependsOn": [ @@ -1911,7 +2012,8 @@ "ID": "rdrand@0.4.0", "Name": "rdrand", "Identifier": { - "PURL": "pkg:cargo/rdrand@0.4.0" + "PURL": "pkg:cargo/rdrand@0.4.0", + "UID": "1669ce8589bdb635" }, "Version": "0.4.0", "DependsOn": [ @@ -1929,7 +2031,8 @@ "ID": "remove_dir_all@0.5.1", "Name": "remove_dir_all", "Identifier": { - "PURL": "pkg:cargo/remove_dir_all@0.5.1" + "PURL": "pkg:cargo/remove_dir_all@0.5.1", + "UID": "754bd8e2f6347b0d" }, "Version": "0.5.1", "DependsOn": [ @@ -1947,7 +2050,8 @@ "ID": "ryu@0.2.8", "Name": "ryu", "Identifier": { - "PURL": "pkg:cargo/ryu@0.2.8" + "PURL": "pkg:cargo/ryu@0.2.8", + "UID": "4aae5dd6e37bffdb" }, "Version": "0.2.8", "Layer": {}, @@ -1962,7 +2066,8 @@ "ID": "same-file@0.1.3", "Name": "same-file", "Identifier": { - "PURL": "pkg:cargo/same-file@0.1.3" + "PURL": "pkg:cargo/same-file@0.1.3", + "UID": "cdadbdb5b52c4959" }, "Version": "0.1.3", "DependsOn": [ @@ -1981,7 +2086,8 @@ "ID": "serde@1.0.91", "Name": "serde", "Identifier": { - "PURL": "pkg:cargo/serde@1.0.91" + "PURL": "pkg:cargo/serde@1.0.91", + "UID": "5a88cd7c139193aa" }, "Version": "1.0.91", "Layer": {}, @@ -1996,7 +2102,8 @@ "ID": "serde_derive@1.0.91", "Name": "serde_derive", "Identifier": { - "PURL": "pkg:cargo/serde_derive@1.0.91" + "PURL": "pkg:cargo/serde_derive@1.0.91", + "UID": "afb0ddf32fd9ad05" }, "Version": "1.0.91", "DependsOn": [ @@ -2016,7 +2123,8 @@ "ID": "serde_json@1.0.39", "Name": "serde_json", "Identifier": { - "PURL": "pkg:cargo/serde_json@1.0.39" + "PURL": "pkg:cargo/serde_json@1.0.39", + "UID": "9b8a47a8820505f" }, "Version": "1.0.39", "DependsOn": [ @@ -2036,7 +2144,8 @@ "ID": "siphasher@0.2.3", "Name": "siphasher", "Identifier": { - "PURL": "pkg:cargo/siphasher@0.2.3" + "PURL": "pkg:cargo/siphasher@0.2.3", + "UID": "b790a7dc3920aecc" }, "Version": "0.2.3", "Layer": {}, @@ -2051,7 +2160,8 @@ "ID": "smallvec@0.6.9", "Name": "smallvec", "Identifier": { - "PURL": "pkg:cargo/smallvec@0.6.9" + "PURL": "pkg:cargo/smallvec@0.6.9", + "UID": "f222b109875a6950" }, "Version": "0.6.9", "Layer": {}, @@ -2066,7 +2176,8 @@ "ID": "string_cache@0.7.3", "Name": "string_cache", "Identifier": { - "PURL": "pkg:cargo/string_cache@0.7.3" + "PURL": "pkg:cargo/string_cache@0.7.3", + "UID": "f4fc88e341199b93" }, "Version": "0.7.3", "DependsOn": [ @@ -2090,7 +2201,8 @@ "ID": "string_cache_codegen@0.4.2", "Name": "string_cache_codegen", "Identifier": { - "PURL": "pkg:cargo/string_cache_codegen@0.4.2" + "PURL": "pkg:cargo/string_cache_codegen@0.4.2", + "UID": "325b6a21ff049d27" }, "Version": "0.4.2", "DependsOn": [ @@ -2112,7 +2224,8 @@ "ID": "string_cache_shared@0.3.0", "Name": "string_cache_shared", "Identifier": { - "PURL": "pkg:cargo/string_cache_shared@0.3.0" + "PURL": "pkg:cargo/string_cache_shared@0.3.0", + "UID": "eaf649152096f011" }, "Version": "0.3.0", "Layer": {}, @@ -2127,7 +2240,8 @@ "ID": "syn@0.15.34", "Name": "syn", "Identifier": { - "PURL": "pkg:cargo/syn@0.15.34" + "PURL": "pkg:cargo/syn@0.15.34", + "UID": "dec15b88d2b96dc6" }, "Version": "0.15.34", "DependsOn": [ @@ -2147,7 +2261,8 @@ "ID": "tempdir@0.3.7", "Name": "tempdir", "Identifier": { - "PURL": "pkg:cargo/tempdir@0.3.7" + "PURL": "pkg:cargo/tempdir@0.3.7", + "UID": "5199d2330f7fbafa" }, "Version": "0.3.7", "DependsOn": [ @@ -2166,7 +2281,8 @@ "ID": "tendril@0.4.1", "Name": "tendril", "Identifier": { - "PURL": "pkg:cargo/tendril@0.4.1" + "PURL": "pkg:cargo/tendril@0.4.1", + "UID": "7d6ea579b9fd4cc2" }, "Version": "0.4.1", "DependsOn": [ @@ -2186,7 +2302,8 @@ "ID": "unicode-bidi@0.3.4", "Name": "unicode-bidi", "Identifier": { - "PURL": "pkg:cargo/unicode-bidi@0.3.4" + "PURL": "pkg:cargo/unicode-bidi@0.3.4", + "UID": "7c952948878fa2d2" }, "Version": "0.3.4", "DependsOn": [ @@ -2204,7 +2321,8 @@ "ID": "unicode-normalization@0.1.8", "Name": "unicode-normalization", "Identifier": { - "PURL": "pkg:cargo/unicode-normalization@0.1.8" + "PURL": "pkg:cargo/unicode-normalization@0.1.8", + "UID": "a4be6e256079f76a" }, "Version": "0.1.8", "DependsOn": [ @@ -2222,7 +2340,8 @@ "ID": "unicode-xid@0.1.0", "Name": "unicode-xid", "Identifier": { - "PURL": "pkg:cargo/unicode-xid@0.1.0" + "PURL": "pkg:cargo/unicode-xid@0.1.0", + "UID": "812bfb20444d6773" }, "Version": "0.1.0", "Layer": {}, @@ -2237,7 +2356,8 @@ "ID": "url@1.7.2", "Name": "url", "Identifier": { - "PURL": "pkg:cargo/url@1.7.2" + "PURL": "pkg:cargo/url@1.7.2", + "UID": "7d7ba75dcebda226" }, "Version": "1.7.2", "DependsOn": [ @@ -2257,7 +2377,8 @@ "ID": "user32-sys@0.2.0", "Name": "user32-sys", "Identifier": { - "PURL": "pkg:cargo/user32-sys@0.2.0" + "PURL": "pkg:cargo/user32-sys@0.2.0", + "UID": "49dd6794a7a6486" }, "Version": "0.2.0", "DependsOn": [ @@ -2276,7 +2397,8 @@ "ID": "utf-8@0.7.5", "Name": "utf-8", "Identifier": { - "PURL": "pkg:cargo/utf-8@0.7.5" + "PURL": "pkg:cargo/utf-8@0.7.5", + "UID": "4bd5c4499e77b190" }, "Version": "0.7.5", "Layer": {}, @@ -2291,7 +2413,8 @@ "ID": "walkdir@1.0.7", "Name": "walkdir", "Identifier": { - "PURL": "pkg:cargo/walkdir@1.0.7" + "PURL": "pkg:cargo/walkdir@1.0.7", + "UID": "46be56ccb4ce809f" }, "Version": "1.0.7", "DependsOn": [ @@ -2311,7 +2434,8 @@ "ID": "winapi@0.2.8", "Name": "winapi", "Identifier": { - "PURL": "pkg:cargo/winapi@0.2.8" + "PURL": "pkg:cargo/winapi@0.2.8", + "UID": "2b9eef53fe7d8358" }, "Version": "0.2.8", "Layer": {}, @@ -2326,7 +2450,8 @@ "ID": "winapi@0.3.7", "Name": "winapi", "Identifier": { - "PURL": "pkg:cargo/winapi@0.3.7" + "PURL": "pkg:cargo/winapi@0.3.7", + "UID": "3be65d501e44878f" }, "Version": "0.3.7", "DependsOn": [ @@ -2345,7 +2470,8 @@ "ID": "winapi-build@0.1.1", "Name": "winapi-build", "Identifier": { - "PURL": "pkg:cargo/winapi-build@0.1.1" + "PURL": "pkg:cargo/winapi-build@0.1.1", + "UID": "96ec9e9d498b4ef7" }, "Version": "0.1.1", "Layer": {}, @@ -2360,7 +2486,8 @@ "ID": "winapi-i686-pc-windows-gnu@0.4.0", "Name": "winapi-i686-pc-windows-gnu", "Identifier": { - "PURL": "pkg:cargo/winapi-i686-pc-windows-gnu@0.4.0" + "PURL": "pkg:cargo/winapi-i686-pc-windows-gnu@0.4.0", + "UID": "1b230d529eec4e4f" }, "Version": "0.4.0", "Layer": {}, @@ -2375,7 +2502,8 @@ "ID": "winapi-x86_64-pc-windows-gnu@0.4.0", "Name": "winapi-x86_64-pc-windows-gnu", "Identifier": { - "PURL": "pkg:cargo/winapi-x86_64-pc-windows-gnu@0.4.0" + "PURL": "pkg:cargo/winapi-x86_64-pc-windows-gnu@0.4.0", + "UID": "94fafef2976facb5" }, "Version": "0.4.0", "Layer": {}, @@ -2396,7 +2524,8 @@ "ID": "guzzlehttp/guzzle@6.2.0", "Name": "guzzlehttp/guzzle", "Identifier": { - "PURL": "pkg:composer/guzzlehttp/guzzle@6.2.0" + "PURL": "pkg:composer/guzzlehttp/guzzle@6.2.0", + "UID": "9dfe1e5591642f22" }, "Version": "6.2.0", "Licenses": [ @@ -2418,7 +2547,8 @@ "ID": "guzzlehttp/promises@v1.3.1", "Name": "guzzlehttp/promises", "Identifier": { - "PURL": "pkg:composer/guzzlehttp/promises@v1.3.1" + "PURL": "pkg:composer/guzzlehttp/promises@v1.3.1", + "UID": "24c8c4cc0bc32a5a" }, "Version": "v1.3.1", "Licenses": [ @@ -2436,7 +2566,8 @@ "ID": "guzzlehttp/psr7@1.5.2", "Name": "guzzlehttp/psr7", "Identifier": { - "PURL": "pkg:composer/guzzlehttp/psr7@1.5.2" + "PURL": "pkg:composer/guzzlehttp/psr7@1.5.2", + "UID": "5a2f37cb99b96e3b" }, "Version": "1.5.2", "Licenses": [ @@ -2458,7 +2589,8 @@ "ID": "laravel/installer@v2.0.1", "Name": "laravel/installer", "Identifier": { - "PURL": "pkg:composer/laravel/installer@v2.0.1" + "PURL": "pkg:composer/laravel/installer@v2.0.1", + "UID": "5b2b8cb546357a8e" }, "Version": "v2.0.1", "Licenses": [ @@ -2482,7 +2614,8 @@ "ID": "pear/log@1.13.1", "Name": "pear/log", "Identifier": { - "PURL": "pkg:composer/pear/log@1.13.1" + "PURL": "pkg:composer/pear/log@1.13.1", + "UID": "a5defa6d09ddcecd" }, "Version": "1.13.1", "Licenses": [ @@ -2503,7 +2636,8 @@ "ID": "pear/pear_exception@v1.0.0", "Name": "pear/pear_exception", "Identifier": { - "PURL": "pkg:composer/pear/pear_exception@v1.0.0" + "PURL": "pkg:composer/pear/pear_exception@v1.0.0", + "UID": "9f4d1b12a2cdbcb0" }, "Version": "v1.0.0", "Licenses": [ @@ -2521,7 +2655,8 @@ "ID": "psr/http-message@1.0.1", "Name": "psr/http-message", "Identifier": { - "PURL": "pkg:composer/psr/http-message@1.0.1" + "PURL": "pkg:composer/psr/http-message@1.0.1", + "UID": "2157df4f82a58274" }, "Version": "1.0.1", "Licenses": [ @@ -2539,7 +2674,8 @@ "ID": "ralouphie/getallheaders@2.0.5", "Name": "ralouphie/getallheaders", "Identifier": { - "PURL": "pkg:composer/ralouphie/getallheaders@2.0.5" + "PURL": "pkg:composer/ralouphie/getallheaders@2.0.5", + "UID": "51f513d42ea55b0a" }, "Version": "2.0.5", "Licenses": [ @@ -2557,7 +2693,8 @@ "ID": "symfony/console@v4.2.7", "Name": "symfony/console", "Identifier": { - "PURL": "pkg:composer/symfony/console@v4.2.7" + "PURL": "pkg:composer/symfony/console@v4.2.7", + "UID": "f441e47bb09da6e3" }, "Version": "v4.2.7", "Licenses": [ @@ -2579,7 +2716,8 @@ "ID": "symfony/contracts@v1.0.2", "Name": "symfony/contracts", "Identifier": { - "PURL": "pkg:composer/symfony/contracts@v1.0.2" + "PURL": "pkg:composer/symfony/contracts@v1.0.2", + "UID": "42d464c36bfccb7" }, "Version": "v1.0.2", "Licenses": [ @@ -2597,7 +2735,8 @@ "ID": "symfony/filesystem@v4.2.7", "Name": "symfony/filesystem", "Identifier": { - "PURL": "pkg:composer/symfony/filesystem@v4.2.7" + "PURL": "pkg:composer/symfony/filesystem@v4.2.7", + "UID": "f56f8da2772fc03c" }, "Version": "v4.2.7", "Licenses": [ @@ -2618,7 +2757,8 @@ "ID": "symfony/polyfill-ctype@v1.11.0", "Name": "symfony/polyfill-ctype", "Identifier": { - "PURL": "pkg:composer/symfony/polyfill-ctype@v1.11.0" + "PURL": "pkg:composer/symfony/polyfill-ctype@v1.11.0", + "UID": "12e7a3c47387acbe" }, "Version": "v1.11.0", "Licenses": [ @@ -2636,7 +2776,8 @@ "ID": "symfony/polyfill-mbstring@v1.11.0", "Name": "symfony/polyfill-mbstring", "Identifier": { - "PURL": "pkg:composer/symfony/polyfill-mbstring@v1.11.0" + "PURL": "pkg:composer/symfony/polyfill-mbstring@v1.11.0", + "UID": "3089180417b349cc" }, "Version": "v1.11.0", "Licenses": [ @@ -2654,7 +2795,8 @@ "ID": "symfony/process@v4.2.7", "Name": "symfony/process", "Identifier": { - "PURL": "pkg:composer/symfony/process@v4.2.7" + "PURL": "pkg:composer/symfony/process@v4.2.7", + "UID": "3c70597c19dc7546" }, "Version": "v4.2.7", "Licenses": [ @@ -2678,7 +2820,8 @@ "ID": "asap@2.0.6", "Name": "asap", "Identifier": { - "PURL": "pkg:npm/asap@2.0.6" + "PURL": "pkg:npm/asap@2.0.6", + "UID": "abb547f5589e4959" }, "Version": "2.0.6", "Layer": {}, @@ -2693,7 +2836,8 @@ "ID": "jquery@3.3.9", "Name": "jquery", "Identifier": { - "PURL": "pkg:npm/jquery@3.3.9" + "PURL": "pkg:npm/jquery@3.3.9", + "UID": "1126bc6bfa8295d8" }, "Version": "3.3.9", "Layer": {}, @@ -2708,7 +2852,8 @@ "ID": "js-tokens@4.0.0", "Name": "js-tokens", "Identifier": { - "PURL": "pkg:npm/js-tokens@4.0.0" + "PURL": "pkg:npm/js-tokens@4.0.0", + "UID": "fbee11c088549b29" }, "Version": "4.0.0", "Layer": {}, @@ -2723,7 +2868,8 @@ "ID": "lodash@4.17.4", "Name": "lodash", "Identifier": { - "PURL": "pkg:npm/lodash@4.17.4" + "PURL": "pkg:npm/lodash@4.17.4", + "UID": "4473a912b2b0d935" }, "Version": "4.17.4", "Layer": {}, @@ -2738,7 +2884,8 @@ "ID": "loose-envify@1.4.0", "Name": "loose-envify", "Identifier": { - "PURL": "pkg:npm/loose-envify@1.4.0" + "PURL": "pkg:npm/loose-envify@1.4.0", + "UID": "3ebeb6506cca5763" }, "Version": "1.4.0", "DependsOn": [ @@ -2756,7 +2903,8 @@ "ID": "object-assign@4.1.1", "Name": "object-assign", "Identifier": { - "PURL": "pkg:npm/object-assign@4.1.1" + "PURL": "pkg:npm/object-assign@4.1.1", + "UID": "23ceee882eda18a4" }, "Version": "4.1.1", "Layer": {}, @@ -2771,7 +2919,8 @@ "ID": "promise@8.0.3", "Name": "promise", "Identifier": { - "PURL": "pkg:npm/promise@8.0.3" + "PURL": "pkg:npm/promise@8.0.3", + "UID": "41a88be886ae90fe" }, "Version": "8.0.3", "DependsOn": [ @@ -2789,7 +2938,8 @@ "ID": "prop-types@15.7.2", "Name": "prop-types", "Identifier": { - "PURL": "pkg:npm/prop-types@15.7.2" + "PURL": "pkg:npm/prop-types@15.7.2", + "UID": "f0609ce5dbe652fc" }, "Version": "15.7.2", "DependsOn": [ @@ -2809,7 +2959,8 @@ "ID": "react@16.8.6", "Name": "react", "Identifier": { - "PURL": "pkg:npm/react@16.8.6" + "PURL": "pkg:npm/react@16.8.6", + "UID": "f0d9536f5c050d74" }, "Version": "16.8.6", "DependsOn": [ @@ -2830,7 +2981,8 @@ "ID": "react-is@16.8.6", "Name": "react-is", "Identifier": { - "PURL": "pkg:npm/react-is@16.8.6" + "PURL": "pkg:npm/react-is@16.8.6", + "UID": "cb2875411ead1f9a" }, "Version": "16.8.6", "Layer": {}, @@ -2845,7 +2997,8 @@ "ID": "redux@4.0.1", "Name": "redux", "Identifier": { - "PURL": "pkg:npm/redux@4.0.1" + "PURL": "pkg:npm/redux@4.0.1", + "UID": "e463b04a52085d00" }, "Version": "4.0.1", "DependsOn": [ @@ -2864,7 +3017,8 @@ "ID": "scheduler@0.13.6", "Name": "scheduler", "Identifier": { - "PURL": "pkg:npm/scheduler@0.13.6" + "PURL": "pkg:npm/scheduler@0.13.6", + "UID": "cdc29e2ca0a03edf" }, "Version": "0.13.6", "DependsOn": [ @@ -2883,7 +3037,8 @@ "ID": "symbol-observable@1.2.0", "Name": "symbol-observable", "Identifier": { - "PURL": "pkg:npm/symbol-observable@1.2.0" + "PURL": "pkg:npm/symbol-observable@1.2.0", + "UID": "5318f6146dc0264" }, "Version": "1.2.0", "Layer": {}, @@ -2903,7 +3058,8 @@ { "Name": "amqp", "Identifier": { - "PURL": "pkg:pypi/amqp@2.4.2" + "PURL": "pkg:pypi/amqp@2.4.2", + "UID": "eaf235720b7bd6d4" }, "Version": "2.4.2", "Layer": {}, @@ -2917,7 +3073,8 @@ { "Name": "autopep8", "Identifier": { - "PURL": "pkg:pypi/autopep8@1.4.3" + "PURL": "pkg:pypi/autopep8@1.4.3", + "UID": "c72404a556d5fefc" }, "Version": "1.4.3", "Layer": {}, @@ -2931,7 +3088,8 @@ { "Name": "babel", "Identifier": { - "PURL": "pkg:pypi/babel@2.6.0" + "PURL": "pkg:pypi/babel@2.6.0", + "UID": "4d4043fc18530c4e" }, "Version": "2.6.0", "Layer": {}, @@ -2945,7 +3103,8 @@ { "Name": "billiard", "Identifier": { - "PURL": "pkg:pypi/billiard@3.6.0.0" + "PURL": "pkg:pypi/billiard@3.6.0.0", + "UID": "a49012bdf0aa6572" }, "Version": "3.6.0.0", "Layer": {}, @@ -2959,7 +3118,8 @@ { "Name": "boto3", "Identifier": { - "PURL": "pkg:pypi/boto3@1.9.130" + "PURL": "pkg:pypi/boto3@1.9.130", + "UID": "4bc185d0ab442a72" }, "Version": "1.9.130", "Layer": {}, @@ -2973,7 +3133,8 @@ { "Name": "botocore", "Identifier": { - "PURL": "pkg:pypi/botocore@1.12.130" + "PURL": "pkg:pypi/botocore@1.12.130", + "UID": "c13ca226c8938c5d" }, "Version": "1.12.130", "Layer": {}, @@ -2987,7 +3148,8 @@ { "Name": "celery", "Identifier": { - "PURL": "pkg:pypi/celery@4.3.0" + "PURL": "pkg:pypi/celery@4.3.0", + "UID": "f1e0486cb7f807fd" }, "Version": "4.3.0", "Layer": {}, @@ -3001,7 +3163,8 @@ { "Name": "certifi", "Identifier": { - "PURL": "pkg:pypi/certifi@2019.3.9" + "PURL": "pkg:pypi/certifi@2019.3.9", + "UID": "56ffce20ab1cfcbf" }, "Version": "2019.3.9", "Layer": {}, @@ -3015,7 +3178,8 @@ { "Name": "chardet", "Identifier": { - "PURL": "pkg:pypi/chardet@3.0.4" + "PURL": "pkg:pypi/chardet@3.0.4", + "UID": "f459dfb7e38575b6" }, "Version": "3.0.4", "Layer": {}, @@ -3029,7 +3193,8 @@ { "Name": "decorator", "Identifier": { - "PURL": "pkg:pypi/decorator@4.4.0" + "PURL": "pkg:pypi/decorator@4.4.0", + "UID": "5453b8bdb53908f4" }, "Version": "4.4.0", "Layer": {}, @@ -3043,7 +3208,8 @@ { "Name": "django", "Identifier": { - "PURL": "pkg:pypi/django@2.0.9" + "PURL": "pkg:pypi/django@2.0.9", + "UID": "ae1afb5dd98fda8d" }, "Version": "2.0.9", "Layer": {}, @@ -3057,7 +3223,8 @@ { "Name": "django-celery-beat", "Identifier": { - "PURL": "pkg:pypi/django-celery-beat@1.4.0" + "PURL": "pkg:pypi/django-celery-beat@1.4.0", + "UID": "2456816e402bc4b2" }, "Version": "1.4.0", "Layer": {}, @@ -3071,7 +3238,8 @@ { "Name": "django-cors-headers", "Identifier": { - "PURL": "pkg:pypi/django-cors-headers@2.5.2" + "PURL": "pkg:pypi/django-cors-headers@2.5.2", + "UID": "6b0c11924c07350" }, "Version": "2.5.2", "Layer": {}, @@ -3085,7 +3253,8 @@ { "Name": "django-extensions", "Identifier": { - "PURL": "pkg:pypi/django-extensions@2.1.6" + "PURL": "pkg:pypi/django-extensions@2.1.6", + "UID": "8837120b637a8fd3" }, "Version": "2.1.6", "Layer": {}, @@ -3099,7 +3268,8 @@ { "Name": "django-postgres-extra", "Identifier": { - "PURL": "pkg:pypi/django-postgres-extra" + "PURL": "pkg:pypi/django-postgres-extra", + "UID": "ef80d693bc9e6b51" }, "Layer": {}, "Locations": [ @@ -3112,7 +3282,8 @@ { "Name": "django-redis-cache", "Identifier": { - "PURL": "pkg:pypi/django-redis-cache@2.0.0" + "PURL": "pkg:pypi/django-redis-cache@2.0.0", + "UID": "d87630c5a30c9d3" }, "Version": "2.0.0", "Layer": {}, @@ -3126,7 +3297,8 @@ { "Name": "django-silk", "Identifier": { - "PURL": "pkg:pypi/django-silk@3.0.1" + "PURL": "pkg:pypi/django-silk@3.0.1", + "UID": "3d7f4ecf81af4fc3" }, "Version": "3.0.1", "Layer": {}, @@ -3140,7 +3312,8 @@ { "Name": "django-timezone-field", "Identifier": { - "PURL": "pkg:pypi/django-timezone-field@3.0" + "PURL": "pkg:pypi/django-timezone-field@3.0", + "UID": "2ed5c8d7d921f353" }, "Version": "3.0", "Layer": {}, @@ -3154,7 +3327,8 @@ { "Name": "djangorestframework", "Identifier": { - "PURL": "pkg:pypi/djangorestframework@3.9.2" + "PURL": "pkg:pypi/djangorestframework@3.9.2", + "UID": "b9818850a6395a78" }, "Version": "3.9.2", "Layer": {}, @@ -3168,7 +3342,8 @@ { "Name": "djangorestframework-jwt", "Identifier": { - "PURL": "pkg:pypi/djangorestframework-jwt@1.11.0" + "PURL": "pkg:pypi/djangorestframework-jwt@1.11.0", + "UID": "e0716ea8c627d52f" }, "Version": "1.11.0", "Layer": {}, @@ -3182,7 +3357,8 @@ { "Name": "docutils", "Identifier": { - "PURL": "pkg:pypi/docutils@0.14" + "PURL": "pkg:pypi/docutils@0.14", + "UID": "10ac97ed7a098f18" }, "Version": "0.14", "Layer": {}, @@ -3196,7 +3372,8 @@ { "Name": "flower", "Identifier": { - "PURL": "pkg:pypi/flower@0.9.3" + "PURL": "pkg:pypi/flower@0.9.3", + "UID": "9faba86de45abcc5" }, "Version": "0.9.3", "Layer": {}, @@ -3210,7 +3387,8 @@ { "Name": "gprof2dot", "Identifier": { - "PURL": "pkg:pypi/gprof2dot@2016.10.13" + "PURL": "pkg:pypi/gprof2dot@2016.10.13", + "UID": "41a5745039319984" }, "Version": "2016.10.13", "Layer": {}, @@ -3224,7 +3402,8 @@ { "Name": "gunicorn", "Identifier": { - "PURL": "pkg:pypi/gunicorn@19.9.0" + "PURL": "pkg:pypi/gunicorn@19.9.0", + "UID": "3feb80b6855d93fc" }, "Version": "19.9.0", "Layer": {}, @@ -3238,7 +3417,8 @@ { "Name": "hiredis", "Identifier": { - "PURL": "pkg:pypi/hiredis@1.0.0" + "PURL": "pkg:pypi/hiredis@1.0.0", + "UID": "bb90d823e31cc905" }, "Version": "1.0.0", "Layer": {}, @@ -3252,7 +3432,8 @@ { "Name": "httplib2", "Identifier": { - "PURL": "pkg:pypi/httplib2@0.12.1" + "PURL": "pkg:pypi/httplib2@0.12.1", + "UID": "cf158fa53fec4809" }, "Version": "0.12.1", "Layer": {}, @@ -3266,7 +3447,8 @@ { "Name": "idna", "Identifier": { - "PURL": "pkg:pypi/idna@2.8" + "PURL": "pkg:pypi/idna@2.8", + "UID": "b14fc2f2a7ef1e0d" }, "Version": "2.8", "Layer": {}, @@ -3280,7 +3462,8 @@ { "Name": "jinja2", "Identifier": { - "PURL": "pkg:pypi/jinja2@2.10.1" + "PURL": "pkg:pypi/jinja2@2.10.1", + "UID": "882fe2359f6ae7d" }, "Version": "2.10.1", "Layer": {}, @@ -3294,7 +3477,8 @@ { "Name": "jmespath", "Identifier": { - "PURL": "pkg:pypi/jmespath@0.9.4" + "PURL": "pkg:pypi/jmespath@0.9.4", + "UID": "82ca41bff20ffe7" }, "Version": "0.9.4", "Layer": {}, @@ -3308,7 +3492,8 @@ { "Name": "kombu", "Identifier": { - "PURL": "pkg:pypi/kombu@4.5.0" + "PURL": "pkg:pypi/kombu@4.5.0", + "UID": "16131f9ec1debbab" }, "Version": "4.5.0", "Layer": {}, @@ -3322,7 +3507,8 @@ { "Name": "markupsafe", "Identifier": { - "PURL": "pkg:pypi/markupsafe@1.1.1" + "PURL": "pkg:pypi/markupsafe@1.1.1", + "UID": "ccd31f5b6cf3b87b" }, "Version": "1.1.1", "Layer": {}, @@ -3336,7 +3522,8 @@ { "Name": "oauth2", "Identifier": { - "PURL": "pkg:pypi/oauth2@1.9.0.post1" + "PURL": "pkg:pypi/oauth2@1.9.0.post1", + "UID": "6ec03a1175710be8" }, "Version": "1.9.0.post1", "Layer": {}, @@ -3350,7 +3537,8 @@ { "Name": "psycopg2-binary", "Identifier": { - "PURL": "pkg:pypi/psycopg2-binary@2.8.1" + "PURL": "pkg:pypi/psycopg2-binary@2.8.1", + "UID": "24ca65b34656f4fe" }, "Version": "2.8.1", "Layer": {}, @@ -3364,7 +3552,8 @@ { "Name": "py", "Identifier": { - "PURL": "pkg:pypi/py@1.8.0" + "PURL": "pkg:pypi/py@1.8.0", + "UID": "cace845a08062384" }, "Version": "1.8.0", "Layer": {}, @@ -3378,7 +3567,8 @@ { "Name": "pycodestyle", "Identifier": { - "PURL": "pkg:pypi/pycodestyle@2.5.0" + "PURL": "pkg:pypi/pycodestyle@2.5.0", + "UID": "5ab70d21f916b062" }, "Version": "2.5.0", "Layer": {}, @@ -3392,7 +3582,8 @@ { "Name": "pycurl", "Identifier": { - "PURL": "pkg:pypi/pycurl@7.43.0.2" + "PURL": "pkg:pypi/pycurl@7.43.0.2", + "UID": "1196e92ba0352e75" }, "Version": "7.43.0.2", "Layer": {}, @@ -3406,7 +3597,8 @@ { "Name": "pygments", "Identifier": { - "PURL": "pkg:pypi/pygments@2.3.1" + "PURL": "pkg:pypi/pygments@2.3.1", + "UID": "ce312b70022257a2" }, "Version": "2.3.1", "Layer": {}, @@ -3420,7 +3612,8 @@ { "Name": "pyjwt", "Identifier": { - "PURL": "pkg:pypi/pyjwt@1.7.1" + "PURL": "pkg:pypi/pyjwt@1.7.1", + "UID": "ca80b0e4aec9de5d" }, "Version": "1.7.1", "Layer": {}, @@ -3434,7 +3627,8 @@ { "Name": "python-crontab", "Identifier": { - "PURL": "pkg:pypi/python-crontab@2.3.6" + "PURL": "pkg:pypi/python-crontab@2.3.6", + "UID": "bc6992ae46534e3" }, "Version": "2.3.6", "Layer": {}, @@ -3448,7 +3642,8 @@ { "Name": "python-dateutil", "Identifier": { - "PURL": "pkg:pypi/python-dateutil@2.8.0" + "PURL": "pkg:pypi/python-dateutil@2.8.0", + "UID": "b2ebaa709e6a347b" }, "Version": "2.8.0", "Layer": {}, @@ -3462,7 +3657,8 @@ { "Name": "python-http-client", "Identifier": { - "PURL": "pkg:pypi/python-http-client@3.1.0" + "PURL": "pkg:pypi/python-http-client@3.1.0", + "UID": "2917289cbf9e4763" }, "Version": "3.1.0", "Layer": {}, @@ -3476,7 +3672,8 @@ { "Name": "pytz", "Identifier": { - "PURL": "pkg:pypi/pytz@2019.1" + "PURL": "pkg:pypi/pytz@2019.1", + "UID": "4ee3e390bf941d50" }, "Version": "2019.1", "Layer": {}, @@ -3490,7 +3687,8 @@ { "Name": "pyyaml", "Identifier": { - "PURL": "pkg:pypi/pyyaml@5.1" + "PURL": "pkg:pypi/pyyaml@5.1", + "UID": "61b1761eb9c760ae" }, "Version": "5.1", "Layer": {}, @@ -3504,7 +3702,8 @@ { "Name": "redis", "Identifier": { - "PURL": "pkg:pypi/redis@3.2.1" + "PURL": "pkg:pypi/redis@3.2.1", + "UID": "265e9c7eea13d22d" }, "Version": "3.2.1", "Layer": {}, @@ -3518,7 +3717,8 @@ { "Name": "requests", "Identifier": { - "PURL": "pkg:pypi/requests@2.21.0" + "PURL": "pkg:pypi/requests@2.21.0", + "UID": "bfa4f165c40babce" }, "Version": "2.21.0", "Layer": {}, @@ -3532,7 +3732,8 @@ { "Name": "retry", "Identifier": { - "PURL": "pkg:pypi/retry@0.9.2" + "PURL": "pkg:pypi/retry@0.9.2", + "UID": "1a4ba07f9995e4ec" }, "Version": "0.9.2", "Layer": {}, @@ -3546,7 +3747,8 @@ { "Name": "s3transfer", "Identifier": { - "PURL": "pkg:pypi/s3transfer@0.2.0" + "PURL": "pkg:pypi/s3transfer@0.2.0", + "UID": "eb74f26bb04808e9" }, "Version": "0.2.0", "Layer": {}, @@ -3560,7 +3762,8 @@ { "Name": "sendgrid", "Identifier": { - "PURL": "pkg:pypi/sendgrid@6.0.4" + "PURL": "pkg:pypi/sendgrid@6.0.4", + "UID": "21f7c84bdff019e5" }, "Version": "6.0.4", "Layer": {}, @@ -3574,7 +3777,8 @@ { "Name": "sentry-sdk", "Identifier": { - "PURL": "pkg:pypi/sentry-sdk@0.7.10" + "PURL": "pkg:pypi/sentry-sdk@0.7.10", + "UID": "2608107c1c7a60c5" }, "Version": "0.7.10", "Layer": {}, @@ -3588,7 +3792,8 @@ { "Name": "six", "Identifier": { - "PURL": "pkg:pypi/six@1.12.0" + "PURL": "pkg:pypi/six@1.12.0", + "UID": "2f910bba245384" }, "Version": "1.12.0", "Layer": {}, @@ -3602,7 +3807,8 @@ { "Name": "sqlparse", "Identifier": { - "PURL": "pkg:pypi/sqlparse@0.3.0" + "PURL": "pkg:pypi/sqlparse@0.3.0", + "UID": "8a41dce5ceb6e25d" }, "Version": "0.3.0", "Layer": {}, @@ -3616,7 +3822,8 @@ { "Name": "tornado", "Identifier": { - "PURL": "pkg:pypi/tornado@5.1.1" + "PURL": "pkg:pypi/tornado@5.1.1", + "UID": "7cb12caf440e98d3" }, "Version": "5.1.1", "Layer": {}, @@ -3630,7 +3837,8 @@ { "Name": "urllib3", "Identifier": { - "PURL": "pkg:pypi/urllib3@1.24.1" + "PURL": "pkg:pypi/urllib3@1.24.1", + "UID": "e6ed0d9bee45f366" }, "Version": "1.24.1", "Layer": {}, @@ -3644,7 +3852,8 @@ { "Name": "vine", "Identifier": { - "PURL": "pkg:pypi/vine@1.3.0" + "PURL": "pkg:pypi/vine@1.3.0", + "UID": "143f2a81995593aa" }, "Version": "1.3.0", "Layer": {}, @@ -3658,7 +3867,8 @@ { "Name": "xmltodict", "Identifier": { - "PURL": "pkg:pypi/xmltodict@0.12.0" + "PURL": "pkg:pypi/xmltodict@0.12.0", + "UID": "c672df016be1530b" }, "Version": "0.12.0", "Layer": {}, diff --git a/pkg/fanal/types/artifact.go b/pkg/fanal/types/artifact.go index 1bc2a0a9575e..6c213a3fad18 100644 --- a/pkg/fanal/types/artifact.go +++ b/pkg/fanal/types/artifact.go @@ -119,6 +119,7 @@ type Package struct { // PkgIdentifier represents a software identifiers in one of more of the supported formats. type PkgIdentifier struct { + UID string `json:",omitempty"` // Calculated by the package struct PURL *packageurl.PackageURL `json:"-"` BOMRef string `json:",omitempty"` // For CycloneDX } @@ -167,7 +168,7 @@ func (id *PkgIdentifier) UnmarshalJSON(data []byte) error { } func (id *PkgIdentifier) Empty() bool { - return id.PURL == nil && id.BOMRef == "" + return id.UID == "" && id.PURL == nil && id.BOMRef == "" } func (id *PkgIdentifier) Match(s string) bool { diff --git a/pkg/rpc/convert.go b/pkg/rpc/convert.go index 5e21e4e1ce4c..033b1944274a 100644 --- a/pkg/rpc/convert.go +++ b/pkg/rpc/convert.go @@ -86,6 +86,7 @@ func ConvertToRPCPkgIdentifier(pkg ftypes.PkgIdentifier) *common.PkgIdentifier { p = pkg.PURL.String() } return &common.PkgIdentifier{ + Uid: pkg.UID, Purl: p, BomRef: pkg.BOMRef, } @@ -236,7 +237,8 @@ func ConvertFromRPCPkgIdentifier(pkg *common.PkgIdentifier) ftypes.PkgIdentifier } pkgID := ftypes.PkgIdentifier{ - BOMRef: pkg.BomRef, + UID: pkg.GetUid(), + BOMRef: pkg.GetBomRef(), } if pkg.Purl != "" { diff --git a/pkg/rpc/convert_test.go b/pkg/rpc/convert_test.go index a74f8eecb99b..f7c7b3d36d86 100644 --- a/pkg/rpc/convert_test.go +++ b/pkg/rpc/convert_test.go @@ -55,6 +55,9 @@ func TestConvertToRpcPkgs(t *testing.T) { }, Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", Indirect: true, + Identifier: ftypes.PkgIdentifier{ + UID: "01", + }, }, }, }, @@ -86,6 +89,9 @@ func TestConvertToRpcPkgs(t *testing.T) { }, Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", Indirect: true, + Identifier: &common.PkgIdentifier{ + Uid: "01", + }, }, }, }, @@ -137,6 +143,9 @@ func TestConvertFromRpcPkgs(t *testing.T) { }, Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", Indirect: true, + Identifier: &common.PkgIdentifier{ + Uid: "01", + }, }, }, }, @@ -168,6 +177,9 @@ func TestConvertFromRpcPkgs(t *testing.T) { }, Digest: "SHA1:901a7b55410321c4d35543506cff2a8613ef5aa2", Indirect: true, + Identifier: ftypes.PkgIdentifier{ + UID: "01", + }, }, }, }, diff --git a/rpc/common/service.pb.go b/rpc/common/service.pb.go index 0174ab2c7f5f..4fcd09927d90 100644 --- a/rpc/common/service.pb.go +++ b/rpc/common/service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 -// protoc v3.19.4 +// protoc-gen-go v1.34.0 +// protoc v5.26.1 // source: rpc/common/service.proto package common @@ -639,6 +639,7 @@ type PkgIdentifier struct { Purl string `protobuf:"bytes,1,opt,name=purl,proto3" json:"purl,omitempty"` BomRef string `protobuf:"bytes,2,opt,name=bom_ref,json=bomRef,proto3" json:"bom_ref,omitempty"` + Uid string `protobuf:"bytes,3,opt,name=uid,proto3" json:"uid,omitempty"` } func (x *PkgIdentifier) Reset() { @@ -687,6 +688,13 @@ func (x *PkgIdentifier) GetBomRef() string { return "" } +func (x *PkgIdentifier) GetUid() string { + if x != nil { + return x.Uid + } + return "" +} + type Location struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2457,322 +2465,323 @@ var file_rpc_common_service_proto_rawDesc = []byte{ 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x65, 0x76, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x64, 0x65, 0x76, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, - 0x74, 0x22, 0x3c, 0x0a, 0x0d, 0x50, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x74, 0x22, 0x4e, 0x0a, 0x0d, 0x50, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x75, 0x72, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x62, 0x6f, 0x6d, 0x5f, 0x72, 0x65, - 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x6f, 0x6d, 0x52, 0x65, 0x66, 0x22, - 0x44, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, - 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, - 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x22, 0xb6, 0x02, 0x0a, 0x10, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, - 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, - 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, - 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, - 0x50, 0x61, 0x74, 0x68, 0x12, 0x39, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, - 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x6f, 0x6d, 0x52, 0x65, 0x66, 0x12, + 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, + 0x64, 0x22, 0x44, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, + 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x22, 0xb6, 0x02, 0x0a, 0x10, 0x4d, 0x69, 0x73, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, + 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, + 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, + 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x39, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, + 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x12, 0x37, 0x0a, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x52, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, + 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x75, + 0x72, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x0a, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x52, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, - 0x37, 0x0a, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0a, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x22, 0xf3, 0x01, 0x0a, 0x0d, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x45, 0x0a, 0x0f, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x52, 0x0e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x12, 0x42, 0x0a, 0x0e, 0x63, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x07, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x52, 0x02, 0x69, 0x64, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x65, + 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x22, 0xf0, 0x01, 0x0a, 0x0e, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x64, 0x76, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x64, 0x76, 0x49, 0x64, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, + 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2f, 0x0a, 0x13, 0x72, 0x65, 0x63, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, + 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x22, 0xf7, 0x03, 0x0a, 0x18, 0x44, 0x65, + 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, + 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, + 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, + 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1f, + 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x55, 0x72, 0x6c, 0x12, + 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x0a, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, + 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x63, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x76, 0x64, 0x5f, 0x69, 0x64, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x76, 0x64, 0x49, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, + 0x65, 0x72, 0x79, 0x22, 0xff, 0x09, 0x0a, 0x0d, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, + 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x49, 0x64, + 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, + 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x78, 0x65, + 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x66, 0x69, 0x78, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, + 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, + 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x70, 0x6b, 0x67, + 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x08, - 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x61, 0x69, 0x6c, - 0x75, 0x72, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, - 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, - 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, - 0x73, 0x12, 0x3b, 0x0a, 0x0a, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, - 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x52, 0x0a, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf3, - 0x01, 0x0a, 0x0d, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x18, - 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x45, 0x0a, 0x0f, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, - 0x0e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, - 0x42, 0x0a, 0x0e, 0x63, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x07, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x52, - 0x02, 0x69, 0x64, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, - 0x72, 0x69, 0x74, 0x79, 0x22, 0xf0, 0x01, 0x0a, 0x0e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x64, 0x76, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x64, 0x76, 0x49, 0x64, 0x12, 0x12, - 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, - 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, - 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2f, 0x0a, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, - 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x12, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, - 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x22, 0xf7, 0x03, 0x0a, 0x18, 0x44, 0x65, 0x74, 0x65, - 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, - 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, - 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x73, 0x6f, - 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, - 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, - 0x72, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, - 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, - 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, - 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, - 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, - 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, - 0x12, 0x42, 0x0a, 0x0e, 0x63, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x76, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0e, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x76, 0x64, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x71, - 0x75, 0x65, 0x72, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x22, 0xff, 0x09, 0x0a, 0x0d, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, - 0x69, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, - 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, - 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x19, - 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x78, 0x65, 0x64, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, - 0x69, 0x78, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, - 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, - 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x73, - 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x70, 0x6b, 0x67, 0x5f, 0x69, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, - 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0d, 0x70, 0x6b, - 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x05, 0x6c, - 0x61, 0x79, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, - 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, - 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, - 0x74, 0x79, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, - 0x39, 0x0a, 0x04, 0x63, 0x76, 0x73, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, - 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, - 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x43, 0x76, 0x73, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x63, 0x76, 0x73, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x77, - 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x63, 0x77, 0x65, - 0x49, 0x64, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x75, - 0x72, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, - 0x79, 0x55, 0x72, 0x6c, 0x12, 0x41, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, - 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, - 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x10, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x10, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x44, 0x61, 0x74, - 0x65, 0x12, 0x48, 0x0a, 0x14, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x61, 0x64, 0x76, 0x69, - 0x73, 0x6f, 0x72, 0x79, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x41, - 0x64, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x12, 0x40, 0x0a, 0x10, 0x63, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x76, 0x75, 0x6c, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x63, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x75, 0x6c, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, - 0x0a, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x09, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x0b, - 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0a, 0x64, 0x61, 0x74, - 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x76, 0x65, 0x6e, 0x64, 0x6f, - 0x72, 0x5f, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x2f, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x56, 0x65, + 0x2e, 0x50, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0d, + 0x70, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x29, 0x0a, + 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, + 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x39, 0x0a, 0x04, 0x63, 0x76, 0x73, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, + 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x43, 0x76, 0x73, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x63, 0x76, 0x73, 0x73, 0x12, 0x17, 0x0a, 0x07, + 0x63, 0x77, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x63, + 0x77, 0x65, 0x49, 0x64, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, + 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, + 0x61, 0x72, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x41, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x73, 0x68, 0x65, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, + 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x44, + 0x61, 0x74, 0x65, 0x12, 0x48, 0x0a, 0x14, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x61, 0x64, + 0x76, 0x69, 0x73, 0x6f, 0x72, 0x79, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x11, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x63, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x41, 0x64, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x12, 0x40, 0x0a, + 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x76, 0x75, 0x6c, 0x6e, 0x5f, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, + 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x75, 0x6c, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, + 0x1d, 0x0a, 0x0a, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x13, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x09, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x49, 0x64, 0x73, 0x12, 0x39, + 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x14, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0a, 0x64, + 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x76, 0x65, 0x6e, + 0x64, 0x6f, 0x72, 0x5f, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x15, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, + 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0e, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, + 0x69, 0x74, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, + 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x15, + 0x0a, 0x06, 0x70, 0x6b, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x70, 0x6b, 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x18, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x4b, 0x0a, + 0x09, 0x43, 0x76, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x56, 0x53, 0x53, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x59, 0x0a, 0x13, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x0e, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, - 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x16, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x15, 0x0a, 0x06, - 0x70, 0x6b, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x6b, - 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x18, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x4b, 0x0a, 0x09, 0x43, - 0x76, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x56, 0x53, 0x53, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x59, 0x0a, 0x13, 0x56, 0x65, 0x6e, 0x64, - 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x22, 0x42, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x57, 0x0a, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, - 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x69, 0x66, 0x66, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x66, 0x66, 0x49, - 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, - 0x22, 0xc3, 0x01, 0x0a, 0x0d, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, - 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, - 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, - 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x26, - 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, - 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x64, 0x65, - 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x76, 0x0a, 0x04, 0x43, 0x56, 0x53, 0x53, 0x12, 0x1b, - 0x0a, 0x09, 0x76, 0x32, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x76, 0x32, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x76, - 0x33, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x76, 0x33, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x32, 0x5f, 0x73, - 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x32, 0x53, 0x63, - 0x6f, 0x72, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x33, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x33, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x98, - 0x01, 0x0a, 0x0e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, - 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, - 0x74, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x2a, 0x0a, - 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xf3, 0x01, 0x0a, 0x04, 0x4c, 0x69, - 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x43, 0x61, 0x75, 0x73, 0x65, 0x12, - 0x1e, 0x0a, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x1c, 0x0a, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x20, 0x0a, - 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x12, - 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x43, 0x61, 0x75, 0x73, 0x65, - 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x43, 0x61, 0x75, 0x73, 0x65, 0x22, - 0x30, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, - 0x73, 0x22, 0x9f, 0x02, 0x0a, 0x0d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, - 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, - 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, - 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, - 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, 0x64, - 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x74, - 0x63, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4a, 0x04, 0x08, - 0x09, 0x10, 0x0a, 0x22, 0x5d, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x69, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, - 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x73, 0x22, 0x85, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, - 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, - 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, - 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3e, 0x0a, 0x08, 0x63, 0x61, - 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, - 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, - 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x6e, 0x75, 0x6d, - 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, - 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, - 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, - 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, - 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, - 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0xed, 0x01, 0x0a, 0x0b, 0x4c, - 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x6c, 0x69, - 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x45, 0x6e, 0x75, 0x6d, - 0x52, 0x0b, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, - 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, - 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, - 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x67, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x67, 0x73, 0x12, - 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, - 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x4c, - 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x3e, 0x0a, - 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, - 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2e, 0x45, - 0x6e, 0x75, 0x6d, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0x95, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, - 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x22, 0x81, 0x01, 0x0a, 0x04, 0x45, 0x6e, - 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, - 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, 0x49, 0x44, 0x44, 0x45, 0x4e, - 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x53, 0x54, 0x52, 0x49, 0x43, 0x54, 0x45, 0x44, - 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x43, 0x49, 0x50, 0x52, 0x4f, 0x43, 0x41, 0x4c, - 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x54, 0x49, 0x43, 0x45, 0x10, 0x04, 0x12, 0x0e, - 0x0a, 0x0a, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x56, 0x45, 0x10, 0x05, 0x12, 0x10, - 0x0a, 0x0c, 0x55, 0x4e, 0x45, 0x4e, 0x43, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x45, 0x44, 0x10, 0x06, - 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x07, 0x22, 0x4e, 0x0a, - 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3f, 0x0a, 0x04, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x42, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x57, 0x0a, 0x05, 0x4c, 0x61, 0x79, + 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x69, + 0x66, 0x66, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x66, + 0x66, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x42, 0x79, 0x22, 0xc3, 0x01, 0x0a, 0x0d, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, + 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, + 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, + 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x76, 0x0a, 0x04, 0x43, 0x56, 0x53, 0x53, + 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x32, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x76, 0x32, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x0a, + 0x09, 0x76, 0x33, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x76, 0x33, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x32, + 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x32, + 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x33, 0x5f, 0x73, 0x63, 0x6f, 0x72, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x33, 0x53, 0x63, 0x6f, 0x72, 0x65, + 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, + 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, + 0x50, 0x61, 0x74, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, + 0x2a, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xf3, 0x01, 0x0a, 0x04, + 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x63, 0x61, 0x75, + 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x43, 0x61, 0x75, 0x73, + 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, + 0x20, 0x0a, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x43, 0x61, 0x75, + 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x43, 0x61, 0x75, 0x73, + 0x65, 0x22, 0x30, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x6c, 0x69, 0x6e, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x05, 0x6c, 0x69, + 0x6e, 0x65, 0x73, 0x22, 0x9f, 0x02, 0x0a, 0x0d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, + 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, + 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, + 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, + 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, + 0x61, 0x74, 0x63, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4a, + 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0x5d, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, + 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x12, 0x37, 0x0a, 0x08, 0x66, + 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x73, 0x22, 0x85, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, + 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3e, 0x0a, 0x08, + 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, + 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x6e, + 0x75, 0x6d, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, + 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, + 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, + 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0xed, 0x01, 0x0a, + 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x0c, + 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x45, 0x6e, + 0x75, 0x6d, 0x52, 0x0b, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, + 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, + 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, + 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x67, + 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x22, 0x98, 0x01, 0x0a, + 0x0e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, + 0x3e, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, + 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, + 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0x95, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x22, 0x81, 0x01, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x50, 0x4b, 0x47, 0x10, 0x01, 0x12, - 0x0a, 0x0a, 0x06, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4c, - 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a, 0x44, 0x0a, - 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, - 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, - 0x0a, 0x0a, 0x06, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x48, - 0x49, 0x47, 0x48, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x49, 0x54, 0x49, 0x43, 0x41, - 0x4c, 0x10, 0x04, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, - 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3b, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, 0x49, 0x44, 0x44, + 0x45, 0x4e, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x53, 0x54, 0x52, 0x49, 0x43, 0x54, + 0x45, 0x44, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x43, 0x49, 0x50, 0x52, 0x4f, 0x43, + 0x41, 0x4c, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x54, 0x49, 0x43, 0x45, 0x10, 0x04, + 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x56, 0x45, 0x10, 0x05, + 0x12, 0x10, 0x0a, 0x0c, 0x55, 0x4e, 0x45, 0x4e, 0x43, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x45, 0x44, + 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x07, 0x22, + 0x4e, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3f, + 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x50, 0x4b, 0x47, 0x10, + 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x10, 0x0a, + 0x0c, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a, + 0x44, 0x0a, 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x4f, 0x57, 0x10, + 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, 0x02, 0x12, 0x08, 0x0a, + 0x04, 0x48, 0x49, 0x47, 0x48, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x49, 0x54, 0x49, + 0x43, 0x41, 0x4c, 0x10, 0x04, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, + 0x2f, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/rpc/common/service.proto b/rpc/common/service.proto index d5c1472b4aef..8882bbf20e39 100644 --- a/rpc/common/service.proto +++ b/rpc/common/service.proto @@ -59,6 +59,7 @@ message Package { message PkgIdentifier { string purl = 1; string bom_ref = 2; + string uid = 3; } message Location { From 14c1024b47bff7e2fd59ccfa735906b8ffff2614 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 3 May 2024 17:27:37 +0600 Subject: [PATCH 040/352] refactor: move setting scanners when using compliance reports to flag parsing (#6619) --- pkg/commands/app_test.go | 42 ++++++++++++++++++++++++++++++++++++ pkg/commands/artifact/run.go | 19 ---------------- pkg/flag/options.go | 34 +++++++++++++++++++++++++++-- 3 files changed, 74 insertions(+), 21 deletions(-) diff --git a/pkg/commands/app_test.go b/pkg/commands/app_test.go index c1f1593cf53c..858e2ed6b245 100644 --- a/pkg/commands/app_test.go +++ b/pkg/commands/app_test.go @@ -172,6 +172,7 @@ func TestFlags(t *testing.T) { type want struct { format types.Format severities []dbTypes.Severity + scanners types.Scanners } tests := []struct { name string @@ -193,6 +194,10 @@ func TestFlags(t *testing.T) { dbTypes.SeverityHigh, dbTypes.SeverityCritical, }, + scanners: types.Scanners{ + types.VulnerabilityScanner, + types.SecretScanner, + }, }, }, { @@ -208,6 +213,10 @@ func TestFlags(t *testing.T) { dbTypes.SeverityLow, dbTypes.SeverityMedium, }, + scanners: types.Scanners{ + types.VulnerabilityScanner, + types.SecretScanner, + }, }, }, { @@ -225,6 +234,10 @@ func TestFlags(t *testing.T) { dbTypes.SeverityLow, dbTypes.SeverityHigh, }, + scanners: types.Scanners{ + types.VulnerabilityScanner, + types.SecretScanner, + }, }, }, { @@ -241,6 +254,33 @@ func TestFlags(t *testing.T) { severities: []dbTypes.Severity{ dbTypes.SeverityCritical, }, + scanners: types.Scanners{ + types.VulnerabilityScanner, + types.SecretScanner, + }, + }, + }, + { + name: "happy path with scanners for compliance report", + arguments: []string{ + "test", + "--scanners", + "license", + "--compliance", + "docker-cis", + }, + want: want{ + format: types.FormatTable, + severities: []dbTypes.Severity{ + dbTypes.SeverityUnknown, + dbTypes.SeverityLow, + dbTypes.SeverityMedium, + dbTypes.SeverityHigh, + dbTypes.SeverityCritical, + }, + scanners: types.Scanners{ + types.VulnerabilityScanner, + }, }, }, { @@ -264,6 +304,7 @@ func TestFlags(t *testing.T) { flags := &flag.Flags{ GlobalFlagGroup: globalFlags, ReportFlagGroup: flag.NewReportFlagGroup(), + ScanFlagGroup: flag.NewScanFlagGroup(), } cmd := &cobra.Command{ Use: "test", @@ -280,6 +321,7 @@ func TestFlags(t *testing.T) { assert.Equal(t, tt.want.format, options.Format) assert.Equal(t, tt.want.severities, options.Severities) + assert.Equal(t, tt.want.scanners, options.Scanners) return nil }, } diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index a1fde23d519f..d6a61018fecc 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -533,25 +533,6 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi target = opts.Input } - if opts.Compliance.Spec.ID != "" { - // set scanners types by spec - scanners, err := opts.Compliance.Scanners() - if err != nil { - return ScannerConfig{}, types.ScanOptions{}, xerrors.Errorf("scanner error: %w", err) - } - - opts.Scanners = scanners - opts.ImageConfigScanners = nil - // TODO: define image-config-scanners in the spec - if opts.Compliance.Spec.ID == "docker-cis" { - opts.Scanners = types.Scanners{types.VulnerabilityScanner} - opts.ImageConfigScanners = types.Scanners{ - types.MisconfigScanner, - types.SecretScanner, - } - } - } - scanOptions := types.ScanOptions{ VulnType: opts.VulnType, Scanners: opts.Scanners, diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 9d49f5dfe807..744abbd1ddaa 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -353,7 +353,7 @@ type Options struct { } // Align takes consistency of options -func (o *Options) Align() { +func (o *Options) Align() error { if o.Format == types.FormatSPDX || o.Format == types.FormatSPDXJSON { log.Info(`"--format spdx" and "--format spdx-json" disable security scanning`) o.Scanners = nil @@ -364,6 +364,34 @@ func (o *Options) Align() { log.Info(`"--format cyclonedx" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the CycloneDX report.`) o.Scanners = nil } + + if o.Compliance.Spec.ID != "" { + if viper.IsSet(ScannersFlag.ConfigName) { + log.Info(`The option to change scanners is disabled for scanning with the "--compliance" flag. Default scanners used.`) + } + if viper.IsSet(ImageConfigScannersFlag.ConfigName) { + log.Info(`The option to change image config scanners is disabled for scanning with the "--compliance" flag. Default image config scanners used.`) + } + + // set scanners types by spec + scanners, err := o.Compliance.Scanners() + if err != nil { + return xerrors.Errorf("scanner error: %w", err) + } + + o.Scanners = scanners + o.ImageConfigScanners = nil + // TODO: define image-config-scanners in the spec + if o.Compliance.Spec.ID == types.ComplianceDockerCIS { + o.Scanners = types.Scanners{types.VulnerabilityScanner} + o.ImageConfigScanners = types.Scanners{ + types.MisconfigScanner, + types.SecretScanner, + } + } + } + + return nil } // RegistryOpts returns options for OCI registries @@ -693,7 +721,9 @@ func (f *Flags) ToOptions(args []string) (Options, error) { } } - opts.Align() + if err := opts.Align(); err != nil { + return Options{}, xerrors.Errorf("align options error: %w", err) + } return opts, nil } From 9c794c0ffc8d31c82cad3cbd593eb03e689cf583 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Sat, 4 May 2024 08:45:29 +0600 Subject: [PATCH 041/352] fix(misconf): do not use semver for parsing tf module versions (#6614) --- .../parser/parser_integration_test.go | 23 +++++++++++++++++++ .../terraform/parser/resolvers/registry.go | 8 +++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/pkg/iac/scanners/terraform/parser/parser_integration_test.go b/pkg/iac/scanners/terraform/parser/parser_integration_test.go index 1ca634dc280f..58b7b3cfbed7 100644 --- a/pkg/iac/scanners/terraform/parser/parser_integration_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_integration_test.go @@ -49,3 +49,26 @@ module "registry" { require.NoError(t, err) require.Len(t, modules, 2) } + +func Test_ModuleWithPessimisticVersionConstraint(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` +module "registry" { + source = "registry.terraform.io/terraform-aws-modules/s3-bucket/aws" + bucket = "my-s3-bucket" + version = "~> 3.1" +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) + if err := parser.ParseFS(context.TODO(), "code"); err != nil { + t.Fatal(err) + } + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + require.Len(t, modules, 2) +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/registry.go b/pkg/iac/scanners/terraform/parser/resolvers/registry.go index 30ceb1a6bceb..22778cc10230 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/registry.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/registry.go @@ -13,7 +13,7 @@ import ( "golang.org/x/net/idna" - "github.com/aquasecurity/go-version/pkg/semver" + "github.com/aquasecurity/go-version/pkg/version" ) type registryResolver struct { @@ -167,13 +167,13 @@ func resolveVersion(input string, versions moduleVersions) (string, error) { return "", fmt.Errorf("no available versions for module") } - constraints, err := semver.NewConstraints(input) + constraints, err := version.NewConstraints(input) if err != nil { return "", err } - var realVersions semver.Collection + var realVersions version.Collection for _, rawVersion := range versions.Modules[0].Versions { - realVersion, err := semver.Parse(rawVersion.Version) + realVersion, err := version.Parse(rawVersion.Version) if err != nil { continue } From 7a25dadb44a57a1099227cde44e1732f25409cea Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Sat, 4 May 2024 10:24:39 +0600 Subject: [PATCH 042/352] fix(misconf): load cached tf modules (#6607) --- .../config/terraform/terraform_test.go | 5 ++++ pkg/iac/detection/detect.go | 4 +++ .../terraform/parser/load_module_metadata.go | 8 +++--- pkg/iac/scanners/terraform/parser/modules.go | 4 +++ .../scanners/terraform/parser/parser_test.go | 25 +++++++++++++++++++ .../.terraform/modules/modules.json | 1 + .../.terraform/modules/s3-bucket/main.tf | 7 ++++++ .../parser/testdata/cached-modules/main.tf | 5 ++++ 8 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/modules.json create mode 100644 pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/s3-bucket/main.tf create mode 100644 pkg/iac/scanners/terraform/parser/testdata/cached-modules/main.tf diff --git a/pkg/fanal/analyzer/config/terraform/terraform_test.go b/pkg/fanal/analyzer/config/terraform/terraform_test.go index 9096ee062a5e..988a857777cf 100644 --- a/pkg/fanal/analyzer/config/terraform/terraform_test.go +++ b/pkg/fanal/analyzer/config/terraform/terraform_test.go @@ -42,6 +42,11 @@ func TestConfigAnalyzer_Required(t *testing.T) { filePath: "deployment.yaml", want: false, }, + { + name: "manifest snapshot file", + filePath: ".terraform/modules/modules.json", + want: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/iac/detection/detect.go b/pkg/iac/detection/detect.go index 6d1e627d11dc..87c16582ca6c 100644 --- a/pkg/iac/detection/detect.go +++ b/pkg/iac/detection/detect.go @@ -244,6 +244,10 @@ func init() { } func IsTerraformFile(path string) bool { + if strings.HasSuffix(path, filepath.ToSlash(".terraform/modules/modules.json")) { + return true + } + for _, ext := range []string{".tf", ".tf.json", ".tfvars"} { if strings.HasSuffix(path, ext) { return true diff --git a/pkg/iac/scanners/terraform/parser/load_module_metadata.go b/pkg/iac/scanners/terraform/parser/load_module_metadata.go index 7b316f8d66e1..9648e6475ff8 100644 --- a/pkg/iac/scanners/terraform/parser/load_module_metadata.go +++ b/pkg/iac/scanners/terraform/parser/load_module_metadata.go @@ -3,9 +3,11 @@ package parser import ( "encoding/json" "io/fs" - "path/filepath" + "path" ) +const manifestSnapshotFile = ".terraform/modules/modules.json" + type modulesMetadata struct { Modules []struct { Key string `json:"Key"` @@ -16,13 +18,13 @@ type modulesMetadata struct { } func loadModuleMetadata(target fs.FS, fullPath string) (*modulesMetadata, string, error) { - metadataPath := filepath.Join(fullPath, ".terraform/modules/modules.json") // nolint: gocritic + metadataPath := path.Join(fullPath, manifestSnapshotFile) f, err := target.Open(metadataPath) if err != nil { return nil, metadataPath, err } - defer func() { _ = f.Close() }() + defer f.Close() var metadata modulesMetadata if err := json.NewDecoder(f).Decode(&metadata); err != nil { diff --git a/pkg/iac/scanners/terraform/parser/modules.go b/pkg/iac/scanners/terraform/parser/modules.go index 499fc2fb9647..415f52a117d0 100644 --- a/pkg/iac/scanners/terraform/parser/modules.go +++ b/pkg/iac/scanners/terraform/parser/modules.go @@ -16,6 +16,10 @@ import ( // It builds a graph based on the module dependencies and determines the modules that have no incoming dependencies, // considering them as root modules. func (p *Parser) FindRootModules(ctx context.Context, dirs []string) ([]string, error) { + // skip cached terraform modules as they cannot be root modules + dirs = lo.Filter(dirs, func(dir string, _ int) bool { + return !strings.Contains(dir, ".terraform/modules/") + }) for _, dir := range dirs { if err := p.ParseFS(ctx, dir); err != nil { return nil, err diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index 554a8b0fb3d0..48181eee63e7 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -3,6 +3,7 @@ package parser import ( "context" "os" + "path/filepath" "sort" "testing" @@ -1699,3 +1700,27 @@ resource "test" "values" { "a": cty.NumberIntVal(1), "b": cty.NumberIntVal(2), }))) } + +func Test_LoadLocalCachedModule(t *testing.T) { + fsys := os.DirFS(filepath.Join("testdata", "cached-modules")) + + parser := New( + fsys, "", + OptionStopOnHCLError(true), + OptionWithDownloads(false), + ) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + + assert.Len(t, modules, 2) + + buckets := modules.GetResourcesByType("aws_s3_bucket") + assert.Len(t, buckets, 1) + + assert.Equal(t, "my-private-module/s3-bucket/aws/.terraform/modules/s3-bucket/main.tf", buckets[0].GetMetadata().Range().GetFilename()) + + bucketName := buckets[0].GetAttribute("bucket").Value().AsString() + assert.Equal(t, "my-s3-bucket", bucketName) +} diff --git a/pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/modules.json b/pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/modules.json new file mode 100644 index 000000000000..76be610c1339 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/modules.json @@ -0,0 +1 @@ +{"Modules":[{"Key":"","Source":"","Dir":"."},{"Key":"s3-bucket","Source":"registry.myregistry.org/my-private-module/s3-bucket/aws","Version":"1.0.0","Dir":".terraform/modules/s3-bucket"}]} \ No newline at end of file diff --git a/pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/s3-bucket/main.tf b/pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/s3-bucket/main.tf new file mode 100644 index 000000000000..68506b9930b8 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/testdata/cached-modules/.terraform/modules/s3-bucket/main.tf @@ -0,0 +1,7 @@ +variable "bucket" { + type = string +} + +resource "aws_s3_bucket" "this" { + bucket = var.bucket +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraform/parser/testdata/cached-modules/main.tf b/pkg/iac/scanners/terraform/parser/testdata/cached-modules/main.tf new file mode 100644 index 000000000000..231058b26e01 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/testdata/cached-modules/main.tf @@ -0,0 +1,5 @@ +module "s3-bucket" { + source = "my-private-module/s3-bucket/aws" + version = "1.0.0" + bucket = "my-s3-bucket" +} \ No newline at end of file From 8016b821a260840ccb81ef520f2804b9482f3820 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Sat, 4 May 2024 09:34:54 +0400 Subject: [PATCH 043/352] fix(fs): handle default skip dirs properly (#6628) Signed-off-by: knqyf263 --- pkg/fanal/walker/fs.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/fanal/walker/fs.go b/pkg/fanal/walker/fs.go index 8255585192d9..f6bec84d1fc2 100644 --- a/pkg/fanal/walker/fs.go +++ b/pkg/fanal/walker/fs.go @@ -23,8 +23,8 @@ func NewFS() *FS { // Walk walks the filesystem rooted at root, calling fn for each unfiltered file. func (w *FS) Walk(root string, opt Option, fn WalkFunc) error { opt.SkipFiles = w.BuildSkipPaths(root, opt.SkipFiles) - opt.SkipDirs = append(opt.SkipDirs, defaultSkipDirs...) opt.SkipDirs = w.BuildSkipPaths(root, opt.SkipDirs) + opt.SkipDirs = append(opt.SkipDirs, defaultSkipDirs...) walkDirFunc := w.WalkDirFunc(root, fn, opt) walkDirFunc = w.onError(walkDirFunc) @@ -50,24 +50,24 @@ func (w *FS) WalkDirFunc(root string, fn WalkFunc, opt Option) fs.WalkDirFunc { } relPath = filepath.ToSlash(relPath) - info, err := d.Info() - if err != nil { - return xerrors.Errorf("file info error: %w", err) - } - // Skip unnecessary files switch { - case info.IsDir(): + case d.IsDir(): if SkipPath(relPath, opt.SkipDirs) { return filepath.SkipDir } return nil - case !info.Mode().IsRegular(): + case !d.Type().IsRegular(): return nil case SkipPath(relPath, opt.SkipFiles): return nil } + info, err := d.Info() + if err != nil { + return xerrors.Errorf("file info error: %w", err) + } + if err = fn(relPath, info, fileOpener(filePath)); err != nil { return xerrors.Errorf("failed to analyze file: %w", err) } @@ -83,7 +83,7 @@ func (w *FS) onError(wrapped fs.WalkDirFunc) fs.WalkDirFunc { // Unwrap fs.SkipDir error case errors.Is(err, fs.SkipDir): return fs.SkipDir - // ignore permission errors + // Ignore permission errors case os.IsPermission(err): return nil case err != nil: From 290462be6f5d1e6f51df2eb77d7c66cced1ec17a Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 6 May 2024 12:24:44 +0600 Subject: [PATCH 044/352] chore(deps): bump `knqyf263/trivy-issue-action` to v0.0.6 (#6632) --- .github/workflows/scan.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scan.yaml b/.github/workflows/scan.yaml index 82c8903c43c0..475397dc6e35 100644 --- a/.github/workflows/scan.yaml +++ b/.github/workflows/scan.yaml @@ -13,7 +13,7 @@ jobs: uses: actions/checkout@v4.1.4 - name: Run Trivy vulnerability scanner and create GitHub issues - uses: knqyf263/trivy-issue-action@v0.0.5 + uses: knqyf263/trivy-issue-action@v0.0.6 with: assignee: knqyf263 severity: CRITICAL From 2482aa74f82707f24c7689401692d9abae92ff16 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Mon, 6 May 2024 13:58:16 +0600 Subject: [PATCH 045/352] docs: fix usage of image-config-scanners (#6635) --- docs/docs/target/container_image.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/target/container_image.md b/docs/docs/target/container_image.md index 4a0cad1673a1..948cf9678283 100644 --- a/docs/docs/target/container_image.md +++ b/docs/docs/target/container_image.md @@ -107,7 +107,7 @@ The image config is converted into Dockerfile and Trivy handles it as Dockerfile See [here](../scanner/misconfiguration/index.md) for the detail of Dockerfile scanning. It is disabled by default. -You can enable it with `--image-config-scanners config`. +You can enable it with `--image-config-scanners misconfig`. ``` $ trivy image --image-config-scanners misconfig [YOUR_IMAGE_NAME] From 38e2fbf7f9a26a293b0c7e65f29932bca7b34871 Mon Sep 17 00:00:00 2001 From: Katrin Leinweber <9948149+katrinleinweber@users.noreply.github.com> Date: Mon, 6 May 2024 10:26:37 +0200 Subject: [PATCH 046/352] docs: link warning to both timeout config options (#6620) --- pkg/cloud/aws/commands/run.go | 2 +- pkg/commands/artifact/run.go | 2 +- pkg/k8s/commands/run.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/cloud/aws/commands/run.go b/pkg/cloud/aws/commands/run.go index af8afb36f8de..708d9314f94f 100644 --- a/pkg/cloud/aws/commands/run.go +++ b/pkg/cloud/aws/commands/run.go @@ -140,7 +140,7 @@ func Run(ctx context.Context, opt flag.Options) error { var err error defer func() { if errors.Is(err, context.DeadlineExceeded) { - log.Warn("Increase --timeout value") + log.Warn("Provide a higher timeout value, see https://aquasecurity.github.io/trivy/latest/docs/configuration/") } }() diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index d6a61018fecc..14aae3659cac 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -397,7 +397,7 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err defer func() { if errors.Is(err, context.DeadlineExceeded) { - log.Warn("Increase --timeout value") + log.Warn("Provide a higher timeout value, see https://aquasecurity.github.io/trivy/latest/docs/configuration/") } }() diff --git a/pkg/k8s/commands/run.go b/pkg/k8s/commands/run.go index 3516f1afc8e9..7c34bb4feb2a 100644 --- a/pkg/k8s/commands/run.go +++ b/pkg/k8s/commands/run.go @@ -39,7 +39,7 @@ func Run(ctx context.Context, args []string, opts flag.Options) error { defer func() { cancel() if errors.Is(err, context.DeadlineExceeded) { - log.WarnContext(ctx, "Increase --timeout value") + log.WarnContext(ctx, "Provide a higher timeout value, see https://aquasecurity.github.io/trivy/latest/docs/configuration/") } }() opts.K8sVersion = cluster.GetClusterVersion() From 16e9fc054fd5b758ed6d6e74df01c8ec594fdf11 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 6 May 2024 14:43:09 +0600 Subject: [PATCH 047/352] ci: add `generic` dir to deb deploy script (#6636) --- ci/deploy-deb.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/deploy-deb.sh b/ci/deploy-deb.sh index 81cba977afa1..a9e78dcd9465 100755 --- a/ci/deploy-deb.sh +++ b/ci/deploy-deb.sh @@ -5,14 +5,14 @@ UBUNTU_RELEASES=$(sort -u <(ubuntu-distro-info --supported-esm) <(ubuntu-distro- cd trivy-repo/deb -for release in ${DEBIAN_RELEASES[@]} ${UBUNTU_RELEASES[@]}; do +for release in generic ${DEBIAN_RELEASES[@]} ${UBUNTU_RELEASES[@]}; do echo "Removing deb package of $release" reprepro -A i386 remove $release trivy reprepro -A amd64 remove $release trivy reprepro -A arm64 remove $release trivy done -for release in ${DEBIAN_RELEASES[@]} ${UBUNTU_RELEASES[@]}; do +for release in generic ${DEBIAN_RELEASES[@]} ${UBUNTU_RELEASES[@]}; do echo "Adding deb package to $release" reprepro includedeb $release ../../dist/*Linux-32bit.deb reprepro includedeb $release ../../dist/*Linux-64bit.deb From a2c522ddb229f049999c4ce74ef75a0e0f9fdc62 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Mon, 6 May 2024 21:18:41 +0600 Subject: [PATCH 048/352] fix(misconf): skip Rego errors with a nil location (#6638) --- pkg/iac/rego/load.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/iac/rego/load.go b/pkg/iac/rego/load.go index 4c37edb80f33..284fd2f653a2 100644 --- a/pkg/iac/rego/load.go +++ b/pkg/iac/rego/load.go @@ -149,6 +149,10 @@ func (s *Scanner) fallbackChecks(compiler *ast.Compiler) { var excludedFiles []string for _, e := range compiler.Errors { + if e.Location == nil { + continue + } + loc := e.Location.File if lo.Contains(excludedFiles, loc) { From 67c6b1d473999003d682bdb42657bbf3a4a69a9c Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 7 May 2024 10:20:38 +0600 Subject: [PATCH 049/352] perf(misconf): parse rego input once (#6615) Signed-off-by: Simar Co-authored-by: Simar --- pkg/iac/rego/exceptions.go | 8 +++++--- pkg/iac/rego/scanner.go | 35 ++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/pkg/iac/rego/exceptions.go b/pkg/iac/rego/exceptions.go index 7abe9e8a9afa..591c72abdbbb 100644 --- a/pkg/iac/rego/exceptions.go +++ b/pkg/iac/rego/exceptions.go @@ -3,9 +3,11 @@ package rego import ( "context" "fmt" + + "github.com/open-policy-agent/opa/ast" ) -func (s *Scanner) isIgnored(ctx context.Context, namespace, ruleName string, input interface{}) (bool, error) { +func (s *Scanner) isIgnored(ctx context.Context, namespace, ruleName string, input ast.Value) (bool, error) { if ignored, err := s.isNamespaceIgnored(ctx, namespace, input); err != nil { return false, err } else if ignored { @@ -14,7 +16,7 @@ func (s *Scanner) isIgnored(ctx context.Context, namespace, ruleName string, inp return s.isRuleIgnored(ctx, namespace, ruleName, input) } -func (s *Scanner) isNamespaceIgnored(ctx context.Context, namespace string, input interface{}) (bool, error) { +func (s *Scanner) isNamespaceIgnored(ctx context.Context, namespace string, input ast.Value) (bool, error) { exceptionQuery := fmt.Sprintf("data.namespace.exceptions.exception[_] == %q", namespace) result, _, err := s.runQuery(ctx, exceptionQuery, input, true) if err != nil { @@ -23,7 +25,7 @@ func (s *Scanner) isNamespaceIgnored(ctx context.Context, namespace string, inpu return result.Allowed(), nil } -func (s *Scanner) isRuleIgnored(ctx context.Context, namespace, ruleName string, input interface{}) (bool, error) { +func (s *Scanner) isRuleIgnored(ctx context.Context, namespace, ruleName string, input ast.Value) (bool, error) { exceptionQuery := fmt.Sprintf("endswith(%q, data.%s.exception[_][_])", ruleName, namespace) result, _, err := s.runQuery(ctx, exceptionQuery, input, true) if err != nil { diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go index 001e8f52a080..f2b9fff0fdf9 100644 --- a/pkg/iac/rego/scanner.go +++ b/pkg/iac/rego/scanner.go @@ -13,6 +13,7 @@ import ( "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/rego" "github.com/open-policy-agent/opa/storage" + "github.com/open-policy-agent/opa/util" "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" @@ -161,7 +162,7 @@ func (s *Scanner) SetParentDebugLogger(l debug.Logger) { s.debug = l.Extend("rego") } -func (s *Scanner) runQuery(ctx context.Context, query string, input interface{}, disableTracing bool) (rego.ResultSet, []string, error) { +func (s *Scanner) runQuery(ctx context.Context, query string, input ast.Value, disableTracing bool) (rego.ResultSet, []string, error) { trace := (s.traceWriter != nil || s.tracePerResult) && !disableTracing @@ -180,7 +181,7 @@ func (s *Scanner) runQuery(ctx context.Context, query string, input interface{}, } if input != nil { - regoOptions = append(regoOptions, rego.Input(input)) + regoOptions = append(regoOptions, rego.ParsedInput(input)) } instance := rego.New(regoOptions...) @@ -342,6 +343,14 @@ func isPolicyApplicable(staticMetadata *StaticMetadata, inputs ...Input) bool { return false } +func parseRawInput(input any) (ast.Value, error) { + if err := util.RoundTrip(&input); err != nil { + return nil, err + } + + return ast.InterfaceToValue(input) +} + func (s *Scanner) applyRule(ctx context.Context, namespace, rule string, inputs []Input, combined bool) (scan.Results, error) { // handle combined evaluations if possible @@ -354,7 +363,12 @@ func (s *Scanner) applyRule(ctx context.Context, namespace, rule string, inputs qualified := fmt.Sprintf("data.%s.%s", namespace, rule) for _, input := range inputs { s.trace("INPUT", input) - if ignored, err := s.isIgnored(ctx, namespace, rule, input.Contents); err != nil { + parsedInput, err := parseRawInput(input.Contents) + if err != nil { + s.debug.Log("Error occurred while parsing input: %s", err) + continue + } + if ignored, err := s.isIgnored(ctx, namespace, rule, parsedInput); err != nil { return nil, err } else if ignored { var result regoResult @@ -364,7 +378,7 @@ func (s *Scanner) applyRule(ctx context.Context, namespace, rule string, inputs results.AddIgnored(result) continue } - set, traces, err := s.runQuery(ctx, qualified, input.Contents, false) + set, traces, err := s.runQuery(ctx, qualified, parsedInput, false) if err != nil { return nil, err } @@ -388,9 +402,15 @@ func (s *Scanner) applyRuleCombined(ctx context.Context, namespace, rule string, if len(inputs) == 0 { return nil, nil } + + parsed, err := parseRawInput(inputs) + if err != nil { + return nil, fmt.Errorf("failed to parse input: %w", err) + } + var results scan.Results - qualified := fmt.Sprintf("data.%s.%s", namespace, rule) - if ignored, err := s.isIgnored(ctx, namespace, rule, inputs); err != nil { + + if ignored, err := s.isIgnored(ctx, namespace, rule, parsed); err != nil { return nil, err } else if ignored { for _, input := range inputs { @@ -402,7 +422,8 @@ func (s *Scanner) applyRuleCombined(ctx context.Context, namespace, rule string, } return results, nil } - set, traces, err := s.runQuery(ctx, qualified, inputs, false) + qualified := fmt.Sprintf("data.%s.%s", namespace, rule) + set, traces, err := s.runQuery(ctx, qualified, parsed, false) if err != nil { return nil, err } From 39ebed45f8c218509d264bd3f3ca548fc33d2b3a Mon Sep 17 00:00:00 2001 From: chenk Date: Tue, 7 May 2024 15:20:07 +0300 Subject: [PATCH 050/352] fix: use of specified context to obtain cluster name (#6645) Signed-off-by: chenk --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index ebce0b46f297..ea55d6a7e25a 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/aquasecurity/trivy-checks v0.10.5-0.20240430045208-6cc735de6b9e github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 - github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb + github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240507080745-f6c5fb0a3f3f github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2/config v1.27.11 github.com/aws/aws-sdk-go-v2/credentials v1.17.11 @@ -177,7 +177,7 @@ require ( github.com/antchfx/xpath v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.51.16 // indirect + github.com/aws/aws-sdk-go v1.51.25 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect diff --git a/go.sum b/go.sum index 0b1d4019fd85..b75ba80f6db3 100644 --- a/go.sum +++ b/go.sum @@ -781,8 +781,8 @@ github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTU github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb h1:U07awOdXGT8NMwTPVuXkL/cKZyvO4PuG+VX1oIvsuiQ= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240425111126-a549f8de71bb/go.mod h1:+NJBTgQErUmq21Ag71q/EuXZKIP+/OJvBAR0G+YUkKo= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240507080745-f6c5fb0a3f3f h1:IJkhrSrlpemDZ+tPLKlJeuuK64yFcLqpTdQa4v173zA= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240507080745-f6c5fb0a3f3f/go.mod h1:5uAM0CbAlVBTWc4yKCDHtl7zCwZMMYfL7erBnP3gwkI= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -796,8 +796,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.51.16 h1:vnWKK8KjbftEkuPX8bRj3WHsLy1uhotn0eXptpvrxJI= -github.com/aws/aws-sdk-go v1.51.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.51.25 h1:DjTT8mtmsachhV6yrXR8+yhnG6120dazr720nopRsls= +github.com/aws/aws-sdk-go v1.51.25/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= From 3eecfc6b6e1d6cef1497f7ff0044e676da159243 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Tue, 7 May 2024 16:25:52 +0400 Subject: [PATCH 051/352] refactor: unify Library and Package structs (#6633) Signed-off-by: knqyf263 Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Co-authored-by: DmitriyLewen --- integration/testdata/conan.json.golden | 48 +- integration/testdata/poetry.json.golden | 22 +- pkg/dependency/parser/c/conan/parse.go | 49 +- pkg/dependency/parser/c/conan/parse_test.go | 75 +- .../parser/conda/environment/parse.go | 26 +- .../parser/conda/environment/parse_test.go | 36 +- pkg/dependency/parser/conda/meta/parse.go | 15 +- .../parser/conda/meta/parse_test.go | 19 +- pkg/dependency/parser/dart/pub/parse.go | 21 +- pkg/dependency/parser/dart/pub/parse_test.go | 18 +- .../parser/dotnet/core_deps/parse.go | 14 +- .../parser/dotnet/core_deps/parse_test.go | 26 +- .../parser/frameworks/wordpress/parse.go | 8 +- .../parser/frameworks/wordpress/parse_test.go | 6 +- pkg/dependency/parser/golang/binary/parse.go | 20 +- .../parser/golang/binary/parse_test.go | 36 +- pkg/dependency/parser/golang/mod/parse.go | 39 +- .../parser/golang/mod/parse_test.go | 12 +- .../parser/golang/mod/parse_testcase.go | 184 ++--- pkg/dependency/parser/golang/sum/parse.go | 17 +- .../parser/golang/sum/parse_test.go | 12 +- .../parser/golang/sum/parse_testcase.go | 10 +- .../parser/gradle/lockfile/parse.go | 15 +- .../parser/gradle/lockfile/parse_test.go | 29 +- pkg/dependency/parser/hex/mix/parse.go | 13 +- pkg/dependency/parser/hex/mix/parse_test.go | 75 +- pkg/dependency/parser/java/jar/parse.go | 56 +- pkg/dependency/parser/java/jar/parse_test.go | 28 +- pkg/dependency/parser/java/jar/types.go | 6 +- pkg/dependency/parser/java/pom/artifact.go | 12 +- pkg/dependency/parser/java/pom/parse.go | 79 +- pkg/dependency/parser/java/pom/parse_test.go | 515 ++++++------- pkg/dependency/parser/java/pom/pom.go | 13 +- pkg/dependency/parser/julia/manifest/parse.go | 24 +- .../parser/julia/manifest/parse_test.go | 22 +- .../parser/julia/manifest/parse_testcase.go | 62 +- pkg/dependency/parser/nodejs/npm/parse.go | 123 ++- .../parser/nodejs/npm/parse_test.go | 16 +- .../parser/nodejs/npm/parse_testcase.go | 622 +++++++-------- .../parser/nodejs/packagejson/parse.go | 25 +- .../parser/nodejs/packagejson/parse_test.go | 33 +- pkg/dependency/parser/nodejs/pnpm/parse.go | 23 +- .../parser/nodejs/pnpm/parse_test.go | 43 +- .../parser/nodejs/pnpm/parse_testcase.go | 188 ++--- pkg/dependency/parser/nodejs/yarn/parse.go | 33 +- .../parser/nodejs/yarn/parse_test.go | 44 +- .../parser/nodejs/yarn/parse_testcase.go | 104 +-- pkg/dependency/parser/nuget/config/parse.go | 16 +- .../parser/nuget/config/parse_test.go | 8 +- pkg/dependency/parser/nuget/lock/parse.go | 21 +- .../parser/nuget/lock/parse_test.go | 25 +- .../parser/nuget/lock/parse_testcase.go | 226 +++--- .../parser/nuget/packagesprops/parse.go | 31 +- .../parser/nuget/packagesprops/parse_test.go | 14 +- pkg/dependency/parser/php/composer/parse.go | 59 +- .../parser/php/composer/parse_test.go | 98 +-- .../parser/python/packaging/parse.go | 15 +- .../parser/python/packaging/parse_test.go | 88 ++- pkg/dependency/parser/python/pip/parse.go | 12 +- .../parser/python/pip/parse_test.go | 4 +- .../parser/python/pip/parse_testcase.go | 20 +- pkg/dependency/parser/python/pipenv/parse.go | 22 +- .../parser/python/pipenv/parse_test.go | 22 +- .../parser/python/pipenv/parse_testcase.go | 88 +-- pkg/dependency/parser/python/poetry/parse.go | 39 +- .../parser/python/poetry/parse_test.go | 32 +- .../parser/python/poetry/parse_testcase.go | 12 +- pkg/dependency/parser/ruby/bundler/parse.go | 37 +- .../parser/ruby/bundler/parse_test.go | 224 +++--- pkg/dependency/parser/ruby/gemspec/parse.go | 15 +- .../parser/ruby/gemspec/parse_test.go | 54 +- pkg/dependency/parser/rust/binary/parse.go | 17 +- .../parser/rust/binary/parse_test.go | 20 +- pkg/dependency/parser/rust/cargo/parse.go | 37 +- .../parser/rust/cargo/parse_test.go | 375 +++++++-- .../parser/swift/cocoapods/parse.go | 35 +- .../parser/swift/cocoapods/parse_test.go | 24 +- pkg/dependency/parser/swift/swift/parse.go | 19 +- .../parser/swift/swift/parse_test.go | 28 +- pkg/dependency/parser/utils/utils.go | 30 +- pkg/dependency/parser/utils/utils_test.go | 28 +- pkg/dependency/types/types.go | 127 --- pkg/fanal/analyzer/analyzer.go | 8 +- pkg/fanal/analyzer/analyzer_test.go | 16 +- pkg/fanal/analyzer/language/analyze.go | 75 +- pkg/fanal/analyzer/language/analyze_test.go | 7 +- pkg/fanal/analyzer/language/c/conan/conan.go | 9 +- .../analyzer/language/c/conan/conan_test.go | 4 +- .../conda/environment/environment_test.go | 2 +- .../analyzer/language/conda/meta/meta_test.go | 2 +- .../analyzer/language/dart/pub/pubspec.go | 13 +- .../language/dart/pub/pubspec_test.go | 32 +- .../language/dotnet/deps/deps_test.go | 2 +- .../analyzer/language/dotnet/nuget/nuget.go | 16 +- .../language/dotnet/nuget/nuget_test.go | 8 +- .../packagesprops/packagesprops_test.go | 4 +- .../analyzer/language/elixir/mix/mix_test.go | 2 +- .../language/golang/binary/binary_test.go | 2 +- pkg/fanal/analyzer/language/golang/mod/mod.go | 43 +- .../analyzer/language/golang/mod/mod_test.go | 62 +- .../analyzer/language/java/gradle/lockfile.go | 15 +- .../language/java/gradle/lockfile_test.go | 4 +- .../analyzer/language/java/jar/jar_test.go | 6 +- pkg/fanal/analyzer/language/java/pom/pom.go | 4 +- .../analyzer/language/java/pom/pom_test.go | 8 +- .../language/nodejs/license/license.go | 16 +- .../language/nodejs/license/license_test.go | 14 +- pkg/fanal/analyzer/language/nodejs/npm/npm.go | 17 +- .../analyzer/language/nodejs/npm/npm_test.go | 60 +- pkg/fanal/analyzer/language/nodejs/pkg/pkg.go | 7 +- .../analyzer/language/nodejs/pkg/pkg_test.go | 4 +- .../language/nodejs/pnpm/pnpm_test.go | 2 +- .../analyzer/language/nodejs/yarn/yarn.go | 33 +- .../language/nodejs/yarn/yarn_test.go | 326 ++++---- .../language/php/composer/composer.go | 15 +- .../language/php/composer/composer_test.go | 6 +- .../language/python/packaging/packaging.go | 9 +- .../python/packaging/packaging_test.go | 23 +- .../analyzer/language/python/pip/pip_test.go | 2 +- .../analyzer/language/python/poetry/poetry.go | 13 +- .../language/python/poetry/poetry_test.go | 6 +- .../language/ruby/gemspec/gemspec_test.go | 4 +- .../language/rust/binary/binary_test.go | 2 +- .../analyzer/language/rust/cargo/cargo.go | 15 +- .../language/rust/cargo/cargo_test.go | 154 ++-- .../swift/cocoapods/cocoapods_test.go | 4 +- .../language/swift/swift/swift_test.go | 2 +- pkg/fanal/analyzer/sbom/sbom.go | 6 +- pkg/fanal/analyzer/sbom/sbom_test.go | 8 +- pkg/fanal/applier/applier_test.go | 14 +- pkg/fanal/applier/docker.go | 20 +- pkg/fanal/applier/docker_test.go | 44 +- pkg/fanal/artifact/image/image_test.go | 218 +++--- pkg/fanal/artifact/image/remote_sbom_test.go | 8 +- pkg/fanal/artifact/local/fs_test.go | 16 +- pkg/fanal/artifact/sbom/sbom_test.go | 28 +- pkg/fanal/cache/fs_test.go | 4 +- pkg/fanal/handler/sysfile/filter.go | 8 +- pkg/fanal/handler/sysfile/filter_test.go | 22 +- .../handler/unpackaged/unpackaged_test.go | 2 +- pkg/fanal/test/integration/library_test.go | 10 +- .../vuln-image1.2.3.expectedlibs.golden | 272 +++---- pkg/fanal/types/artifact.go | 120 ++- pkg/k8s/scanner/scanner.go | 8 +- pkg/licensing/normalize.go | 4 + pkg/rpc/convert.go | 12 +- pkg/rpc/server/server_test.go | 4 +- pkg/sbom/cyclonedx/unmarshal_test.go | 26 +- pkg/sbom/io/decode.go | 6 +- pkg/sbom/spdx/unmarshal_test.go | 16 +- pkg/scanner/langpkg/scan.go | 10 +- pkg/scanner/local/scan.go | 4 +- pkg/scanner/local/scan_test.go | 28 +- pkg/x/slices/slices.go | 8 + rpc/common/service.pb.go | 720 +++++++++--------- rpc/common/service.proto | 2 +- 156 files changed, 3895 insertions(+), 3669 deletions(-) delete mode 100644 pkg/dependency/types/types.go create mode 100644 pkg/x/slices/slices.go diff --git a/integration/testdata/conan.json.golden b/integration/testdata/conan.json.golden index 8cee1572ba19..1aac990b6304 100644 --- a/integration/testdata/conan.json.golden +++ b/integration/testdata/conan.json.golden @@ -21,6 +21,30 @@ "Class": "lang-pkgs", "Type": "conan", "Packages": [ + { + "ID": "poco/1.9.4", + "Name": "poco", + "Identifier": { + "PURL": "pkg:conan/poco@1.9.4", + "UID": "312753cebe80c0eb" + }, + "Version": "1.9.4", + "Relationship": "direct", + "DependsOn": [ + "pcre/8.43", + "zlib/1.2.12", + "expat/2.4.8", + "sqlite3/3.39.2", + "openssl/1.1.1q" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 12, + "EndLine": 25 + } + ] + }, { "ID": "bzip2/1.0.8", "Name": "bzip2", @@ -97,30 +121,6 @@ } ] }, - { - "ID": "poco/1.9.4", - "Name": "poco", - "Identifier": { - "PURL": "pkg:conan/poco@1.9.4", - "UID": "312753cebe80c0eb" - }, - "Version": "1.9.4", - "Relationship": "direct", - "DependsOn": [ - "pcre/8.43", - "zlib/1.2.12", - "expat/2.4.8", - "sqlite3/3.39.2", - "openssl/1.1.1q" - ], - "Layer": {}, - "Locations": [ - { - "StartLine": 12, - "EndLine": 25 - } - ] - }, { "ID": "sqlite3/3.39.2", "Name": "sqlite3", diff --git a/integration/testdata/poetry.json.golden b/integration/testdata/poetry.json.golden index f1ddf6802143..384c982876d3 100644 --- a/integration/testdata/poetry.json.golden +++ b/integration/testdata/poetry.json.golden @@ -35,6 +35,17 @@ ], "Layer": {} }, + { + "ID": "werkzeug@0.14", + "Name": "werkzeug", + "Identifier": { + "PURL": "pkg:pypi/werkzeug@0.14", + "UID": "4176be111ad01070" + }, + "Version": "0.14", + "Relationship": "direct", + "Layer": {} + }, { "ID": "colorama@0.4.6", "Name": "colorama", @@ -46,17 +57,6 @@ "Indirect": true, "Relationship": "indirect", "Layer": {} - }, - { - "ID": "werkzeug@0.14", - "Name": "werkzeug", - "Identifier": { - "PURL": "pkg:pypi/werkzeug@0.14", - "UID": "4176be111ad01070" - }, - "Version": "0.14", - "Relationship": "direct", - "Layer": {} } ], "Vulnerabilities": [ diff --git a/pkg/dependency/parser/c/conan/parse.go b/pkg/dependency/parser/c/conan/parse.go index 06c583b47282..4528da67b778 100644 --- a/pkg/dependency/parser/c/conan/parse.go +++ b/pkg/dependency/parser/c/conan/parse.go @@ -10,7 +10,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -44,27 +43,27 @@ type Parser struct { logger *log.Logger } -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("conan"), } } -func (p *Parser) parseV1(lock LockFile) ([]types.Library, []types.Dependency, error) { - var libs []types.Library - var deps []types.Dependency +func (p *Parser) parseV1(lock LockFile) ([]ftypes.Package, []ftypes.Dependency, error) { + var pkgs []ftypes.Package + var deps []ftypes.Dependency var directDeps []string if root, ok := lock.GraphLock.Nodes["0"]; ok { directDeps = root.Requires } // Parse packages - parsed := make(map[string]types.Library) + parsed := make(map[string]ftypes.Package) for i, node := range lock.GraphLock.Nodes { if node.Ref == "" { continue } - lib, err := toLibrary(node.Ref, node.StartLine, node.EndLine) + pkg, err := toPackage(node.Ref, node.StartLine, node.EndLine) if err != nil { p.logger.Debug("Parse ref error", log.Err(err)) continue @@ -72,14 +71,14 @@ func (p *Parser) parseV1(lock LockFile) ([]types.Library, []types.Dependency, er // Determine if the package is a direct dependency or not direct := slices.Contains(directDeps, i) - lib.Relationship = lo.Ternary(direct, types.RelationshipDirect, types.RelationshipIndirect) + pkg.Relationship = lo.Ternary(direct, ftypes.RelationshipDirect, ftypes.RelationshipIndirect) - parsed[i] = lib + parsed[i] = pkg } // Parse dependency graph for i, node := range lock.GraphLock.Nodes { - lib, ok := parsed[i] + pkg, ok := parsed[i] if !ok { continue } @@ -91,33 +90,33 @@ func (p *Parser) parseV1(lock LockFile) ([]types.Library, []types.Dependency, er } } if len(childDeps) != 0 { - deps = append(deps, types.Dependency{ - ID: lib.ID, + deps = append(deps, ftypes.Dependency{ + ID: pkg.ID, DependsOn: childDeps, }) } - libs = append(libs, lib) + pkgs = append(pkgs, pkg) } - return libs, deps, nil + return pkgs, deps, nil } -func (p *Parser) parseV2(lock LockFile) ([]types.Library, []types.Dependency, error) { - var libs []types.Library +func (p *Parser) parseV2(lock LockFile) ([]ftypes.Package, []ftypes.Dependency, error) { + var pkgs []ftypes.Package for _, req := range lock.Requires { - lib, err := toLibrary(req.Dependency, req.StartLine, req.EndLine) + pkg, err := toPackage(req.Dependency, req.StartLine, req.EndLine) if err != nil { - p.logger.Debug("Creating library entry from requirement failed", err) + p.logger.Debug("Creating package entry from requirement failed", err) continue } - libs = append(libs, lib) + pkgs = append(pkgs, pkg) } - return libs, []types.Dependency{}, nil + return pkgs, []ftypes.Dependency{}, nil } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var lock LockFile input, err := io.ReadAll(r) @@ -153,16 +152,16 @@ func parsePackage(text string) (string, string, error) { return ss[0], ss[1], nil } -func toLibrary(pkg string, startLine, endLine int) (types.Library, error) { +func toPackage(pkg string, startLine, endLine int) (ftypes.Package, error) { name, version, err := parsePackage(pkg) if err != nil { - return types.Library{}, err + return ftypes.Package{}, err } - return types.Library{ + return ftypes.Package{ ID: dependency.ID(ftypes.Conan, name, version), Name: name, Version: version, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: startLine, EndLine: endLine, diff --git a/pkg/dependency/parser/c/conan/parse_test.go b/pkg/dependency/parser/c/conan/parse_test.go index abbf9f921a18..48abd9f0c0ac 100644 --- a/pkg/dependency/parser/c/conan/parse_test.go +++ b/pkg/dependency/parser/c/conan/parse_test.go @@ -3,65 +3,64 @@ package conan_test import ( "os" "sort" - "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/c/conan" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { tests := []struct { name string inputFile string // Test input file - wantLibs []types.Library - wantDeps []types.Dependency + wantPkgs []ftypes.Package + wantDeps []ftypes.Dependency }{ { name: "happy path", inputFile: "testdata/happy_v1_case1.lock", - wantLibs: []types.Library{ + wantPkgs: []ftypes.Package{ { ID: "pkga/0.0.1", Name: "pkga", Version: "0.0.1", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 13, EndLine: 22, }, }, }, - { - ID: "pkgb/system", - Name: "pkgb", - Version: "system", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 23, - EndLine: 29, - }, - }, - }, { ID: "pkgc/0.1.1", Name: "pkgc", Version: "0.1.1", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 30, EndLine: 35, }, }, }, + { + ID: "pkgb/system", + Name: "pkgb", + Version: "system", + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ + { + StartLine: 23, + EndLine: 29, + }, + }, + }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "pkga/0.0.1", DependsOn: []string{ @@ -73,13 +72,13 @@ func TestParse(t *testing.T) { { name: "happy path. lock file with revisions support", inputFile: "testdata/happy_v1_case2.lock", - wantLibs: []types.Library{ + wantPkgs: []ftypes.Package{ { ID: "openssl/3.0.3", Name: "openssl", Version: "3.0.3", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 12, EndLine: 22, @@ -90,8 +89,8 @@ func TestParse(t *testing.T) { ID: "zlib/1.2.12", Name: "zlib", Version: "1.2.12", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 23, EndLine: 30, @@ -99,7 +98,7 @@ func TestParse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "openssl/3.0.3", DependsOn: []string{ @@ -111,12 +110,12 @@ func TestParse(t *testing.T) { { name: "happy path conan v2", inputFile: "testdata/happy_v2.lock", - wantLibs: []types.Library{ + wantPkgs: []ftypes.Package{ { ID: "matrix/1.3", Name: "matrix", Version: "1.3", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 5, EndLine: 5, @@ -127,7 +126,7 @@ func TestParse(t *testing.T) { ID: "sound32/1.0", Name: "sound32", Version: "1.0", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 4, EndLine: 4, @@ -135,7 +134,7 @@ func TestParse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{}, + wantDeps: []ftypes.Dependency{}, }, { name: "happy path. lock file without dependencies", @@ -153,18 +152,12 @@ func TestParse(t *testing.T) { require.NoError(t, err) defer f.Close() - gotLibs, gotDeps, err := conan.NewParser().Parse(f) + gotPkgs, gotDeps, err := conan.NewParser().Parse(f) require.NoError(t, err) - sort.Slice(gotLibs, func(i, j int) bool { - ret := strings.Compare(gotLibs[i].Name, gotLibs[j].Name) - if ret != 0 { - return ret < 0 - } - return gotLibs[i].Version < gotLibs[j].Version - }) + sort.Sort(ftypes.Packages(gotPkgs)) - assert.Equal(t, tt.wantLibs, gotLibs) + assert.Equal(t, tt.wantPkgs, gotPkgs) assert.Equal(t, tt.wantDeps, gotDeps) }) } diff --git a/pkg/dependency/parser/conda/environment/parse.go b/pkg/dependency/parser/conda/environment/parse.go index 8a4418699f2f..f8bdcfb49a92 100644 --- a/pkg/dependency/parser/conda/environment/parse.go +++ b/pkg/dependency/parser/conda/environment/parse.go @@ -9,7 +9,7 @@ import ( "gopkg.in/yaml.v3" "github.com/aquasecurity/go-version/pkg/version" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -28,44 +28,44 @@ type Parser struct { once sync.Once } -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("conda"), once: sync.Once{}, } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var env environment if err := yaml.NewDecoder(r).Decode(&env); err != nil { return nil, nil, xerrors.Errorf("unable to decode conda environment.yml file: %w", err) } - var libs []types.Library + var pkgs ftypes.Packages for _, dep := range env.Dependencies { - lib := p.toLibrary(dep) - // Skip empty libs - if lib.Name == "" { + pkg := p.toPackage(dep) + // Skip empty pkgs + if pkg.Name == "" { continue } - libs = append(libs, lib) + pkgs = append(pkgs, pkg) } - sort.Sort(types.Libraries(libs)) - return libs, nil, nil + sort.Sort(pkgs) + return pkgs, nil, nil } -func (p *Parser) toLibrary(dep Dependency) types.Library { +func (p *Parser) toPackage(dep Dependency) ftypes.Package { name, ver := p.parseDependency(dep.Value) if ver == "" { p.once.Do(func() { p.logger.Warn("Unable to detect the dependency versions from `environment.yml` as those versions are not pinned. Use `conda env export` to pin versions.") }) } - return types.Library{ + return ftypes.Package{ Name: name, Version: ver, - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: dep.Line, EndLine: dep.Line, diff --git a/pkg/dependency/parser/conda/environment/parse_test.go b/pkg/dependency/parser/conda/environment/parse_test.go index f68736947119..109f53a405bd 100644 --- a/pkg/dependency/parser/conda/environment/parse_test.go +++ b/pkg/dependency/parser/conda/environment/parse_test.go @@ -8,23 +8,23 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/conda/environment" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { tests := []struct { name string input string - want []types.Library + want []ftypes.Package wantErr string }{ { name: "happy path", input: "testdata/happy.yaml", - want: []types.Library{ + want: []ftypes.Package{ { Name: "_openmp_mutex", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 6, EndLine: 6, @@ -34,7 +34,7 @@ func TestParse(t *testing.T) { { Name: "blas", Version: "1.0", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 5, EndLine: 5, @@ -44,7 +44,7 @@ func TestParse(t *testing.T) { { Name: "bzip2", Version: "1.0.8", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 19, EndLine: 19, @@ -54,7 +54,7 @@ func TestParse(t *testing.T) { { Name: "ca-certificates", Version: "2024.2", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 7, EndLine: 7, @@ -63,7 +63,7 @@ func TestParse(t *testing.T) { }, { Name: "ld_impl_linux-aarch64", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 8, EndLine: 8, @@ -72,7 +72,7 @@ func TestParse(t *testing.T) { }, { Name: "libblas", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 9, EndLine: 9, @@ -81,7 +81,7 @@ func TestParse(t *testing.T) { }, { Name: "libcblas", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 10, EndLine: 10, @@ -91,7 +91,7 @@ func TestParse(t *testing.T) { { Name: "libexpat", Version: "2.6.2", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 11, EndLine: 11, @@ -101,7 +101,7 @@ func TestParse(t *testing.T) { { Name: "libffi", Version: "3.4.2", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 12, EndLine: 12, @@ -110,7 +110,7 @@ func TestParse(t *testing.T) { }, { Name: "libgcc-ng", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 13, EndLine: 13, @@ -119,7 +119,7 @@ func TestParse(t *testing.T) { }, { Name: "libgfortran-ng", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 14, EndLine: 14, @@ -128,7 +128,7 @@ func TestParse(t *testing.T) { }, { Name: "libgfortran5", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 15, EndLine: 15, @@ -138,7 +138,7 @@ func TestParse(t *testing.T) { { Name: "libgomp", Version: "13.2.0", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 16, EndLine: 16, @@ -147,7 +147,7 @@ func TestParse(t *testing.T) { }, { Name: "liblapack", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 17, EndLine: 17, @@ -157,7 +157,7 @@ func TestParse(t *testing.T) { { Name: "libnsl", Version: "2.0.1", - Locations: types.Locations{ + Locations: ftypes.Locations{ { StartLine: 18, EndLine: 18, diff --git a/pkg/dependency/parser/conda/meta/parse.go b/pkg/dependency/parser/conda/meta/parse.go index 30d344bfdfa3..08f5623fa052 100644 --- a/pkg/dependency/parser/conda/meta/parse.go +++ b/pkg/dependency/parser/conda/meta/parse.go @@ -3,9 +3,10 @@ package meta import ( "encoding/json" + "github.com/samber/lo" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -17,14 +18,14 @@ type packageJSON struct { type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } // Parse parses Anaconda (a.k.a. conda) environment metadata. // e.g. /envs//conda-meta/.json // For details see https://conda.io/projects/conda/en/latest/user-guide/concepts/environments.html -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var data packageJSON err := json.NewDecoder(r).Decode(&data) if err != nil { @@ -35,11 +36,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return nil, nil, xerrors.Errorf("unable to parse conda package") } - return []types.Library{ + return []ftypes.Package{ { - Name: data.Name, - Version: data.Version, - License: data.License, // can be empty + Name: data.Name, + Version: data.Version, + Licenses: lo.Ternary(data.License != "", []string{data.License}, nil), }, }, nil, nil } diff --git a/pkg/dependency/parser/conda/meta/parse_test.go b/pkg/dependency/parser/conda/meta/parse_test.go index 8bde5e184f0e..b2a4eafd6b08 100644 --- a/pkg/dependency/parser/conda/meta/parse_test.go +++ b/pkg/dependency/parser/conda/meta/parse_test.go @@ -8,25 +8,36 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/conda/meta" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { tests := []struct { name string input string - want []types.Library + want []ftypes.Package wantErr string }{ { name: "_libgcc_mutex", input: "testdata/_libgcc_mutex-0.1-main.json", - want: []types.Library{{Name: "_libgcc_mutex", Version: "0.1"}}, + want: []ftypes.Package{ + { + Name: "_libgcc_mutex", + Version: "0.1", + }, + }, }, { name: "libgomp", input: "testdata/libgomp-11.2.0-h1234567_1.json", - want: []types.Library{{Name: "libgomp", Version: "11.2.0", License: "GPL-3.0-only WITH GCC-exception-3.1"}}, + want: []ftypes.Package{ + { + Name: "libgomp", + Version: "11.2.0", + Licenses: []string{"GPL-3.0-only WITH GCC-exception-3.1"}, + }, + }, }, { name: "invalid_json", diff --git a/pkg/dependency/parser/dart/pub/parse.go b/pkg/dependency/parser/dart/pub/parse.go index 4d38fd686252..10b1e7ab729a 100644 --- a/pkg/dependency/parser/dart/pub/parse.go +++ b/pkg/dependency/parser/dart/pub/parse.go @@ -5,7 +5,6 @@ import ( "gopkg.in/yaml.v3" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -19,7 +18,7 @@ const ( // Parser is a parser for pubspec.lock type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } @@ -32,36 +31,36 @@ type Dep struct { Version string `yaml:"version"` } -func (p Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { l := &lock{} if err := yaml.NewDecoder(r).Decode(&l); err != nil { return nil, nil, xerrors.Errorf("failed to decode pubspec.lock: %w", err) } - var libs []types.Library + var pkgs []ftypes.Package for name, dep := range l.Packages { // We would like to exclude dev dependencies, but we cannot identify // which indirect dependencies were introduced by dev dependencies // as there are 3 dependency types, "direct main", "direct dev" and "transitive". // It will be confusing if we exclude direct dev dependencies and include transitive dev dependencies. // We decided to keep all dev dependencies until Pub will add support for "transitive main" and "transitive dev". - lib := types.Library{ + pkg := ftypes.Package{ ID: dependency.ID(ftypes.Pub, name, dep.Version), Name: name, Version: dep.Version, Relationship: p.relationship(dep.Dependency), } - libs = append(libs, lib) + pkgs = append(pkgs, pkg) } - return libs, nil, nil + return pkgs, nil, nil } -func (p Parser) relationship(dep string) types.Relationship { +func (p Parser) relationship(dep string) ftypes.Relationship { switch dep { case directMain, directDev: - return types.RelationshipDirect + return ftypes.RelationshipDirect case transitiveDep: - return types.RelationshipIndirect + return ftypes.RelationshipIndirect } - return types.RelationshipUnknown + return ftypes.RelationshipUnknown } diff --git a/pkg/dependency/parser/dart/pub/parse_test.go b/pkg/dependency/parser/dart/pub/parse_test.go index 7c28355e5a92..be698a7933c5 100644 --- a/pkg/dependency/parser/dart/pub/parse_test.go +++ b/pkg/dependency/parser/dart/pub/parse_test.go @@ -10,37 +10,37 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/dart/pub" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParser_Parse(t *testing.T) { tests := []struct { name string inputFile string - want []types.Library + want []ftypes.Package wantErr assert.ErrorAssertionFunc }{ { name: "happy path", inputFile: "testdata/happy.lock", - want: []types.Library{ + want: []ftypes.Package{ { ID: "crypto@3.0.2", Name: "crypto", Version: "3.0.2", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "flutter_test@0.0.0", Name: "flutter_test", Version: "0.0.0", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "uuid@3.0.6", Name: "uuid", Version: "3.0.6", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, }, wantErr: assert.NoError, @@ -63,13 +63,13 @@ func TestParser_Parse(t *testing.T) { require.NoError(t, err) defer f.Close() - gotLibs, _, err := pub.NewParser().Parse(f) + gotPkgs, _, err := pub.NewParser().Parse(f) if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.inputFile)) { return } - sort.Sort(types.Libraries(gotLibs)) - assert.Equal(t, tt.want, gotLibs) + sort.Sort(ftypes.Packages(gotPkgs)) + assert.Equal(t, tt.want, gotPkgs) }) } } diff --git a/pkg/dependency/parser/dotnet/core_deps/parse.go b/pkg/dependency/parser/dotnet/core_deps/parse.go index 399c38736779..4314e9af9b3d 100644 --- a/pkg/dependency/parser/dotnet/core_deps/parse.go +++ b/pkg/dependency/parser/dotnet/core_deps/parse.go @@ -7,7 +7,7 @@ import ( "github.com/liamg/jfather" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -16,13 +16,13 @@ type Parser struct { logger *log.Logger } -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("dotnet"), } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var depsFile dotNetDependencies input, err := io.ReadAll(r) @@ -33,7 +33,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return nil, nil, xerrors.Errorf("failed to decode .deps.json file: %w", err) } - var libraries []types.Library + var pkgs []ftypes.Package for nameVer, lib := range depsFile.Libraries { if !strings.EqualFold(lib.Type, "package") { continue @@ -46,10 +46,10 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, continue } - libraries = append(libraries, types.Library{ + pkgs = append(pkgs, ftypes.Package{ Name: split[0], Version: split[1], - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: lib.StartLine, EndLine: lib.EndLine, @@ -58,7 +58,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, }) } - return libraries, nil, nil + return pkgs, nil, nil } type dotNetDependencies struct { diff --git a/pkg/dependency/parser/dotnet/core_deps/parse_test.go b/pkg/dependency/parser/dotnet/core_deps/parse_test.go index 839cf9ed97ba..dfd0a9cd96f7 100644 --- a/pkg/dependency/parser/dotnet/core_deps/parse_test.go +++ b/pkg/dependency/parser/dotnet/core_deps/parse_test.go @@ -4,25 +4,24 @@ import ( "os" "path" "sort" - "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { vectors := []struct { file string // Test input file - want []types.Library + want []ftypes.Package wantErr string }{ { file: "testdata/ExampleApp1.deps.json", - want: []types.Library{ - {Name: "Newtonsoft.Json", Version: "13.0.1", Locations: []types.Location{{StartLine: 33, EndLine: 39}}}, + want: []ftypes.Package{ + {Name: "Newtonsoft.Json", Version: "13.0.1", Locations: []ftypes.Location{{StartLine: 33, EndLine: 39}}}, }, }, { @@ -47,21 +46,8 @@ func TestParse(t *testing.T) { } else { require.NoError(t, err) - sort.Slice(got, func(i, j int) bool { - ret := strings.Compare(got[i].Name, got[j].Name) - if ret == 0 { - return got[i].Version < got[j].Version - } - return ret < 0 - }) - - sort.Slice(tt.want, func(i, j int) bool { - ret := strings.Compare(tt.want[i].Name, tt.want[j].Name) - if ret == 0 { - return tt.want[i].Version < tt.want[j].Version - } - return ret < 0 - }) + sort.Sort(ftypes.Packages(got)) + sort.Sort(ftypes.Packages(tt.want)) assert.Equal(t, tt.want, got) } diff --git a/pkg/dependency/parser/frameworks/wordpress/parse.go b/pkg/dependency/parser/frameworks/wordpress/parse.go index 61e00ded81cc..561dfde403d1 100644 --- a/pkg/dependency/parser/frameworks/wordpress/parse.go +++ b/pkg/dependency/parser/frameworks/wordpress/parse.go @@ -7,10 +7,10 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) -func Parse(r io.Reader) (lib types.Library, err error) { +func Parse(r io.Reader) (lib ftypes.Package, err error) { // If wordpress file, open file and // find line with content @@ -68,10 +68,10 @@ func Parse(r io.Reader) (lib types.Library, err error) { } if err = scanner.Err(); err != nil || version == "" { - return types.Library{}, xerrors.New("version.php could not be parsed") + return ftypes.Package{}, xerrors.New("version.php could not be parsed") } - return types.Library{ + return ftypes.Package{ Name: "wordpress", Version: version, }, nil diff --git a/pkg/dependency/parser/frameworks/wordpress/parse_test.go b/pkg/dependency/parser/frameworks/wordpress/parse_test.go index 623ae06b87c7..b717d9cbc50c 100644 --- a/pkg/dependency/parser/frameworks/wordpress/parse_test.go +++ b/pkg/dependency/parser/frameworks/wordpress/parse_test.go @@ -8,18 +8,18 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParseWordPress(t *testing.T) { tests := []struct { file string // Test input file - want types.Library + want ftypes.Package wantErr string }{ { file: "testdata/version.php", - want: types.Library{ + want: ftypes.Package{ Name: "wordpress", Version: "4.9.4-alpha", }, diff --git a/pkg/dependency/parser/golang/binary/parse.go b/pkg/dependency/parser/golang/binary/parse.go index ae7d4d81adae..171d3574800e 100644 --- a/pkg/dependency/parser/golang/binary/parse.go +++ b/pkg/dependency/parser/golang/binary/parse.go @@ -11,7 +11,7 @@ import ( "golang.org/x/mod/semver" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -39,22 +39,22 @@ type Parser struct { logger *log.Logger } -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("gobinary"), } } // Parse scans file to try to report the Go and module versions. -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { info, err := buildinfo.Read(r) if err != nil { return nil, nil, convertError(err) } ldflags := p.ldFlags(info.Settings) - libs := make([]types.Library, 0, len(info.Deps)+2) - libs = append(libs, []types.Library{ + pkgs := make(ftypes.Packages, 0, len(info.Deps)+2) + pkgs = append(pkgs, []ftypes.Package{ { // Add main module Name: info.Main.Path, @@ -64,13 +64,13 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // as a secondary source. // See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477. Version: cmp.Or(p.checkVersion(info.Main.Path, info.Main.Version), p.ParseLDFlags(info.Main.Path, ldflags)), - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { // Add the Go version used to build this binary. Name: "stdlib", Version: strings.TrimPrefix(info.GoVersion, "go"), - Relationship: types.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages. + Relationship: ftypes.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages. }, }...) @@ -87,14 +87,14 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, mod = dep.Replace } - libs = append(libs, types.Library{ + pkgs = append(pkgs, ftypes.Package{ Name: mod.Path, Version: p.checkVersion(mod.Path, mod.Version), }) } - sort.Sort(types.Libraries(libs)) - return libs, nil, nil + sort.Sort(pkgs) + return pkgs, nil, nil } // checkVersion detects `(devel)` versions, removes them and adds a debug message about it. diff --git a/pkg/dependency/parser/golang/binary/parse_test.go b/pkg/dependency/parser/golang/binary/parse_test.go index 96dc7213311c..e3144064ffe3 100644 --- a/pkg/dependency/parser/golang/binary/parse_test.go +++ b/pkg/dependency/parser/golang/binary/parse_test.go @@ -8,20 +8,20 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/binary" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { - wantLibs := []types.Library{ + wantPkgs := []ftypes.Package{ { Name: "github.com/aquasecurity/test", Version: "", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { Name: "stdlib", Version: "1.15.2", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { Name: "github.com/aquasecurity/go-pep440-version", @@ -40,37 +40,37 @@ func TestParse(t *testing.T) { tests := []struct { name string inputFile string - want []types.Library + want []ftypes.Package wantErr string }{ { name: "ELF", inputFile: "testdata/test.elf", - want: wantLibs, + want: wantPkgs, }, { name: "PE", inputFile: "testdata/test.exe", - want: wantLibs, + want: wantPkgs, }, { name: "Mach-O", inputFile: "testdata/test.macho", - want: wantLibs, + want: wantPkgs, }, { name: "with replace directive", inputFile: "testdata/replace.elf", - want: []types.Library{ + want: []ftypes.Package{ { Name: "github.com/ebati/trivy-mod-parse", Version: "", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { Name: "stdlib", Version: "1.16.4", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { Name: "github.com/davecgh/go-spew", @@ -85,32 +85,32 @@ func TestParse(t *testing.T) { { name: "with semver main module version", inputFile: "testdata/semver-main-module-version.macho", - want: []types.Library{ + want: []ftypes.Package{ { Name: "go.etcd.io/bbolt", Version: "v1.3.5", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { Name: "stdlib", Version: "1.20.6", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, }, }, { name: "with -ldflags=\"-X main.version=v1.0.0\"", inputFile: "testdata/main-version-via-ldflags.elf", - want: []types.Library{ + want: []ftypes.Package{ { Name: "github.com/aquasecurity/test", Version: "v1.0.0", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { Name: "stdlib", Version: "1.22.1", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, }, }, @@ -238,7 +238,7 @@ func TestParser_ParseLDFlags(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := binary.NewParser().(*binary.Parser) + p := binary.NewParser() assert.Equal(t, tt.want, p.ParseLDFlags(tt.args.name, tt.args.flags)) }) } diff --git a/pkg/dependency/parser/golang/mod/parse.go b/pkg/dependency/parser/golang/mod/parse.go index 3be3bb0c2800..fa5116f19bfa 100644 --- a/pkg/dependency/parser/golang/mod/parse.go +++ b/pkg/dependency/parser/golang/mod/parse.go @@ -12,7 +12,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -34,17 +33,17 @@ type Parser struct { replace bool // 'replace' represents if the 'replace' directive should be taken into account. } -func NewParser(replace bool) types.Parser { +func NewParser(replace bool) *Parser { return &Parser{ replace: replace, } } -func (p *Parser) GetExternalRefs(path string) []types.ExternalRef { +func (p *Parser) GetExternalRefs(path string) []ftypes.ExternalRef { if url := resolveVCSUrl(path); url != "" { - return []types.ExternalRef{ + return []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: url, }, } @@ -67,8 +66,8 @@ func resolveVCSUrl(modulePath string) string { } // Parse parses a go.mod file -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { - libs := make(map[string]types.Library) +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { + pkgs := make(map[string]ftypes.Package) goModData, err := io.ReadAll(r) if err != nil { @@ -88,12 +87,12 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // Main module if m := modFileParsed.Module; m != nil { ver := strings.TrimPrefix(m.Mod.Version, "v") - libs[m.Mod.Path] = types.Library{ + pkgs[m.Mod.Path] = ftypes.Package{ ID: packageID(m.Mod.Path, ver), Name: m.Mod.Path, Version: ver, ExternalReferences: p.GetExternalRefs(m.Mod.Path), - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, } } @@ -104,11 +103,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, continue } ver := strings.TrimPrefix(require.Mod.Version, "v") - libs[require.Mod.Path] = types.Library{ + pkgs[require.Mod.Path] = ftypes.Package{ ID: packageID(require.Mod.Path, ver), Name: require.Mod.Path, Version: ver, - Relationship: lo.Ternary(require.Indirect, types.RelationshipIndirect, types.RelationshipDirect), + Relationship: lo.Ternary(require.Indirect, ftypes.RelationshipIndirect, ftypes.RelationshipDirect), ExternalReferences: p.GetExternalRefs(require.Mod.Path), } } @@ -116,8 +115,8 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // No need to evaluate the 'replace' directive for indirect dependencies if p.replace { for _, rep := range modFileParsed.Replace { - // Check if replaced path is actually in our libs. - old, ok := libs[rep.Old.Path] + // Check if replaced path is actually in our pkgs. + old, ok := pkgs[rep.Old.Path] if !ok { continue } @@ -130,16 +129,16 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // Only support replace directive with version on the right side. // Directive without version is a local path. if rep.New.Version == "" { - // Delete old lib, since it's a local path now. - delete(libs, rep.Old.Path) + // Delete old pkg, since it's a local path now. + delete(pkgs, rep.Old.Path) continue } - // Delete old lib, in case the path has changed. - delete(libs, rep.Old.Path) + // Delete old pkg, in case the path has changed. + delete(pkgs, rep.Old.Path) - // Add replaced library to library register. - libs[rep.New.Path] = types.Library{ + // Add replaced package to package register. + pkgs[rep.New.Path] = ftypes.Package{ ID: packageID(rep.New.Path, rep.New.Version[1:]), Name: rep.New.Path, Version: rep.New.Version[1:], @@ -149,7 +148,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, } } - return maps.Values(libs), nil, nil + return maps.Values(pkgs), nil, nil } // Check if the Go version is less than 1.17 diff --git a/pkg/dependency/parser/golang/mod/parse_test.go b/pkg/dependency/parser/golang/mod/parse_test.go index 6372785df058..598cb3fe70f3 100644 --- a/pkg/dependency/parser/golang/mod/parse_test.go +++ b/pkg/dependency/parser/golang/mod/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { @@ -16,7 +16,7 @@ func TestParse(t *testing.T) { name string file string replace bool - want []types.Library + want []ftypes.Package }{ { name: "normal", @@ -88,12 +88,8 @@ func TestParse(t *testing.T) { got, _, err := NewParser(tt.replace).Parse(f) require.NoError(t, err) - sort.Slice(got, func(i, j int) bool { - return got[i].Name < got[j].Name - }) - sort.Slice(tt.want, func(i, j int) bool { - return tt.want[i].Name < tt.want[j].Name - }) + sort.Sort(ftypes.Packages(got)) + sort.Sort(ftypes.Packages(tt.want)) assert.Equal(t, tt.want, got) }) diff --git a/pkg/dependency/parser/golang/mod/parse_testcase.go b/pkg/dependency/parser/golang/mod/parse_testcase.go index dfadc32f67c3..5a07b939c549 100644 --- a/pkg/dependency/parser/golang/mod/parse_testcase.go +++ b/pkg/dependency/parser/golang/mod/parse_testcase.go @@ -1,17 +1,17 @@ package mod -import "github.com/aquasecurity/trivy/pkg/dependency/types" +import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( // execute go mod tidy in normal folder - GoModNormal = []types.Library{ + GoModNormal = []ftypes.Package{ { ID: "github.com/org/repo", Name: "github.com/org/repo", - Relationship: types.RelationshipRoot, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipRoot, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/org/repo", }, }, @@ -20,10 +20,10 @@ var ( ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", Version: "0.0.0-20211224170007-df43bca6b6ff", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/aquasecurity/go-dep-parser", }, }, @@ -32,16 +32,16 @@ var ( ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", Name: "golang.org/x/xerrors", Version: "0.0.0-20200804184101-5ec99f83aff1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", Name: "gopkg.in/yaml.v3", Version: "3.0.0-20210107192922-496545a6307b", - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/go-yaml/yaml", }, }, @@ -49,14 +49,14 @@ var ( } // execute go mod tidy in replaced folder - GoModReplaced = []types.Library{ + GoModReplaced = []ftypes.Package{ { ID: "github.com/org/repo", Name: "github.com/org/repo", - Relationship: types.RelationshipRoot, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipRoot, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/org/repo", }, }, @@ -65,10 +65,10 @@ var ( ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", Name: "github.com/aquasecurity/go-dep-parser", Version: "0.0.0-20220406074731-71021a481237", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/aquasecurity/go-dep-parser", }, }, @@ -77,19 +77,19 @@ var ( ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", Name: "golang.org/x/xerrors", Version: "0.0.0-20200804184101-5ec99f83aff1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, } // execute go mod tidy in replaced folder - GoModUnreplaced = []types.Library{ + GoModUnreplaced = []ftypes.Package{ { ID: "github.com/org/repo", Name: "github.com/org/repo", - Relationship: types.RelationshipRoot, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipRoot, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/org/repo", }, }, @@ -98,10 +98,10 @@ var ( ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211110174639-8257534ffed3", Name: "github.com/aquasecurity/go-dep-parser", Version: "0.0.0-20211110174639-8257534ffed3", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/aquasecurity/go-dep-parser", }, }, @@ -110,19 +110,19 @@ var ( ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", Name: "golang.org/x/xerrors", Version: "0.0.0-20200804184101-5ec99f83aff1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, } // execute go mod tidy in replaced-with-version folder - GoModReplacedWithVersion = []types.Library{ + GoModReplacedWithVersion = []ftypes.Package{ { ID: "github.com/org/repo", Name: "github.com/org/repo", - Relationship: types.RelationshipRoot, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipRoot, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/org/repo", }, }, @@ -131,10 +131,10 @@ var ( ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", Name: "github.com/aquasecurity/go-dep-parser", Version: "0.0.0-20220406074731-71021a481237", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/aquasecurity/go-dep-parser", }, }, @@ -143,19 +143,19 @@ var ( ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", Name: "golang.org/x/xerrors", Version: "0.0.0-20200804184101-5ec99f83aff1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, } // execute go mod tidy in replaced-with-version-mismatch folder - GoModReplacedWithVersionMismatch = []types.Library{ + GoModReplacedWithVersionMismatch = []ftypes.Package{ { ID: "github.com/org/repo", Name: "github.com/org/repo", - Relationship: types.RelationshipRoot, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipRoot, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/org/repo", }, }, @@ -164,10 +164,10 @@ var ( ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", Version: "0.0.0-20211224170007-df43bca6b6ff", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/aquasecurity/go-dep-parser", }, }, @@ -176,16 +176,16 @@ var ( ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", Name: "golang.org/x/xerrors", Version: "0.0.0-20200804184101-5ec99f83aff1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", Name: "gopkg.in/yaml.v3", Version: "3.0.0-20210107192922-496545a6307b", - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/go-yaml/yaml", }, }, @@ -193,14 +193,14 @@ var ( } // execute go mod tidy in replaced-with-local-path folder - GoModReplacedWithLocalPath = []types.Library{ + GoModReplacedWithLocalPath = []ftypes.Package{ { ID: "github.com/org/repo", Name: "github.com/org/repo", - Relationship: types.RelationshipRoot, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipRoot, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/org/repo", }, }, @@ -209,10 +209,10 @@ var ( ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", Version: "0.0.0-20211224170007-df43bca6b6ff", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/aquasecurity/go-dep-parser", }, }, @@ -221,10 +221,10 @@ var ( ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", Name: "gopkg.in/yaml.v3", Version: "3.0.0-20210107192922-496545a6307b", - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/go-yaml/yaml", }, }, @@ -232,14 +232,14 @@ var ( } // execute go mod tidy in replaced-with-local-path-and-version folder - GoModReplacedWithLocalPathAndVersion = []types.Library{ + GoModReplacedWithLocalPathAndVersion = []ftypes.Package{ { ID: "github.com/org/repo", Name: "github.com/org/repo", - Relationship: types.RelationshipRoot, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipRoot, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/org/repo", }, }, @@ -248,10 +248,10 @@ var ( ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", Version: "0.0.0-20211224170007-df43bca6b6ff", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/aquasecurity/go-dep-parser", }, }, @@ -260,10 +260,10 @@ var ( ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", Name: "gopkg.in/yaml.v3", Version: "3.0.0-20210107192922-496545a6307b", - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/go-yaml/yaml", }, }, @@ -271,14 +271,14 @@ var ( } // execute go mod tidy in replaced-with-local-path-and-version-mismatch folder - GoModReplacedWithLocalPathAndVersionMismatch = []types.Library{ + GoModReplacedWithLocalPathAndVersionMismatch = []ftypes.Package{ { ID: "github.com/org/repo", Name: "github.com/org/repo", - Relationship: types.RelationshipRoot, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipRoot, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/org/repo", }, }, @@ -287,10 +287,10 @@ var ( ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", Version: "0.0.0-20211224170007-df43bca6b6ff", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/aquasecurity/go-dep-parser", }, }, @@ -299,16 +299,16 @@ var ( ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", Name: "golang.org/x/xerrors", Version: "0.0.0-20200804184101-5ec99f83aff1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", Name: "gopkg.in/yaml.v3", Version: "3.0.0-20210107192922-496545a6307b", - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/go-yaml/yaml", }, }, @@ -316,14 +316,14 @@ var ( } // execute go mod tidy in go116 folder - GoMod116 = []types.Library{ + GoMod116 = []ftypes.Package{ { ID: "github.com/org/repo", Name: "github.com/org/repo", - Relationship: types.RelationshipRoot, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipRoot, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/org/repo", }, }, @@ -332,10 +332,10 @@ var ( ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", Version: "0.0.0-20211224170007-df43bca6b6ff", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/aquasecurity/go-dep-parser", }, }, @@ -343,14 +343,14 @@ var ( } // execute go mod tidy in no-go-version folder - GoModNoGoVersion = []types.Library{ + GoModNoGoVersion = []ftypes.Package{ { ID: "github.com/org/repo", Name: "github.com/org/repo", - Relationship: types.RelationshipRoot, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipRoot, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/org/repo", }, }, @@ -359,10 +359,10 @@ var ( ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", Name: "github.com/aquasecurity/go-dep-parser", Version: "0.0.0-20211224170007-df43bca6b6ff", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefVCS, + Type: ftypes.RefVCS, URL: "https://github.com/aquasecurity/go-dep-parser", }, }, diff --git a/pkg/dependency/parser/golang/sum/parse.go b/pkg/dependency/parser/golang/sum/parse.go index e06b474c0a97..4ec742b1bae2 100644 --- a/pkg/dependency/parser/golang/sum/parse.go +++ b/pkg/dependency/parser/golang/sum/parse.go @@ -7,21 +7,20 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } // Parse parses a go.sum file -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { - var libs []types.Library - uniqueLibs := make(map[string]string) +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { + var pkgs []ftypes.Package + uniquePkgs := make(map[string]string) scanner := bufio.NewScanner(r) for scanner.Scan() { @@ -33,19 +32,19 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // go.sum records and sorts all non-major versions // with the latest version as last entry - uniqueLibs[s[0]] = strings.TrimSuffix(strings.TrimPrefix(s[1], "v"), "/go.mod") + uniquePkgs[s[0]] = strings.TrimSuffix(strings.TrimPrefix(s[1], "v"), "/go.mod") } if err := scanner.Err(); err != nil { return nil, nil, xerrors.Errorf("scan error: %w", err) } - for k, v := range uniqueLibs { - libs = append(libs, types.Library{ + for k, v := range uniquePkgs { + pkgs = append(pkgs, ftypes.Package{ ID: dependency.ID(ftypes.GoModule, k, v), Name: k, Version: v, }) } - return libs, nil, nil + return pkgs, nil, nil } diff --git a/pkg/dependency/parser/golang/sum/parse_test.go b/pkg/dependency/parser/golang/sum/parse_test.go index 3888743cdf85..90578d0a8a62 100644 --- a/pkg/dependency/parser/golang/sum/parse_test.go +++ b/pkg/dependency/parser/golang/sum/parse_test.go @@ -9,13 +9,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { vectors := []struct { file string - want []types.Library + want []ftypes.Package }{ { file: "testdata/gomod_normal.sum", @@ -48,12 +48,8 @@ func TestParse(t *testing.T) { got[i].ID = "" // Not compare IDs, tested in mod.TestModuleID() } - sort.Slice(got, func(i, j int) bool { - return got[i].Name < got[j].Name - }) - sort.Slice(v.want, func(i, j int) bool { - return v.want[i].Name < v.want[j].Name - }) + sort.Sort(ftypes.Packages(got)) + sort.Sort(ftypes.Packages(v.want)) assert.Equal(t, v.want, got) }) diff --git a/pkg/dependency/parser/golang/sum/parse_testcase.go b/pkg/dependency/parser/golang/sum/parse_testcase.go index 70d4972c6f76..fc607de86e2a 100644 --- a/pkg/dependency/parser/golang/sum/parse_testcase.go +++ b/pkg/dependency/parser/golang/sum/parse_testcase.go @@ -1,6 +1,6 @@ package sum -import "github.com/aquasecurity/trivy/pkg/dependency/types" +import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( // docker run --name gomod --rm -it golang:1.15 bash @@ -10,12 +10,12 @@ var ( // go mod init github.com/org/repo // go get golang.org/x/xerrors // go list -m all | awk 'NR>1 {sub(/^v/, "", $2); printf("{\""$1"\", \""$2"\", },\n")}' - GoModNormal = []types.Library{ + GoModNormal = []ftypes.Package{ {Name: "golang.org/x/xerrors", Version: "0.0.0-20200804184101-5ec99f83aff1"}, } // https://github.com/uudashr/gopkgs/blob/616744904701ef01d868da4b66aad0e6856c361d/v2/go.sum - GoModEmptyLine = []types.Library{ + GoModEmptyLine = []ftypes.Package{ {Name: "github.com/karrick/godirwalk", Version: "1.12.0"}, {Name: "github.com/pkg/errors", Version: "0.8.1"}, } @@ -30,7 +30,7 @@ var ( // go get github.com/stretchr/testify // go get github.com/BurntSushi/toml // go list -m all | awk 'NR>1 {sub(/^v/, "", $2); printf("{\""$1"\", \""$2"\", },\n")}' - GoModMany = []types.Library{ + GoModMany = []ftypes.Package{ {Name: "github.com/BurntSushi/toml", Version: "0.3.1"}, {Name: "github.com/cpuguy83/go-md2man/v2", Version: "2.0.0-20190314233015-f79a8a8ca69d"}, {Name: "github.com/davecgh/go-spew", Version: "1.1.0"}, @@ -53,7 +53,7 @@ var ( // go mod init github.com/org/repo // go get github.com/aquasecurity/trivy // go list -m all | awk 'NR>1 {sub(/^v/, "", $2); printf("{\""$1"\", \""$2"\", },\n")}' - GoModTrivy = []types.Library{ + GoModTrivy = []ftypes.Package{ {Name: "cloud.google.com/go", Version: "0.65.0"}, {Name: "cloud.google.com/go/bigquery", Version: "1.8.0"}, {Name: "cloud.google.com/go/datastore", Version: "1.1.0"}, diff --git a/pkg/dependency/parser/gradle/lockfile/parse.go b/pkg/dependency/parser/gradle/lockfile/parse.go index 47827faca036..9210d6d2f865 100644 --- a/pkg/dependency/parser/gradle/lockfile/parse.go +++ b/pkg/dependency/parser/gradle/lockfile/parse.go @@ -6,19 +6,18 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } -func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { - var libs []types.Library +func (Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { + var pkgs []ftypes.Package scanner := bufio.NewScanner(r) var lineNum int for scanner.Scan() { @@ -36,19 +35,19 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, er name := strings.Join(dep[:2], ":") version := strings.Split(dep[2], "=")[0] // remove classPaths - libs = append(libs, types.Library{ + pkgs = append(pkgs, ftypes.Package{ ID: dependency.ID(ftypes.Gradle, name, version), Name: name, Version: version, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: lineNum, EndLine: lineNum, }, }, - Relationship: types.RelationshipUnknown, + Relationship: ftypes.RelationshipUnknown, }) } - return utils.UniqueLibraries(libs), nil, nil + return utils.UniquePackages(pkgs), nil, nil } diff --git a/pkg/dependency/parser/gradle/lockfile/parse_test.go b/pkg/dependency/parser/gradle/lockfile/parse_test.go index e9f76883e4e5..fa73ea81122e 100644 --- a/pkg/dependency/parser/gradle/lockfile/parse_test.go +++ b/pkg/dependency/parser/gradle/lockfile/parse_test.go @@ -3,10 +3,9 @@ package lockfile import ( "os" "sort" - "strings" "testing" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" ) @@ -14,17 +13,17 @@ func TestParser_Parse(t *testing.T) { tests := []struct { name string inputFile string - want []types.Library + want []ftypes.Package }{ { name: "happy path", inputFile: "testdata/happy.lockfile", - want: []types.Library{ + want: []ftypes.Package{ { ID: "cglib:cglib-nodep:2.1.2", Name: "cglib:cglib-nodep", Version: "2.1.2", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 4, EndLine: 4, @@ -35,7 +34,7 @@ func TestParser_Parse(t *testing.T) { ID: "org.springframework:spring-asm:3.1.3.RELEASE", Name: "org.springframework:spring-asm", Version: "3.1.3.RELEASE", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 5, EndLine: 5, @@ -46,7 +45,7 @@ func TestParser_Parse(t *testing.T) { ID: "org.springframework:spring-beans:5.0.5.RELEASE", Name: "org.springframework:spring-beans", Version: "5.0.5.RELEASE", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 6, EndLine: 6, @@ -68,19 +67,9 @@ func TestParser_Parse(t *testing.T) { f, err := os.Open(tt.inputFile) assert.NoError(t, err) - libs, _, _ := parser.Parse(f) - sortLibs(libs) - assert.Equal(t, tt.want, libs) + pkgs, _, _ := parser.Parse(f) + sort.Sort(ftypes.Packages(pkgs)) + assert.Equal(t, tt.want, pkgs) }) } } - -func sortLibs(libs []types.Library) { - sort.Slice(libs, func(i, j int) bool { - ret := strings.Compare(libs[i].Name, libs[j].Name) - if ret == 0 { - return libs[i].Version < libs[j].Version - } - return ret < 0 - }) -} diff --git a/pkg/dependency/parser/hex/mix/parse.go b/pkg/dependency/parser/hex/mix/parse.go index ed5543ca4507..b95a375b29bd 100644 --- a/pkg/dependency/parser/hex/mix/parse.go +++ b/pkg/dependency/parser/hex/mix/parse.go @@ -7,7 +7,6 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -18,14 +17,14 @@ type Parser struct { logger *log.Logger } -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("mix"), } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { - var libs []types.Library +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { + var pkgs []ftypes.Package scanner := bufio.NewScanner(r) var lineNumber int // It is used to save dependency location for scanner.Scan() { @@ -54,11 +53,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, continue } version := strings.Trim(ss[2], `"`) - libs = append(libs, types.Library{ + pkgs = append(pkgs, ftypes.Package{ ID: dependency.ID(ftypes.Hex, name, version), Name: name, Version: version, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: lineNumber, EndLine: lineNumber, @@ -67,5 +66,5 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, }) } - return utils.UniqueLibraries(libs), nil, nil + return utils.UniquePackages(pkgs), nil, nil } diff --git a/pkg/dependency/parser/hex/mix/parse_test.go b/pkg/dependency/parser/hex/mix/parse_test.go index ab3d929dd96f..a46f35f8d158 100644 --- a/pkg/dependency/parser/hex/mix/parse_test.go +++ b/pkg/dependency/parser/hex/mix/parse_test.go @@ -3,10 +3,9 @@ package mix import ( "os" "sort" - "strings" "testing" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" ) @@ -14,35 +13,55 @@ func TestParser_Parse(t *testing.T) { tests := []struct { name string inputFile string - want []types.Library + want []ftypes.Package }{ { name: "happy path", inputFile: "testdata/happy.mix.lock", - want: []types.Library{ + want: []ftypes.Package{ { - ID: "bunt@0.2.0", - Name: "bunt", - Version: "0.2.0", - Locations: []types.Location{{StartLine: 2, EndLine: 2}}, + ID: "bunt@0.2.0", + Name: "bunt", + Version: "0.2.0", + Locations: []ftypes.Location{ + { + StartLine: 2, + EndLine: 2, + }, + }, }, { - ID: "credo@1.6.6", - Name: "credo", - Version: "1.6.6", - Locations: []types.Location{{StartLine: 3, EndLine: 3}}, + ID: "credo@1.6.6", + Name: "credo", + Version: "1.6.6", + Locations: []ftypes.Location{ + { + StartLine: 3, + EndLine: 3, + }, + }, }, { - ID: "file_system@0.2.10", - Name: "file_system", - Version: "0.2.10", - Locations: []types.Location{{StartLine: 4, EndLine: 4}}, + ID: "file_system@0.2.10", + Name: "file_system", + Version: "0.2.10", + Locations: []ftypes.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, }, { - ID: "jason@1.3.0", - Name: "jason", - Version: "1.3.0", - Locations: []types.Location{{StartLine: 5, EndLine: 5}}, + ID: "jason@1.3.0", + Name: "jason", + Version: "1.3.0", + Locations: []ftypes.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, }, }, }, @@ -59,19 +78,9 @@ func TestParser_Parse(t *testing.T) { f, err := os.Open(tt.inputFile) assert.NoError(t, err) - libs, _, _ := parser.Parse(f) - sortLibs(libs) - assert.Equal(t, tt.want, libs) + pkgs, _, _ := parser.Parse(f) + sort.Sort(ftypes.Packages(pkgs)) + assert.Equal(t, tt.want, pkgs) }) } } - -func sortLibs(libs []types.Library) { - sort.Slice(libs, func(i, j int) bool { - ret := strings.Compare(libs[i].Name, libs[j].Name) - if ret == 0 { - return libs[i].Version < libs[j].Version - } - return ret < 0 - }) -} diff --git a/pkg/dependency/parser/java/jar/parse.go b/pkg/dependency/parser/java/jar/parse.go index 06f130c6b07c..86bfb64a5ed6 100644 --- a/pkg/dependency/parser/java/jar/parse.go +++ b/pkg/dependency/parser/java/jar/parse.go @@ -17,7 +17,7 @@ import ( "github.com/samber/lo" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -61,7 +61,7 @@ func WithSize(size int64) Option { } } -func NewParser(c Client, opts ...Option) types.Parser { +func NewParser(c Client, opts ...Option) *Parser { p := &Parser{ logger: log.WithPrefix("jar"), client: c, @@ -74,15 +74,15 @@ func NewParser(c Client, opts ...Option) types.Parser { return p } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { - libs, deps, err := p.parseArtifact(p.rootFilePath, p.size, r) +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { + pkgs, deps, err := p.parseArtifact(p.rootFilePath, p.size, r) if err != nil { return nil, nil, xerrors.Errorf("unable to parse %s: %w", p.rootFilePath, err) } - return removeLibraryDuplicates(libs), deps, nil + return removePackageDuplicates(pkgs), deps, nil } -func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { p.logger.Debug("Parsing Java artifacts...", log.String("file", filePath)) // Try to extract artifactId and version from the file name @@ -90,14 +90,14 @@ func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt) fileName := filepath.Base(filePath) fileProps := parseFileName(filePath) - libs, m, foundPomProps, err := p.traverseZip(filePath, size, r, fileProps) + pkgs, m, foundPomProps, err := p.traverseZip(filePath, size, r, fileProps) if err != nil { return nil, nil, xerrors.Errorf("zip error: %w", err) } // If pom.properties is found, it should be preferred than MANIFEST.MF. if foundPomProps { - return libs, nil, nil + return pkgs, nil, nil } manifestProps := m.properties(filePath) @@ -105,9 +105,9 @@ func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt) // In offline mode, we will not check if the artifact information is correct. if !manifestProps.Valid() { p.logger.Debug("Unable to identify POM in offline mode", log.String("file", fileName)) - return libs, nil, nil + return pkgs, nil, nil } - return append(libs, manifestProps.Library()), nil, nil + return append(pkgs, manifestProps.Package()), nil, nil } if manifestProps.Valid() { @@ -115,14 +115,14 @@ func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt) // We have to make sure that the artifact exists actually. if ok, _ := p.client.Exists(manifestProps.GroupID, manifestProps.ArtifactID); ok { // If groupId and artifactId are valid, they will be returned. - return append(libs, manifestProps.Library()), nil, nil + return append(pkgs, manifestProps.Package()), nil, nil } } // If groupId and artifactId are not found, call Maven Central's search API with SHA-1 digest. props, err := p.searchBySHA1(r, filePath) if err == nil { - return append(libs, props.Library()), nil, nil + return append(pkgs, props.Package()), nil, nil } else if !errors.Is(err, ArtifactNotFoundErr) { return nil, nil, xerrors.Errorf("failed to search by SHA1: %w", err) } @@ -131,7 +131,7 @@ func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt) // Return when artifactId or version from the file name are empty if fileProps.ArtifactID == "" || fileProps.Version == "" { - return libs, nil, nil + return pkgs, nil, nil } // Try to search groupId by artifactId via sonatype API @@ -140,17 +140,17 @@ func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt) if err == nil { p.logger.Debug("POM was determined in a heuristic way", log.String("file", fileName), log.String("artifact", fileProps.String())) - libs = append(libs, fileProps.Library()) + pkgs = append(pkgs, fileProps.Package()) } else if !errors.Is(err, ArtifactNotFoundErr) { return nil, nil, xerrors.Errorf("failed to search by artifact id: %w", err) } - return libs, nil, nil + return pkgs, nil, nil } func (p *Parser) traverseZip(filePath string, size int64, r xio.ReadSeekerAt, fileProps Properties) ( - []types.Library, manifest, bool, error) { - var libs []types.Library + []ftypes.Package, manifest, bool, error) { + var pkgs []ftypes.Package var m manifest var foundPomProps bool @@ -166,9 +166,9 @@ func (p *Parser) traverseZip(filePath string, size int64, r xio.ReadSeekerAt, fi if err != nil { return nil, manifest{}, false, xerrors.Errorf("failed to parse %s: %w", fileInJar.Name, err) } - // Validation of props to avoid getting libs with empty Name/Version + // Validation of props to avoid getting packages with empty Name/Version if props.Valid() { - libs = append(libs, props.Library()) + pkgs = append(pkgs, props.Package()) // Check if the pom.properties is for the original JAR/WAR/EAR if fileProps.ArtifactID == props.ArtifactID && fileProps.Version == props.Version { @@ -181,18 +181,18 @@ func (p *Parser) traverseZip(filePath string, size int64, r xio.ReadSeekerAt, fi return nil, manifest{}, false, xerrors.Errorf("failed to parse MANIFEST.MF: %w", err) } case isArtifact(fileInJar.Name): - innerLibs, _, err := p.parseInnerJar(fileInJar, filePath) // TODO process inner deps + innerPkgs, _, err := p.parseInnerJar(fileInJar, filePath) // TODO process inner deps if err != nil { p.logger.Debug("Failed to parse", log.String("file", fileInJar.Name), log.Err(err)) continue } - libs = append(libs, innerLibs...) + pkgs = append(pkgs, innerPkgs...) } } - return libs, m, foundPomProps, nil + return pkgs, m, foundPomProps, nil } -func (p *Parser) parseInnerJar(zf *zip.File, rootPath string) ([]types.Library, []types.Dependency, error) { +func (p *Parser) parseInnerJar(zf *zip.File, rootPath string) ([]ftypes.Package, []ftypes.Dependency, error) { fr, err := zf.Open() if err != nil { return nil, nil, xerrors.Errorf("unable to open %s: %w", zf.Name, err) @@ -221,12 +221,12 @@ func (p *Parser) parseInnerJar(zf *zip.File, rootPath string) ([]types.Library, } // Parse jar/war/ear recursively - innerLibs, innerDeps, err := p.parseArtifact(fullPath, int64(zf.UncompressedSize64), f) + innerPkgs, innerDeps, err := p.parseArtifact(fullPath, int64(zf.UncompressedSize64), f) if err != nil { return nil, nil, xerrors.Errorf("failed to parse %s: %w", zf.Name, err) } - return innerLibs, innerDeps, nil + return innerPkgs, innerDeps, nil } func (p *Parser) searchBySHA1(r io.ReadSeeker, filePath string) (Properties, error) { @@ -438,8 +438,8 @@ func (m manifest) determineVersion() (string, error) { return strings.TrimSpace(version), nil } -func removeLibraryDuplicates(libs []types.Library) []types.Library { - return lo.UniqBy(libs, func(lib types.Library) string { - return fmt.Sprintf("%s::%s::%s", lib.Name, lib.Version, lib.FilePath) +func removePackageDuplicates(pkgs []ftypes.Package) []ftypes.Package { + return lo.UniqBy(pkgs, func(pkg ftypes.Package) string { + return fmt.Sprintf("%s::%s::%s", pkg.Name, pkg.Version, pkg.FilePath) }) } diff --git a/pkg/dependency/parser/java/jar/parse_test.go b/pkg/dependency/parser/java/jar/parse_test.go index 88125a5a34b1..6813349e9754 100644 --- a/pkg/dependency/parser/java/jar/parse_test.go +++ b/pkg/dependency/parser/java/jar/parse_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) var ( @@ -23,7 +23,7 @@ var ( // mvn dependency:list // mvn dependency:tree -Dscope=compile -Dscope=runtime | awk '/:tree/,/BUILD SUCCESS/' | awk 'NR > 1 { print }' | head -n -2 | awk '{print $NF}' | awk -F":" '{printf("{\""$1":"$2"\", \""$4 "\", \"\"},\n")}' // paths filled in manually - wantMaven = []types.Library{ + wantMaven = []ftypes.Package{ { Name: "com.example:web-app", Version: "1.0-SNAPSHOT", @@ -70,7 +70,7 @@ var ( // docker run --rm --name test -it test bash // gradle app:dependencies --configuration implementation | grep "[+\]---" | cut -d" " -f2 | awk -F":" '{printf("{\""$1":"$2"\", \""$3"\", \"\"},\n")}' // paths filled in manually - wantGradle = []types.Library{ + wantGradle = []ftypes.Package{ { Name: "commons-dbcp:commons-dbcp", Version: "1.4", @@ -94,7 +94,7 @@ var ( } // manually created - wantSHA1 = []types.Library{ + wantSHA1 = []ftypes.Package{ { Name: "org.springframework:spring-core", Version: "5.3.3", @@ -103,7 +103,7 @@ var ( } // offline - wantOffline = []types.Library{ + wantOffline = []ftypes.Package{ { Name: "org.springframework:Spring Framework", Version: "2.5.6.SEC03", @@ -112,7 +112,7 @@ var ( } // manually created - wantHeuristic = []types.Library{ + wantHeuristic = []ftypes.Package{ { Name: "com.example:heuristic", Version: "1.0.0-SNAPSHOT", @@ -121,7 +121,7 @@ var ( } // manually created - wantFatjar = []types.Library{ + wantFatjar = []ftypes.Package{ { Name: "com.google.guava:failureaccess", Version: "1.0.1", @@ -150,7 +150,7 @@ var ( } // manually created - wantNestedJar = []types.Library{ + wantNestedJar = []ftypes.Package{ { Name: "test:nested", Version: "0.0.1", @@ -169,7 +169,7 @@ var ( } // manually created - wantDuplicatesJar = []types.Library{ + wantDuplicatesJar = []ftypes.Package{ { Name: "io.quarkus.gizmo:gizmo", Version: "1.1.1.Final", @@ -211,7 +211,7 @@ func TestParse(t *testing.T) { name string file string // Test input file offline bool - want []types.Library + want []ftypes.Package }{ { name: "maven", @@ -319,12 +319,8 @@ func TestParse(t *testing.T) { got, _, err := p.Parse(f) require.NoError(t, err) - sort.Slice(got, func(i, j int) bool { - return got[i].Name < got[j].Name - }) - sort.Slice(v.want, func(i, j int) bool { - return v.want[i].Name < v.want[j].Name - }) + sort.Sort(ftypes.Packages(got)) + sort.Sort(ftypes.Packages(v.want)) assert.Equal(t, v.want, got) }) diff --git a/pkg/dependency/parser/java/jar/types.go b/pkg/dependency/parser/java/jar/types.go index ddd378b778b7..4246bbceb27f 100644 --- a/pkg/dependency/parser/java/jar/types.go +++ b/pkg/dependency/parser/java/jar/types.go @@ -5,7 +5,7 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) var ArtifactNotFoundErr = xerrors.New("no artifact found") @@ -17,8 +17,8 @@ type Properties struct { FilePath string // path to file containing these props } -func (p Properties) Library() types.Library { - return types.Library{ +func (p Properties) Package() ftypes.Package { + return ftypes.Package{ Name: fmt.Sprintf("%s:%s", p.GroupID, p.ArtifactID), Version: p.Version, FilePath: p.FilePath, diff --git a/pkg/dependency/parser/java/pom/artifact.go b/pkg/dependency/parser/java/pom/artifact.go index 1e849aa6cab5..a99ff8569357 100644 --- a/pkg/dependency/parser/java/pom/artifact.go +++ b/pkg/dependency/parser/java/pom/artifact.go @@ -9,7 +9,7 @@ import ( "github.com/samber/lo" "golang.org/x/exp/slices" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" ) @@ -26,9 +26,9 @@ type artifact struct { Exclusions map[string]struct{} Module bool - Relationship types.Relationship + Relationship ftypes.Relationship - Locations types.Locations + Locations ftypes.Locations } func newArtifact(groupID, artifactID, version string, licenses []string, props map[string]string) artifact { @@ -37,7 +37,7 @@ func newArtifact(groupID, artifactID, version string, licenses []string, props m ArtifactID: evaluateVariable(artifactID, props, nil), Version: newVersion(evaluateVariable(version, props, nil)), Licenses: licenses, - Relationship: types.RelationshipIndirect, // default + Relationship: ftypes.RelationshipIndirect, // default } } @@ -49,10 +49,6 @@ func (a artifact) Equal(o artifact) bool { return a.GroupID == o.GroupID || a.ArtifactID == o.ArtifactID || a.Version.String() == o.Version.String() } -func (a artifact) JoinLicenses() string { - return strings.Join(a.Licenses, ", ") -} - func (a artifact) ToPOMLicenses() pomLicenses { return pomLicenses{ License: lo.Map(a.Licenses, func(lic string, _ int) pomLicense { diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index 59551a0e98ff..bf8df2ad1c0a 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -19,7 +19,6 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -49,7 +48,7 @@ func WithReleaseRemoteRepos(repos []string) option { } } -type parser struct { +type Parser struct { logger *log.Logger rootPath string cache pomCache @@ -60,7 +59,7 @@ type parser struct { servers []Server } -func NewParser(filePath string, opts ...option) types.Parser { +func NewParser(filePath string, opts ...option) *Parser { o := &options{ offline: false, releaseRemoteRepos: []string{centralURL}, // Maven doesn't use central repository for snapshot dependencies @@ -77,7 +76,7 @@ func NewParser(filePath string, opts ...option) types.Parser { localRepository = filepath.Join(homeDir, ".m2", "repository") } - return &parser{ + return &Parser{ logger: log.WithPrefix("pom"), rootPath: filepath.Clean(filePath), cache: newPOMCache(), @@ -89,7 +88,7 @@ func NewParser(filePath string, opts ...option) types.Parser { } } -func (p *parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { content, err := parsePom(r) if err != nil { return nil, nil, xerrors.Errorf("failed to parse POM: %w", err) @@ -112,18 +111,18 @@ func (p *parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return p.parseRoot(root.artifact(), make(map[string]struct{})) } -func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]types.Library, []types.Dependency, error) { +func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ftypes.Package, []ftypes.Dependency, error) { // Prepare a queue for dependencies queue := newArtifactQueue() // Enqueue root POM - root.Relationship = types.RelationshipRoot + root.Relationship = ftypes.RelationshipRoot root.Module = false queue.enqueue(root) var ( - libs []types.Library - deps []types.Dependency + pkgs ftypes.Packages + deps ftypes.Dependencies rootDepManagement []pomDependency uniqArtifacts = make(map[string]artifact) uniqDeps = make(map[string][]string) @@ -141,12 +140,12 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty } uniqModules[art.String()] = struct{}{} - moduleLibs, moduleDeps, err := p.parseRoot(art, uniqModules) + modulePkgs, moduleDeps, err := p.parseRoot(art, uniqModules) if err != nil { return nil, nil, err } - libs = append(libs, moduleLibs...) + pkgs = append(pkgs, modulePkgs...) if moduleDeps != nil { deps = append(deps, moduleDeps...) } @@ -160,7 +159,7 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty } // mark artifact as Direct, if saved artifact is Direct // take a look `hard requirement for the specified version` test - if uniqueArt.Relationship == types.RelationshipRoot || uniqueArt.Relationship == types.RelationshipDirect { + if uniqueArt.Relationship == ftypes.RelationshipRoot || uniqueArt.Relationship == ftypes.RelationshipDirect { art.Relationship = uniqueArt.Relationship } // We don't need to overwrite dependency location for hard links @@ -174,13 +173,13 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty return nil, nil, xerrors.Errorf("resolve error (%s): %w", art, err) } - if art.Relationship == types.RelationshipRoot { + if art.Relationship == ftypes.RelationshipRoot { // Managed dependencies in the root POM affect transitive dependencies rootDepManagement = p.resolveDepManagement(result.properties, result.dependencyManagement) // mark its dependencies as "direct" result.dependencies = lo.Map(result.dependencies, func(dep artifact, _ int) artifact { - dep.Relationship = types.RelationshipDirect + dep.Relationship = ftypes.RelationshipDirect return dep }) } @@ -219,37 +218,37 @@ func (p *parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ty } } - // Convert to []types.Library and []types.Dependency + // Convert to []ftypes.Package and []ftypes.Dependency for name, art := range uniqArtifacts { - lib := types.Library{ + pkg := ftypes.Package{ ID: packageID(name, art.Version.String()), Name: name, Version: art.Version.String(), - License: art.JoinLicenses(), + Licenses: art.Licenses, Relationship: art.Relationship, Locations: art.Locations, } - libs = append(libs, lib) + pkgs = append(pkgs, pkg) // Convert dependency names into dependency IDs - dependsOn := lo.FilterMap(uniqDeps[lib.ID], func(dependOnName string, _ int) (string, bool) { + dependsOn := lo.FilterMap(uniqDeps[pkg.ID], func(dependOnName string, _ int) (string, bool) { ver := depVersion(dependOnName, uniqArtifacts) return packageID(dependOnName, ver), ver != "" }) sort.Strings(dependsOn) if len(dependsOn) > 0 { - deps = append(deps, types.Dependency{ - ID: lib.ID, + deps = append(deps, ftypes.Dependency{ + ID: pkg.ID, DependsOn: dependsOn, }) } } - sort.Sort(types.Libraries(libs)) - sort.Sort(types.Dependencies(deps)) + sort.Sort(pkgs) + sort.Sort(deps) - return libs, deps, nil + return pkgs, deps, nil } // depVersion finds dependency in uniqArtifacts and return its version @@ -260,7 +259,7 @@ func depVersion(depName string, uniqArtifacts map[string]artifact) string { return "" } -func (p *parser) parseModule(currentPath, relativePath string) (artifact, error) { +func (p *Parser) parseModule(currentPath, relativePath string) (artifact, error) { // modulePath: "root/" + "module/" => "root/module" module, err := p.openRelativePom(currentPath, relativePath) if err != nil { @@ -280,7 +279,7 @@ func (p *parser) parseModule(currentPath, relativePath string) (artifact, error) return moduleArtifact, nil } -func (p *parser) resolve(art artifact, rootDepManagement []pomDependency) (analysisResult, error) { +func (p *Parser) resolve(art artifact, rootDepManagement []pomDependency) (analysisResult, error) { // If the artifact is found in cache, it is returned. if result := p.cache.get(art); result != nil { return *result, nil @@ -319,7 +318,7 @@ type analysisOptions struct { lineNumber bool // Save line numbers } -func (p *parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error) { +func (p *Parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error) { if pom == nil || pom.content == nil { return analysisResult{}, nil } @@ -362,7 +361,7 @@ func (p *parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error) }, nil } -func (p *parser) mergeDependencyManagements(depManagements ...[]pomDependency) []pomDependency { +func (p *Parser) mergeDependencyManagements(depManagements ...[]pomDependency) []pomDependency { uniq := make(map[string]struct{}) var depManagement []pomDependency // The preceding argument takes precedence. @@ -378,7 +377,7 @@ func (p *parser) mergeDependencyManagements(depManagements ...[]pomDependency) [ return depManagement } -func (p *parser) parseDependencies(deps []pomDependency, props map[string]string, depManagement []pomDependency, +func (p *Parser) parseDependencies(deps []pomDependency, props map[string]string, depManagement []pomDependency, opts analysisOptions) []artifact { // Imported POMs often have no dependencies, so dependencyManagement resolution can be skipped. if len(deps) == 0 { @@ -403,7 +402,7 @@ func (p *parser) parseDependencies(deps []pomDependency, props map[string]string return dependencies } -func (p *parser) resolveDepManagement(props map[string]string, depManagement []pomDependency) []pomDependency { +func (p *Parser) resolveDepManagement(props map[string]string, depManagement []pomDependency) []pomDependency { var newDepManagement, imports []pomDependency for _, dep := range depManagement { // cf. https://howtodoinjava.com/maven/maven-dependency-scopes/#import @@ -437,7 +436,7 @@ func (p *parser) resolveDepManagement(props map[string]string, depManagement []p return newDepManagement } -func (p *parser) mergeDependencies(parent, child []artifact, exclusions map[string]struct{}) []artifact { +func (p *Parser) mergeDependencies(parent, child []artifact, exclusions map[string]struct{}) []artifact { var deps []artifact unique := make(map[string]struct{}) @@ -471,7 +470,7 @@ func excludeDep(exclusions map[string]struct{}, art artifact) bool { return false } -func (p *parser) parseParent(currentPath string, parent pomParent) (analysisResult, error) { +func (p *Parser) parseParent(currentPath string, parent pomParent) (analysisResult, error) { // Pass nil properties so that variables in are not evaluated. target := newArtifact(parent.GroupId, parent.ArtifactId, parent.Version, nil, nil) // if version is property (e.g. ${revision}) - we still need to parse this pom @@ -503,7 +502,7 @@ func (p *parser) parseParent(currentPath string, parent pomParent) (analysisResu return result, nil } -func (p *parser) retrieveParent(currentPath, relativePath string, target artifact) (*pom, error) { +func (p *Parser) retrieveParent(currentPath, relativePath string, target artifact) (*pom, error) { var errs error // Try relativePath @@ -536,7 +535,7 @@ func (p *parser) retrieveParent(currentPath, relativePath string, target artifac return nil, errs } -func (p *parser) tryRelativePath(parentArtifact artifact, currentPath, relativePath string) (*pom, error) { +func (p *Parser) tryRelativePath(parentArtifact artifact, currentPath, relativePath string) (*pom, error) { pom, err := p.openRelativePom(currentPath, relativePath) if err != nil { return nil, err @@ -563,7 +562,7 @@ func (p *parser) tryRelativePath(parentArtifact artifact, currentPath, relativeP return pom, nil } -func (p *parser) openRelativePom(currentPath, relativePath string) (*pom, error) { +func (p *Parser) openRelativePom(currentPath, relativePath string) (*pom, error) { // e.g. child/pom.xml => child/ dir := filepath.Dir(currentPath) @@ -585,7 +584,7 @@ func (p *parser) openRelativePom(currentPath, relativePath string) (*pom, error) return pom, nil } -func (p *parser) openPom(filePath string) (*pom, error) { +func (p *Parser) openPom(filePath string) (*pom, error) { f, err := os.Open(filePath) if err != nil { return nil, xerrors.Errorf("file open error (%s): %w", filePath, err) @@ -601,7 +600,7 @@ func (p *parser) openPom(filePath string) (*pom, error) { content: content, }, nil } -func (p *parser) tryRepository(groupID, artifactID, version string) (*pom, error) { +func (p *Parser) tryRepository(groupID, artifactID, version string) (*pom, error) { if version == "" { return nil, xerrors.Errorf("Version missing for %s:%s", groupID, artifactID) } @@ -627,14 +626,14 @@ func (p *parser) tryRepository(groupID, artifactID, version string) (*pom, error return nil, xerrors.Errorf("%s:%s:%s was not found in local/remote repositories", groupID, artifactID, version) } -func (p *parser) loadPOMFromLocalRepository(paths []string) (*pom, error) { +func (p *Parser) loadPOMFromLocalRepository(paths []string) (*pom, error) { paths = append([]string{p.localRepository}, paths...) localPath := filepath.Join(paths...) return p.openPom(localPath) } -func (p *parser) fetchPOMFromRemoteRepositories(paths []string, snapshot bool) (*pom, error) { +func (p *Parser) fetchPOMFromRemoteRepositories(paths []string, snapshot bool) (*pom, error) { // Do not try fetching pom.xml from remote repositories in offline mode if p.offline { p.logger.Debug("Fetching the remote pom.xml is skipped") @@ -660,7 +659,7 @@ func (p *parser) fetchPOMFromRemoteRepositories(paths []string, snapshot bool) ( return nil, xerrors.Errorf("the POM was not found in remote remoteRepositories") } -func (p *parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom, error) { +func (p *Parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom, error) { repoURL, err := url.Parse(repo) if err != nil { p.logger.Error("URL parse error", log.String("repo", repo)) diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go index 7c14839ec3fa..3627a5c2eb90 100644 --- a/pkg/dependency/parser/java/pom/parse_test.go +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/java/pom" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestPom_Parse(t *testing.T) { @@ -20,29 +20,29 @@ func TestPom_Parse(t *testing.T) { inputFile string local bool offline bool - want []types.Library - wantDeps []types.Dependency + want []ftypes.Package + wantDeps []ftypes.Dependency wantErr string }{ { name: "local repository", inputFile: filepath.Join("testdata", "happy", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:happy:1.0.0", Name: "com.example:happy", Version: "1.0.0", - License: "BSD-3-Clause", - Relationship: types.RelationshipRoot, + Licenses: []string{"BSD-3-Clause"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 32, EndLine: 36, @@ -53,8 +53,8 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-runtime:1.0.0", Name: "org.example:example-runtime", Version: "1.0.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 37, EndLine: 42, @@ -62,7 +62,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:happy:1.0.0", DependsOn: []string{ @@ -76,21 +76,21 @@ func TestPom_Parse(t *testing.T) { name: "remote release repository", inputFile: filepath.Join("testdata", "happy", "pom.xml"), local: false, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:happy:1.0.0", Name: "com.example:happy", Version: "1.0.0", - License: "BSD-3-Clause", - Relationship: types.RelationshipRoot, + Licenses: []string{"BSD-3-Clause"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 32, EndLine: 36, @@ -101,8 +101,8 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-runtime:1.0.0", Name: "org.example:example-runtime", Version: "1.0.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 37, EndLine: 42, @@ -110,7 +110,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:happy:1.0.0", DependsOn: []string{ @@ -124,19 +124,19 @@ func TestPom_Parse(t *testing.T) { name: "snapshot dependency", inputFile: filepath.Join("testdata", "snapshot", "pom.xml"), local: false, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:happy:1.0.0", Name: "com.example:happy", Version: "1.0.0", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-dependency:1.2.3-SNAPSHOT", Name: "org.example:example-dependency", Version: "1.2.3-SNAPSHOT", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 14, EndLine: 18, @@ -144,7 +144,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:happy:1.0.0", DependsOn: []string{ @@ -158,13 +158,13 @@ func TestPom_Parse(t *testing.T) { inputFile: filepath.Join("testdata", "offline", "pom.xml"), local: false, offline: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "org.example:example-offline:2.3.4", Name: "org.example:example-offline", Version: "2.3.4", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 17, EndLine: 21, @@ -177,21 +177,21 @@ func TestPom_Parse(t *testing.T) { name: "inherit parent properties", inputFile: filepath.Join("testdata", "parent-properties", "child", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:child:1.0.0", Name: "com.example:child", Version: "1.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 33, EndLine: 37, @@ -199,7 +199,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:child:1.0.0", DependsOn: []string{ @@ -212,20 +212,20 @@ func TestPom_Parse(t *testing.T) { name: "inherit project properties from parent", inputFile: filepath.Join("testdata", "project-version-from-parent", "child", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:child:2.0.0", Name: "com.example:child", Version: "2.0.0", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:2.0.0", Name: "org.example:example-api", Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 18, EndLine: 22, @@ -233,7 +233,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:child:2.0.0", DependsOn: []string{ @@ -246,20 +246,20 @@ func TestPom_Parse(t *testing.T) { name: "inherit properties in parent depManagement with import scope", inputFile: filepath.Join("testdata", "inherit-props", "base", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:test:0.0.1-SNAPSHOT", Name: "com.example:test", Version: "0.0.1-SNAPSHOT", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:2.0.0", Name: "org.example:example-api", Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 18, EndLine: 21, @@ -267,7 +267,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:test:0.0.1-SNAPSHOT", DependsOn: []string{ @@ -280,19 +280,19 @@ func TestPom_Parse(t *testing.T) { name: "dependencyManagement prefers child properties", inputFile: filepath.Join("testdata", "parent-child-properties", "child", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:child:1.0.0", Name: "com.example:child", Version: "1.0.0", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 22, EndLine: 26, @@ -303,10 +303,10 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-api:4.0.0", Name: "org.example:example-api", Version: "4.0.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:child:1.0.0", DependsOn: []string{ @@ -325,23 +325,23 @@ func TestPom_Parse(t *testing.T) { name: "inherit parent dependencies", inputFile: filepath.Join("testdata", "parent-dependencies", "child", "pom.xml"), local: false, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:child:1.0.0-SNAPSHOT", Name: "com.example:child", Version: "1.0.0-SNAPSHOT", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:child:1.0.0-SNAPSHOT", DependsOn: []string{ @@ -354,21 +354,21 @@ func TestPom_Parse(t *testing.T) { name: "inherit parent dependencyManagement", inputFile: filepath.Join("testdata", "parent-dependency-management", "child", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:child:3.0.0", Name: "com.example:child", Version: "3.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 26, EndLine: 29, @@ -376,7 +376,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:child:3.0.0", DependsOn: []string{ @@ -389,21 +389,21 @@ func TestPom_Parse(t *testing.T) { name: "transitive parents", inputFile: filepath.Join("testdata", "transitive-parents", "base", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:base:4.0.0", Name: "com.example:base", Version: "4.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-child:2.0.0", Name: "org.example:example-child", Version: "2.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 28, EndLine: 32, @@ -414,11 +414,11 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipIndirect, + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipIndirect, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:base:4.0.0", DependsOn: []string{ @@ -437,21 +437,21 @@ func TestPom_Parse(t *testing.T) { name: "parent relativePath", inputFile: filepath.Join("testdata", "parent-relative-path", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:child:1.0.0", Name: "com.example:child", Version: "1.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 26, EndLine: 30, @@ -459,7 +459,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:child:1.0.0", DependsOn: []string{ @@ -472,19 +472,19 @@ func TestPom_Parse(t *testing.T) { name: "parent version in property", inputFile: filepath.Join("testdata", "parent-version-is-property", "child", "pom.xml"), local: false, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:child:1.0.0-SNAPSHOT", Name: "com.example:child", Version: "1.0.0-SNAPSHOT", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.1.1", Name: "org.example:example-api", Version: "1.1.1", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 19, EndLine: 22, @@ -492,7 +492,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:child:1.0.0-SNAPSHOT", DependsOn: []string{ @@ -505,21 +505,21 @@ func TestPom_Parse(t *testing.T) { name: "parent in a remote repository", inputFile: filepath.Join("testdata", "parent-remote-repository", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "org.example:child:1.0.0", Name: "org.example:child", Version: "1.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 25, EndLine: 29, @@ -527,7 +527,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "org.example:child:1.0.0", DependsOn: []string{ @@ -541,25 +541,25 @@ func TestPom_Parse(t *testing.T) { // [INFO] com.example:soft:jar:1.0.0 // [INFO] +- org.example:example-api:jar:1.7.30:compile // [INFO] \- org.example:example-dependency:jar:1.2.3:compile - // Save DependsOn for each library - https://github.com/aquasecurity/go-dep-parser/pull/243#discussion_r1303904548 + // Save DependsOn for each package - https://github.com/aquasecurity/go-dep-parser/pull/243#discussion_r1303904548 name: "soft requirement", inputFile: filepath.Join("testdata", "soft-requirement", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:soft:1.0.0", Name: "com.example:soft", Version: "1.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 32, EndLine: 36, @@ -570,8 +570,8 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 37, EndLine: 41, @@ -579,7 +579,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:soft:1.0.0", DependsOn: []string{ @@ -601,50 +601,50 @@ func TestPom_Parse(t *testing.T) { // [INFO] +- org.example:example-dependency:jar:1.2.3:compile // [INFO] | \- org.example:example-api:jar:2.0.0:compile // [INFO] \- org.example:example-dependency2:jar:2.3.4:compile - // Save DependsOn for each library - https://github.com/aquasecurity/go-dep-parser/pull/243#discussion_r1303904548 + // Save DependsOn for each package - https://github.com/aquasecurity/go-dep-parser/pull/243#discussion_r1303904548 name: "soft requirement with transitive dependencies", inputFile: filepath.Join("testdata", "soft-requirement-with-transitive-dependencies", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:soft-transitive:1.0.0", Name: "com.example:soft-transitive", Version: "1.0.0", - Relationship: types.RelationshipRoot, - }, - { - ID: "org.example:example-dependency2:2.3.4", - Name: "org.example:example-dependency2", - Version: "2.3.4", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ - { - StartLine: 18, - EndLine: 22, - }, - }, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 13, EndLine: 17, }, }, }, + { + ID: "org.example:example-dependency2:2.3.4", + Name: "org.example:example-dependency2", + Version: "2.3.4", + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ + { + StartLine: 18, + EndLine: 22, + }, + }, + }, { ID: "org.example:example-api:2.0.0", Name: "org.example:example-api", Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipIndirect, + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipIndirect, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:soft-transitive:1.0.0", DependsOn: []string{ @@ -672,24 +672,24 @@ func TestPom_Parse(t *testing.T) { //[INFO] +- org.example:example-nested:jar:3.3.4:compile //[INFO] \- org.example:example-dependency:jar:1.2.3:compile //[INFO] \- org.example:example-api:jar:2.0.0:compile - // Save DependsOn for each library - https://github.com/aquasecurity/go-dep-parser/pull/243#discussion_r1303904548 + // Save DependsOn for each package - https://github.com/aquasecurity/go-dep-parser/pull/243#discussion_r1303904548 name: "hard requirement for the specified version", inputFile: filepath.Join("testdata", "hard-requirement", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:hard:1.0.0", Name: "com.example:hard", Version: "1.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 33, EndLine: 37, @@ -700,8 +700,8 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-nested:3.3.4", Name: "org.example:example-nested", Version: "3.3.4", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 28, EndLine: 32, @@ -712,11 +712,11 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-api:2.0.0", Name: "org.example:example-api", Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipIndirect, + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipIndirect, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:hard:1.0.0", DependsOn: []string{ @@ -742,13 +742,13 @@ func TestPom_Parse(t *testing.T) { name: "version requirement", inputFile: filepath.Join("testdata", "version-requirement", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:hard:1.0.0", Name: "com.example:hard", Version: "1.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, }, }, @@ -756,21 +756,21 @@ func TestPom_Parse(t *testing.T) { name: "import dependencyManagement", inputFile: filepath.Join("testdata", "import-dependency-management", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:import:2.0.0", Name: "com.example:import", Version: "2.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 34, EndLine: 37, @@ -778,7 +778,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:import:2.0.0", DependsOn: []string{ @@ -791,21 +791,21 @@ func TestPom_Parse(t *testing.T) { name: "import multiple dependencyManagement", inputFile: filepath.Join("testdata", "import-dependency-management-multiple", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:import:2.0.0", Name: "com.example:import", Version: "2.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 42, EndLine: 45, @@ -813,7 +813,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:import:2.0.0", DependsOn: []string{ @@ -826,19 +826,19 @@ func TestPom_Parse(t *testing.T) { name: "exclusions", inputFile: filepath.Join("testdata", "exclusions", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:exclusions:3.0.0", Name: "com.example:exclusions", Version: "3.0.0", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-nested:3.3.3", Name: "org.example:example-nested", Version: "3.3.3", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 14, EndLine: 28, @@ -849,10 +849,10 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:exclusions:3.0.0", DependsOn: []string{ @@ -871,19 +871,19 @@ func TestPom_Parse(t *testing.T) { name: "exclusions in child", inputFile: filepath.Join("testdata", "exclusions-in-child", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:example:1.0.0", Name: "com.example:example", Version: "1.0.0", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-exclusions:4.0.0", Name: "org.example:example-exclusions", Version: "4.0.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 10, EndLine: 14, @@ -894,17 +894,17 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipIndirect, + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipIndirect, }, { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:example:1.0.0", DependsOn: []string{ @@ -924,43 +924,43 @@ func TestPom_Parse(t *testing.T) { name: "exclusions with wildcards", inputFile: filepath.Join("testdata", "wildcard-exclusions", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:wildcard-exclusions:4.0.0", Name: "com.example:wildcard-exclusions", Version: "4.0.0", - Relationship: types.RelationshipRoot, - }, - { - ID: "org.example:example-dependency2:2.3.4", - Name: "org.example:example-dependency2", - Version: "2.3.4", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ - { - StartLine: 25, - EndLine: 35, - }, - }, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 14, EndLine: 24, }, }, }, + { + ID: "org.example:example-dependency2:2.3.4", + Name: "org.example:example-dependency2", + Version: "2.3.4", + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ + { + StartLine: 25, + EndLine: 35, + }, + }, + }, { ID: "org.example:example-nested:3.3.3", Name: "org.example:example-nested", Version: "3.3.3", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 36, EndLine: 46, @@ -968,7 +968,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:wildcard-exclusions:4.0.0", DependsOn: []string{ @@ -983,33 +983,33 @@ func TestPom_Parse(t *testing.T) { name: "multi module", inputFile: filepath.Join("testdata", "multi-module", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:aggregation:1.0.0", Name: "com.example:aggregation", Version: "1.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "com.example:module:1.1.1", Name: "com.example:module", Version: "1.1.1", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, // TODO: Several root modules break SBOM relationships + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, // TODO: Several root modules break SBOM relationships }, { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "org.example:example-api:2.0.0", Name: "org.example:example-api", Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipIndirect, + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipIndirect, }, }, // maven doesn't include modules in dep tree of root pom @@ -1029,7 +1029,7 @@ func TestPom_Parse(t *testing.T) { // [INFO] // [INFO] --- dependency:3.6.0:tree (default-cli) @ aggregation --- // [INFO] com.example:aggregation:pom:1.0.0 - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:module:1.1.1", DependsOn: []string{ @@ -1048,35 +1048,35 @@ func TestPom_Parse(t *testing.T) { name: "Infinity loop for modules", inputFile: filepath.Join("testdata", "modules-infinity-loop", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ // as module { ID: "org.example:module-1:2.0.0", Name: "org.example:module-1", Version: "2.0.0", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:module-2:3.0.0", Name: "org.example:module-2", Version: "3.0.0", - Relationship: types.RelationshipRoot, // TODO: Several root modules break SBOM relationships + Relationship: ftypes.RelationshipRoot, // TODO: Several root modules break SBOM relationships }, { ID: "org.example:root:1.0.0", Name: "org.example:root", Version: "1.0.0", - Relationship: types.RelationshipRoot, // TODO: Several root modules break SBOM relationships + Relationship: ftypes.RelationshipRoot, // TODO: Several root modules break SBOM relationships }, // as dependency { ID: "org.example:module-1:2.0.0", Name: "org.example:module-1", Version: "2.0.0", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "org.example:module-2:3.0.0", DependsOn: []string{ @@ -1089,41 +1089,41 @@ func TestPom_Parse(t *testing.T) { name: "multi module soft requirement", inputFile: filepath.Join("testdata", "multi-module-soft-requirement", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:aggregation:1.0.0", Name: "com.example:aggregation", Version: "1.0.0", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "com.example:module1:1.1.1", Name: "com.example:module1", Version: "1.1.1", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "com.example:module2:1.1.1", Name: "com.example:module2", Version: "1.1.1", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, }, { ID: "org.example:example-api:2.0.0", Name: "org.example:example-api", Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:module1:1.1.1", DependsOn: []string{ @@ -1142,19 +1142,19 @@ func TestPom_Parse(t *testing.T) { name: "overwrite artifact version from dependencyManagement in the root POM", inputFile: filepath.Join("testdata", "root-pom-dep-management", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:root-pom-dep-management:1.0.0", Name: "com.example:root-pom-dep-management", Version: "1.0.0", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-nested:3.3.3", Name: "org.example:example-nested", Version: "3.3.3", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 20, EndLine: 24, @@ -1165,8 +1165,8 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-api:2.0.0", Name: "org.example:example-api", Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipIndirect, + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipIndirect, }, // dependency version is taken from `com.example:root-pom-dep-management` from dependencyManagement // not from `com.example:example-nested` from `com.example:example-nested` @@ -1174,10 +1174,10 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-dependency:1.2.4", Name: "org.example:example-dependency", Version: "1.2.4", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:root-pom-dep-management:1.0.0", DependsOn: []string{ @@ -1202,19 +1202,19 @@ func TestPom_Parse(t *testing.T) { name: "transitive dependencyManagement should not be inherited", inputFile: filepath.Join("testdata", "transitive-dependency-management", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "org.example:transitive-dependency-management:2.0.0", Name: "org.example:transitive-dependency-management", Version: "2.0.0", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-dependency-management3:1.1.1", Name: "org.example:example-dependency-management3", Version: "1.1.1", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 14, EndLine: 18, @@ -1227,17 +1227,17 @@ func TestPom_Parse(t *testing.T) { ID: "org.example:example-api:2.0.0", Name: "org.example:example-api", Version: "2.0.0", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipIndirect, + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipIndirect, }, { ID: "org.example:example-dependency:1.2.3", Name: "org.example:example-dependency", Version: "1.2.3", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "org.example:example-dependency-management3:1.1.1", DependsOn: []string{ @@ -1262,21 +1262,21 @@ func TestPom_Parse(t *testing.T) { name: "parent not found", inputFile: filepath.Join("testdata", "not-found-parent", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:no-parent:1.0-SNAPSHOT", Name: "com.example:no-parent", Version: "1.0-SNAPSHOT", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-api:1.7.30", Name: "org.example:example-api", Version: "1.7.30", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 27, EndLine: 31, @@ -1284,7 +1284,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:no-parent:1.0-SNAPSHOT", DependsOn: []string{ @@ -1297,20 +1297,20 @@ func TestPom_Parse(t *testing.T) { name: "dependency not found", inputFile: filepath.Join("testdata", "not-found-dependency", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:not-found-dependency:1.0.0", Name: "com.example:not-found-dependency", Version: "1.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, { ID: "org.example:example-not-found:999", Name: "org.example:example-not-found", Version: "999", - Relationship: types.RelationshipDirect, - Locations: types.Locations{ + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ { StartLine: 21, EndLine: 25, @@ -1318,7 +1318,7 @@ func TestPom_Parse(t *testing.T) { }, }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "com.example:not-found-dependency:1.0.0", DependsOn: []string{ @@ -1331,13 +1331,13 @@ func TestPom_Parse(t *testing.T) { name: "module not found - unable to parse module", inputFile: filepath.Join("testdata", "not-found-module", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:aggregation:1.0.0", Name: "com.example:aggregation", Version: "1.0.0", - License: "Apache 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, }, }, }, @@ -1345,13 +1345,16 @@ func TestPom_Parse(t *testing.T) { name: "multiply licenses", inputFile: filepath.Join("testdata", "multiply-licenses", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { - ID: "com.example:multiply-licenses:1.0.0", - Name: "com.example:multiply-licenses", - Version: "1.0.0", - License: "MIT, Apache 2.0", - Relationship: types.RelationshipRoot, + ID: "com.example:multiply-licenses:1.0.0", + Name: "com.example:multiply-licenses", + Version: "1.0.0", + Licenses: []string{ + "MIT", + "Apache 2.0", + }, + Relationship: ftypes.RelationshipRoot, }, }, }, @@ -1359,13 +1362,13 @@ func TestPom_Parse(t *testing.T) { name: "inherit parent license", inputFile: filepath.Join("testdata", "inherit-license", "module", "submodule", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example.app:submodule:1.0.0", Name: "com.example.app:submodule", Version: "1.0.0", - License: "Apache-2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"Apache-2.0"}, + Relationship: ftypes.RelationshipRoot, }, }, }, @@ -1373,13 +1376,13 @@ func TestPom_Parse(t *testing.T) { name: "compare ArtifactIDs for base and parent pom's", inputFile: filepath.Join("testdata", "no-parent-infinity-loop", "pom.xml"), local: true, - want: []types.Library{ + want: []ftypes.Package{ { ID: "com.example:child:1.0.0", Name: "com.example:child", Version: "1.0.0", - License: "The Apache Software License, Version 2.0", - Relationship: types.RelationshipRoot, + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipRoot, }, }, }, @@ -1403,7 +1406,7 @@ func TestPom_Parse(t *testing.T) { p := pom.NewParser(tt.inputFile, pom.WithReleaseRemoteRepos(remoteRepos), pom.WithOffline(tt.offline)) - gotLibs, gotDeps, err := p.Parse(f) + gotPkgs, gotDeps, err := p.Parse(f) if tt.wantErr != "" { require.NotNil(t, err) assert.Contains(t, err.Error(), tt.wantErr) @@ -1411,7 +1414,7 @@ func TestPom_Parse(t *testing.T) { } require.NoError(t, err) - assert.Equal(t, tt.want, gotLibs) + assert.Equal(t, tt.want, gotPkgs) assert.Equal(t, tt.wantDeps, gotDeps) }) } diff --git a/pkg/dependency/parser/java/pom/pom.go b/pkg/dependency/parser/java/pom/pom.go index 7ae73fd6edd9..3a0170d36811 100644 --- a/pkg/dependency/parser/java/pom/pom.go +++ b/pkg/dependency/parser/java/pom/pom.go @@ -13,8 +13,9 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/x/slices" ) type pom struct { @@ -110,9 +111,9 @@ func (p pom) artifact() artifact { } func (p pom) licenses() []string { - return lo.FilterMap(p.content.Licenses.License, func(lic pomLicense, _ int) (string, bool) { + return slices.ZeroToNil(lo.FilterMap(p.content.Licenses.License, func(lic pomLicense, _ int) (string, bool) { return lic.Name, lic.Name != "" - }) + })) } func (p pom) repositories(servers []Server) ([]string, []string) { @@ -286,9 +287,9 @@ func (d pomDependency) ToArtifact(opts analysisOptions) artifact { exclusions[fmt.Sprintf("%s:%s", e.GroupID, e.ArtifactID)] = struct{}{} } - var locations types.Locations + var locations ftypes.Locations if opts.lineNumber { - locations = types.Locations{ + locations = ftypes.Locations{ { StartLine: d.StartLine, EndLine: d.EndLine, @@ -302,7 +303,7 @@ func (d pomDependency) ToArtifact(opts analysisOptions) artifact { Version: newVersion(d.Version), Exclusions: exclusions, Locations: locations, - Relationship: types.RelationshipIndirect, // default + Relationship: ftypes.RelationshipIndirect, // default } } diff --git a/pkg/dependency/parser/julia/manifest/parse.go b/pkg/dependency/parser/julia/manifest/parse.go index 685a54074629..061676d3f599 100644 --- a/pkg/dependency/parser/julia/manifest/parse.go +++ b/pkg/dependency/parser/julia/manifest/parse.go @@ -8,7 +8,7 @@ import ( "golang.org/x/exp/maps" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -27,11 +27,11 @@ type primitiveDependency struct { type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var oldDeps map[string][]primitiveDependency var primMan primitiveManifest var manMetadata toml.MetaData @@ -68,19 +68,19 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, pkgParser := naivePkgParser{r: r} lineNumIdx := pkgParser.parse() - var libs []types.Library - var deps []types.Dependency + var pkgs ftypes.Packages + var deps ftypes.Dependencies for name, manifestDeps := range man.Dependencies { for _, manifestDep := range manifestDeps { version := depVersion(manifestDep.Version, man.JuliaVersion) pkgID := manifestDep.UUID - lib := types.Library{ + pkg := ftypes.Package{ ID: pkgID, Name: name, Version: version, } if pos, ok := lineNumIdx[manifestDep.UUID]; ok { - lib.Locations = []types.Location{ + pkg.Locations = []ftypes.Location{ { StartLine: pos.start, EndLine: pos.end, @@ -88,19 +88,19 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, } } - libs = append(libs, lib) + pkgs = append(pkgs, pkg) if len(manifestDep.DependsOn) > 0 { - deps = append(deps, types.Dependency{ + deps = append(deps, ftypes.Dependency{ ID: pkgID, DependsOn: manifestDep.DependsOn, }) } } } - sort.Sort(types.Libraries(libs)) - sort.Sort(types.Dependencies(deps)) - return libs, deps, nil + sort.Sort(pkgs) + sort.Sort(deps) + return pkgs, deps, nil } // Returns the effective version of the `dep`. diff --git a/pkg/dependency/parser/julia/manifest/parse_test.go b/pkg/dependency/parser/julia/manifest/parse_test.go index 6eacf5a76133..229499c6cb71 100644 --- a/pkg/dependency/parser/julia/manifest/parse_test.go +++ b/pkg/dependency/parser/julia/manifest/parse_test.go @@ -8,26 +8,26 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { tests := []struct { name string file string // Test input file - want []types.Library - wantDeps []types.Dependency + want []ftypes.Package + wantDeps []ftypes.Dependency }{ { name: "Manifest v1.6", file: "testdata/primary/Manifest_v1.6.toml", - want: juliaV1_6Libs, + want: juliaV1_6Pkgs, wantDeps: juliaV1_6Deps, }, { name: "Manifest v1.8", file: "testdata/primary/Manifest_v1.8.toml", - want: juliaV1_8Libs, + want: juliaV1_8Pkgs, wantDeps: juliaV1_8Deps, }, { @@ -45,13 +45,13 @@ func TestParse(t *testing.T) { { name: "dep extensions v1.9", file: "testdata/dep_ext_v1.9/Manifest.toml", - want: juliaV1_9DepExtLibs, + want: juliaV1_9DepExtPkgs, wantDeps: nil, }, { name: "shadowed dep v1.9", file: "testdata/shadowed_dep_v1.9/Manifest.toml", - want: juliaV1_9ShadowedDepLibs, + want: juliaV1_9ShadowedDepPkgs, wantDeps: juliaV1_9ShadowedDepDeps, }, } @@ -61,13 +61,13 @@ func TestParse(t *testing.T) { f, err := os.Open(tt.file) require.NoError(t, err) - gotLibs, gotDeps, err := NewParser().Parse(f) + gotPkgs, gotDeps, err := NewParser().Parse(f) require.NoError(t, err) - sort.Sort(types.Libraries(tt.want)) - assert.Equal(t, tt.want, gotLibs) + sort.Sort(ftypes.Packages(tt.want)) + assert.Equal(t, tt.want, gotPkgs) if tt.wantDeps != nil { - sort.Sort(types.Dependencies(tt.wantDeps)) + sort.Sort(ftypes.Dependencies(tt.wantDeps)) assert.Equal(t, tt.wantDeps, gotDeps) } }) diff --git a/pkg/dependency/parser/julia/manifest/parse_testcase.go b/pkg/dependency/parser/julia/manifest/parse_testcase.go index 3055cc8d15a8..75602f7311f8 100644 --- a/pkg/dependency/parser/julia/manifest/parse_testcase.go +++ b/pkg/dependency/parser/julia/manifest/parse_testcase.go @@ -1,18 +1,18 @@ package julia -import "github.com/aquasecurity/trivy/pkg/dependency/types" +import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( - juliaV1_6Libs = []types.Library{ - {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", Name: "Dates", Version: "unknown", Locations: []types.Location{{StartLine: 3, EndLine: 5}}}, - {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", Name: "JSON", Version: "0.21.4", Locations: []types.Location{{StartLine: 7, EndLine: 11}}}, - {ID: "a63ad114-7e13-5084-954f-fe012c677804", Name: "Mmap", Version: "unknown", Locations: []types.Location{{StartLine: 13, EndLine: 14}}}, - {ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", Name: "Parsers", Version: "2.4.2", Locations: []types.Location{{StartLine: 16, EndLine: 20}}}, - {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", Name: "Printf", Version: "unknown", Locations: []types.Location{{StartLine: 22, EndLine: 24}}}, - {ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", Name: "Unicode", Version: "unknown", Locations: []types.Location{{StartLine: 26, EndLine: 27}}}, + juliaV1_6Pkgs = []ftypes.Package{ + {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", Name: "Dates", Version: "unknown", Locations: []ftypes.Location{{StartLine: 3, EndLine: 5}}}, + {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", Name: "JSON", Version: "0.21.4", Locations: []ftypes.Location{{StartLine: 7, EndLine: 11}}}, + {ID: "a63ad114-7e13-5084-954f-fe012c677804", Name: "Mmap", Version: "unknown", Locations: []ftypes.Location{{StartLine: 13, EndLine: 14}}}, + {ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", Name: "Parsers", Version: "2.4.2", Locations: []ftypes.Location{{StartLine: 16, EndLine: 20}}}, + {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", Name: "Printf", Version: "unknown", Locations: []ftypes.Location{{StartLine: 22, EndLine: 24}}}, + {ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", Name: "Unicode", Version: "unknown", Locations: []ftypes.Location{{StartLine: 26, EndLine: 27}}}, } - juliaV1_6Deps = []types.Dependency{ + juliaV1_6Deps = []ftypes.Dependency{ {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", DependsOn: []string{"de0858da-6303-5e67-8744-51eddeeeb8d7"}}, {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", DependsOn: []string{ "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", @@ -24,23 +24,23 @@ var ( {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", DependsOn: []string{"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"}}, } - juliaV1_8Libs = []types.Library{ - {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", Name: "Dates", Version: "1.8.5", Locations: []types.Location{{StartLine: 7, EndLine: 9}}}, - {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", Name: "JSON", Version: "0.21.4", Locations: []types.Location{{StartLine: 11, EndLine: 15}}}, - {ID: "a63ad114-7e13-5084-954f-fe012c677804", Name: "Mmap", Version: "1.8.5", Locations: []types.Location{{StartLine: 17, EndLine: 18}}}, - {ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", Name: "Parsers", Version: "2.5.10", Locations: []types.Location{{StartLine: 20, EndLine: 24}}}, - {ID: "aea7be01-6a6a-4083-8856-8a6e6704d82a", Name: "PrecompileTools", Version: "1.1.1", Locations: []types.Location{{StartLine: 26, EndLine: 30}}}, - {ID: "21216c6a-2e73-6563-6e65-726566657250", Name: "Preferences", Version: "1.4.0", Locations: []types.Location{{StartLine: 32, EndLine: 36}}}, - {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", Name: "Printf", Version: "1.8.5", Locations: []types.Location{{StartLine: 38, EndLine: 40}}}, - {ID: "9a3f8284-a2c9-5f02-9a11-845980a1fd5c", Name: "Random", Version: "1.8.5", Locations: []types.Location{{StartLine: 42, EndLine: 44}}}, - {ID: "ea8e919c-243c-51af-8825-aaa63cd721ce", Name: "SHA", Version: "0.7.0", Locations: []types.Location{{StartLine: 46, EndLine: 48}}}, - {ID: "9e88b42a-f829-5b0c-bbe9-9e923198166b", Name: "Serialization", Version: "1.8.5", Locations: []types.Location{{StartLine: 50, EndLine: 51}}}, - {ID: "fa267f1f-6049-4f14-aa54-33bafae1ed76", Name: "TOML", Version: "1.0.0", Locations: []types.Location{{StartLine: 53, EndLine: 56}}}, - {ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4", Name: "UUIDs", Version: "1.8.5", Locations: []types.Location{{StartLine: 58, EndLine: 60}}}, - {ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", Name: "Unicode", Version: "1.8.5", Locations: []types.Location{{StartLine: 62, EndLine: 63}}}, + juliaV1_8Pkgs = []ftypes.Package{ + {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", Name: "Dates", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 7, EndLine: 9}}}, + {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", Name: "JSON", Version: "0.21.4", Locations: []ftypes.Location{{StartLine: 11, EndLine: 15}}}, + {ID: "a63ad114-7e13-5084-954f-fe012c677804", Name: "Mmap", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 17, EndLine: 18}}}, + {ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", Name: "Parsers", Version: "2.5.10", Locations: []ftypes.Location{{StartLine: 20, EndLine: 24}}}, + {ID: "aea7be01-6a6a-4083-8856-8a6e6704d82a", Name: "PrecompileTools", Version: "1.1.1", Locations: []ftypes.Location{{StartLine: 26, EndLine: 30}}}, + {ID: "21216c6a-2e73-6563-6e65-726566657250", Name: "Preferences", Version: "1.4.0", Locations: []ftypes.Location{{StartLine: 32, EndLine: 36}}}, + {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", Name: "Printf", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 38, EndLine: 40}}}, + {ID: "9a3f8284-a2c9-5f02-9a11-845980a1fd5c", Name: "Random", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 42, EndLine: 44}}}, + {ID: "ea8e919c-243c-51af-8825-aaa63cd721ce", Name: "SHA", Version: "0.7.0", Locations: []ftypes.Location{{StartLine: 46, EndLine: 48}}}, + {ID: "9e88b42a-f829-5b0c-bbe9-9e923198166b", Name: "Serialization", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 50, EndLine: 51}}}, + {ID: "fa267f1f-6049-4f14-aa54-33bafae1ed76", Name: "TOML", Version: "1.0.0", Locations: []ftypes.Location{{StartLine: 53, EndLine: 56}}}, + {ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4", Name: "UUIDs", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 58, EndLine: 60}}}, + {ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", Name: "Unicode", Version: "1.8.5", Locations: []ftypes.Location{{StartLine: 62, EndLine: 63}}}, } - juliaV1_8Deps = []types.Dependency{ + juliaV1_8Deps = []ftypes.Dependency{ {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", DependsOn: []string{"de0858da-6303-5e67-8744-51eddeeeb8d7"}}, {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", DependsOn: []string{ "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", @@ -61,17 +61,17 @@ var ( {ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4", DependsOn: []string{"9a3f8284-a2c9-5f02-9a11-845980a1fd5c", "ea8e919c-243c-51af-8825-aaa63cd721ce"}}, } - juliaV1_9DepExtLibs = []types.Library{ - {ID: "621f4979-c628-5d54-868e-fcf4e3e8185c", Name: "AbstractFFTs", Version: "1.3.1", Locations: []types.Location{{StartLine: 7, EndLine: 10}}}, + juliaV1_9DepExtPkgs = []ftypes.Package{ + {ID: "621f4979-c628-5d54-868e-fcf4e3e8185c", Name: "AbstractFFTs", Version: "1.3.1", Locations: []ftypes.Location{{StartLine: 7, EndLine: 10}}}, } - juliaV1_9ShadowedDepLibs = []types.Library{ - {ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", Name: "A", Version: "1.9.0", Locations: []types.Location{{StartLine: 7, EndLine: 8}}}, - {ID: "f41f7b98-334e-11e9-1257-49272045fb24", Name: "B", Version: "1.9.0", Locations: []types.Location{{StartLine: 13, EndLine: 14}}}, - {ID: "edca9bc6-334e-11e9-3554-9595dbb4349c", Name: "B", Version: "1.9.0", Locations: []types.Location{{StartLine: 15, EndLine: 16}}}, + juliaV1_9ShadowedDepPkgs = []ftypes.Package{ + {ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", Name: "A", Version: "1.9.0", Locations: []ftypes.Location{{StartLine: 7, EndLine: 8}}}, + {ID: "f41f7b98-334e-11e9-1257-49272045fb24", Name: "B", Version: "1.9.0", Locations: []ftypes.Location{{StartLine: 13, EndLine: 14}}}, + {ID: "edca9bc6-334e-11e9-3554-9595dbb4349c", Name: "B", Version: "1.9.0", Locations: []ftypes.Location{{StartLine: 15, EndLine: 16}}}, } - juliaV1_9ShadowedDepDeps = []types.Dependency{ + juliaV1_9ShadowedDepDeps = []ftypes.Dependency{ {ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", DependsOn: []string{"f41f7b98-334e-11e9-1257-49272045fb24"}}, } ) diff --git a/pkg/dependency/parser/nodejs/npm/parse.go b/pkg/dependency/parser/nodejs/npm/parse.go index e98bf25bba6d..ec5d654e7469 100644 --- a/pkg/dependency/parser/nodejs/npm/parse.go +++ b/pkg/dependency/parser/nodejs/npm/parse.go @@ -15,7 +15,6 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -56,13 +55,13 @@ type Parser struct { logger *log.Logger } -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("npm"), } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var lockFile LockFile input, err := io.ReadAll(r) if err != nil { @@ -72,20 +71,20 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return nil, nil, xerrors.Errorf("decode error: %w", err) } - var libs []types.Library - var deps []types.Dependency + var pkgs []ftypes.Package + var deps []ftypes.Dependency if lockFile.LockfileVersion == 1 { - libs, deps = p.parseV1(lockFile.Dependencies, make(map[string]string)) + pkgs, deps = p.parseV1(lockFile.Dependencies, make(map[string]string)) } else { - libs, deps = p.parseV2(lockFile.Packages) + pkgs, deps = p.parseV2(lockFile.Packages) } - return utils.UniqueLibraries(libs), uniqueDeps(deps), nil + return utils.UniquePackages(pkgs), uniqueDeps(deps), nil } -func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types.Dependency) { - libs := make(map[string]types.Library, len(packages)-1) - var deps []types.Dependency +func (p *Parser) parseV2(packages map[string]Package) ([]ftypes.Package, []ftypes.Dependency) { + pkgs := make(map[string]ftypes.Package, len(packages)-1) + var deps []ftypes.Dependency // Resolve links first // https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json#packages @@ -116,51 +115,51 @@ func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types. } pkgID := packageID(pkgName, pkg.Version) - location := types.Location{ + location := ftypes.Location{ StartLine: pkg.StartLine, EndLine: pkg.EndLine, } - var ref types.ExternalRef + var ref ftypes.ExternalRef if pkg.Resolved != "" { - ref = types.ExternalRef{ - Type: types.RefOther, + ref = ftypes.ExternalRef{ + Type: ftypes.RefOther, URL: pkg.Resolved, } } - pkgIndirect := isIndirectLib(pkgPath, directDeps) + pkgIndirect := isIndirectPkg(pkgPath, directDeps) - // There are cases when similar libraries use same dependencies + // There are cases when similar packages use same dependencies // we need to add location for each these dependencies - if savedLib, ok := libs[pkgID]; ok { - savedLib.Dev = savedLib.Dev && pkg.Dev - if savedLib.Relationship == types.RelationshipIndirect && !pkgIndirect { - savedLib.Relationship = types.RelationshipDirect + if savedPkg, ok := pkgs[pkgID]; ok { + savedPkg.Dev = savedPkg.Dev && pkg.Dev + if savedPkg.Relationship == ftypes.RelationshipIndirect && !pkgIndirect { + savedPkg.Relationship = ftypes.RelationshipDirect } - if ref.URL != "" && !slices.Contains(savedLib.ExternalReferences, ref) { - savedLib.ExternalReferences = append(savedLib.ExternalReferences, ref) - sortExternalReferences(savedLib.ExternalReferences) + if ref.URL != "" && !slices.Contains(savedPkg.ExternalReferences, ref) { + savedPkg.ExternalReferences = append(savedPkg.ExternalReferences, ref) + sortExternalReferences(savedPkg.ExternalReferences) } - savedLib.Locations = append(savedLib.Locations, location) - sort.Sort(savedLib.Locations) + savedPkg.Locations = append(savedPkg.Locations, location) + sort.Sort(savedPkg.Locations) - libs[pkgID] = savedLib + pkgs[pkgID] = savedPkg continue } - lib := types.Library{ + newPkg := ftypes.Package{ ID: pkgID, Name: pkgName, Version: pkg.Version, - Relationship: lo.Ternary(pkgIndirect, types.RelationshipIndirect, types.RelationshipDirect), + Relationship: lo.Ternary(pkgIndirect, ftypes.RelationshipIndirect, ftypes.RelationshipDirect), Dev: pkg.Dev, - ExternalReferences: lo.Ternary(ref.URL != "", []types.ExternalRef{ref}, nil), - Locations: []types.Location{location}, + ExternalReferences: lo.Ternary(ref.URL != "", []ftypes.ExternalRef{ref}, nil), + Locations: []ftypes.Location{location}, } - libs[pkgID] = lib + pkgs[pkgID] = newPkg // npm builds graph using optional deps. e.g.: // └─┬ watchpack@1.7.5 @@ -179,15 +178,15 @@ func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types. } if len(dependsOn) > 0 { - deps = append(deps, types.Dependency{ - ID: lib.ID, + deps = append(deps, ftypes.Dependency{ + ID: newPkg.ID, DependsOn: dependsOn, }) } } - return maps.Values(libs), deps + return maps.Values(pkgs), deps } // for local package npm uses links. e.g.: @@ -271,73 +270,73 @@ func findDependsOn(pkgPath, depName string, packages map[string]Package) (string return "", xerrors.Errorf("can't find dependsOn for %s", depName) } -func (p *Parser) parseV1(dependencies map[string]Dependency, versions map[string]string) ([]types.Library, []types.Dependency) { +func (p *Parser) parseV1(dependencies map[string]Dependency, versions map[string]string) ([]ftypes.Package, []ftypes.Dependency) { // Update package name and version mapping. for pkgName, dep := range dependencies { // Overwrite the existing package version so that the nested version can take precedence. versions[pkgName] = dep.Version } - var libs []types.Library - var deps []types.Dependency + var pkgs []ftypes.Package + var deps []ftypes.Dependency for pkgName, dep := range dependencies { - lib := types.Library{ + pkg := ftypes.Package{ ID: packageID(pkgName, dep.Version), Name: pkgName, Version: dep.Version, Dev: dep.Dev, - Relationship: types.RelationshipUnknown, // lockfile v1 schema doesn't have information about direct dependencies - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, // lockfile v1 schema doesn't have information about direct dependencies + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: dep.Resolved, }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: dep.StartLine, EndLine: dep.EndLine, }, }, } - libs = append(libs, lib) + pkgs = append(pkgs, pkg) dependsOn := make([]string, 0, len(dep.Requires)) - for libName, requiredVer := range dep.Requires { + for pName, requiredVer := range dep.Requires { // Try to resolve the version with nested dependencies first - if resolvedDep, ok := dep.Dependencies[libName]; ok { - libID := packageID(libName, resolvedDep.Version) - dependsOn = append(dependsOn, libID) + if resolvedDep, ok := dep.Dependencies[pName]; ok { + pkgID := packageID(pName, resolvedDep.Version) + dependsOn = append(dependsOn, pkgID) continue } // Try to resolve the version with the higher level dependencies - if ver, ok := versions[libName]; ok { - dependsOn = append(dependsOn, packageID(libName, ver)) + if ver, ok := versions[pName]; ok { + dependsOn = append(dependsOn, packageID(pName, ver)) continue } // It should not reach here. p.logger.Warn("Unable to resolve the version", - log.String("name", libName), log.String("version", requiredVer)) + log.String("name", pName), log.String("version", requiredVer)) } if len(dependsOn) > 0 { - deps = append(deps, types.Dependency{ - ID: packageID(lib.Name, lib.Version), + deps = append(deps, ftypes.Dependency{ + ID: packageID(pkg.Name, pkg.Version), DependsOn: dependsOn, }) } if dep.Dependencies != nil { // Recursion - childLibs, childDeps := p.parseV1(dep.Dependencies, maps.Clone(versions)) - libs = append(libs, childLibs...) + childpkgs, childDeps := p.parseV1(dep.Dependencies, maps.Clone(versions)) + pkgs = append(pkgs, childpkgs...) deps = append(deps, childDeps...) } } - return libs, deps + return pkgs, deps } func (p *Parser) pkgNameFromPath(pkgPath string) string { @@ -354,8 +353,8 @@ func (p *Parser) pkgNameFromPath(pkgPath string) string { return pkgPath } -func uniqueDeps(deps []types.Dependency) []types.Dependency { - var uniqDeps []types.Dependency +func uniqueDeps(deps []ftypes.Dependency) []ftypes.Dependency { + var uniqDeps ftypes.Dependencies unique := make(map[string]struct{}) for _, dep := range deps { @@ -367,14 +366,14 @@ func uniqueDeps(deps []types.Dependency) []types.Dependency { } } - sort.Sort(types.Dependencies(uniqDeps)) + sort.Sort(uniqDeps) return uniqDeps } -func isIndirectLib(pkgPath string, directDeps map[string]struct{}) bool { +func isIndirectPkg(pkgPath string, directDeps map[string]struct{}) bool { // A project can contain 2 different versions of the same dependency. // e.g. `node_modules/string-width/node_modules/strip-ansi` and `node_modules/string-ansi` - // direct dependencies always have root path (`node_modules/`) + // direct dependencies always have root path (`node_modules/`) if _, ok := directDeps[pkgPath]; ok { return false } @@ -411,7 +410,7 @@ func packageID(name, version string) string { return dependency.ID(ftypes.Npm, name, version) } -func sortExternalReferences(refs []types.ExternalRef) { +func sortExternalReferences(refs []ftypes.ExternalRef) { sort.Slice(refs, func(i, j int) bool { if refs[i].Type != refs[j].Type { return refs[i].Type < refs[j].Type diff --git a/pkg/dependency/parser/nodejs/npm/parse_test.go b/pkg/dependency/parser/nodejs/npm/parse_test.go index 786fe643dfde..68f5d1e8491f 100644 --- a/pkg/dependency/parser/nodejs/npm/parse_test.go +++ b/pkg/dependency/parser/nodejs/npm/parse_test.go @@ -7,44 +7,44 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { tests := []struct { name string file string // Test input file - want []types.Library - wantDeps []types.Dependency + want []ftypes.Package + wantDeps []ftypes.Dependency }{ { name: "lock version v1", file: "testdata/package-lock_v1.json", - want: npmV1Libs, + want: npmV1Pkgs, wantDeps: npmDeps, }, { name: "lock version v2", file: "testdata/package-lock_v2.json", - want: npmV2Libs, + want: npmV2Pkgs, wantDeps: npmDeps, }, { name: "lock version v3", file: "testdata/package-lock_v3.json", - want: npmV2Libs, + want: npmV2Pkgs, wantDeps: npmDeps, }, { name: "lock version v3 with workspace", file: "testdata/package-lock_v3_with_workspace.json", - want: npmV3WithWorkspaceLibs, + want: npmV3WithWorkspacePkgs, wantDeps: npmV3WithWorkspaceDeps, }, { name: "lock file v3 contains same dev and non-dev dependencies", file: "testdata/package-lock_v3_with-same-dev-and-non-dev.json", - want: npmV3WithSameDevAndNonDevLibs, + want: npmV3WithSameDevAndNonDevPkgs, wantDeps: npmV3WithSameDevAndNonDevDeps, }, { diff --git a/pkg/dependency/parser/nodejs/npm/parse_testcase.go b/pkg/dependency/parser/nodejs/npm/parse_testcase.go index d5f87cd33cc0..01dcac6711f9 100644 --- a/pkg/dependency/parser/nodejs/npm/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/npm/parse_testcase.go @@ -1,6 +1,6 @@ package npm -import "github.com/aquasecurity/trivy/pkg/dependency/types" +import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( // docker run --name node --rm -it node@sha256:51dd437f31812df71108b81385e2945071ec813d5815fa3403855669c8f3432b sh @@ -10,22 +10,22 @@ var ( // npm install --save-dev debug@2.5.2 // npm install --save-optional promise // npm i --lockfile-version 1 - // libraries are filled manually + // packages are filled manually - npmV1Libs = []types.Library{ + npmV1Pkgs = []ftypes.Package{ { ID: "@babel/helper-string-parser@7.19.4", Name: "@babel/helper-string-parser", Version: "7.19.4", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 7, EndLine: 11, @@ -37,14 +37,14 @@ var ( Name: "asap", Version: "2.0.6", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 12, EndLine: 17, @@ -56,14 +56,14 @@ var ( Name: "body-parser", Version: "1.18.3", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 18, EndLine: 49, @@ -75,14 +75,14 @@ var ( Name: "bytes", Version: "3.0.0", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 50, EndLine: 54, @@ -94,14 +94,14 @@ var ( Name: "content-type", Version: "1.0.5", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 55, EndLine: 59, @@ -113,14 +113,14 @@ var ( Name: "debug", Version: "2.5.2", Dev: true, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 60, EndLine: 76, @@ -132,14 +132,14 @@ var ( Name: "debug", Version: "2.6.9", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 35, EndLine: 42, @@ -155,14 +155,14 @@ var ( Name: "depd", Version: "1.1.2", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 77, EndLine: 81, @@ -174,14 +174,14 @@ var ( Name: "ee-first", Version: "1.1.1", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 82, EndLine: 86, @@ -193,14 +193,14 @@ var ( Name: "encodeurl", Version: "1.0.2", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 87, EndLine: 91, @@ -212,14 +212,14 @@ var ( Name: "escape-html", Version: "1.0.3", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 92, EndLine: 96, @@ -231,14 +231,14 @@ var ( Name: "finalhandler", Version: "1.1.1", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 97, EndLine: 125, @@ -250,14 +250,14 @@ var ( Name: "http-errors", Version: "1.6.3", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 126, EndLine: 136, @@ -269,14 +269,14 @@ var ( Name: "iconv-lite", Version: "0.4.23", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 137, EndLine: 144, @@ -288,14 +288,14 @@ var ( Name: "inherits", Version: "2.0.3", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 145, EndLine: 149, @@ -307,14 +307,14 @@ var ( Name: "media-typer", Version: "0.3.0", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 150, EndLine: 154, @@ -326,14 +326,14 @@ var ( Name: "mime-db", Version: "1.52.0", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 155, EndLine: 159, @@ -345,14 +345,14 @@ var ( Name: "mime-types", Version: "2.1.35", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 160, EndLine: 167, @@ -364,14 +364,14 @@ var ( Name: "ms", Version: "0.7.2", Dev: true, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 69, EndLine: 74, @@ -383,14 +383,14 @@ var ( Name: "ms", Version: "1.0.0", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 168, EndLine: 172, @@ -402,14 +402,14 @@ var ( Name: "ms", Version: "2.0.0", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 43, EndLine: 47, @@ -425,14 +425,14 @@ var ( Name: "on-finished", Version: "2.3.0", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 173, EndLine: 180, @@ -444,14 +444,14 @@ var ( Name: "parseurl", Version: "1.3.3", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 181, EndLine: 185, @@ -463,14 +463,14 @@ var ( Name: "promise", Version: "8.3.0", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 186, EndLine: 194, @@ -482,14 +482,14 @@ var ( Name: "qs", Version: "6.5.2", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 195, EndLine: 199, @@ -501,14 +501,14 @@ var ( Name: "raw-body", Version: "2.3.3", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 200, EndLine: 210, @@ -520,14 +520,14 @@ var ( Name: "safer-buffer", Version: "2.1.2", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 211, EndLine: 215, @@ -539,14 +539,14 @@ var ( Name: "setprototypeof", Version: "1.1.0", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 216, EndLine: 220, @@ -558,14 +558,14 @@ var ( Name: "statuses", Version: "1.4.0", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 221, EndLine: 225, @@ -577,14 +577,14 @@ var ( Name: "type-is", Version: "1.6.18", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 226, EndLine: 234, @@ -596,14 +596,14 @@ var ( Name: "unpipe", Version: "1.0.0", Dev: false, - Relationship: types.RelationshipUnknown, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipUnknown, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 235, EndLine: 239, @@ -613,7 +613,7 @@ var ( } // dependencies are filled manually - npmDeps = []types.Dependency{ + npmDeps = []ftypes.Dependency{ { ID: "body-parser@1.18.3", DependsOn: []string{ @@ -694,25 +694,25 @@ var ( // ... and // npm i --lockfile-version 2 - // same as npmV1Libs but change `Indirect` field to false for `body-parser@1.18.3`, `finalhandler@1.1.1`, `@babel/helper-string-parser@7.19.4`, `promise@8.3.0` and `ms@1.0.0` libraries. + // same as npmV1Pkgs but change `Indirect` field to false for `body-parser@1.18.3`, `finalhandler@1.1.1`, `@babel/helper-string-parser@7.19.4`, `promise@8.3.0` and `ms@1.0.0` packages. // also need to get locations from `packages` struct // --- lockfile version 3 --- // npm i --lockfile-version 3 - // same as npmV2Libs. - npmV2Libs = []types.Library{ + // same as npmV2Pkgs. + npmV2Pkgs = []ftypes.Package{ { ID: "@babel/helper-string-parser@7.19.4", Name: "@babel/helper-string-parser", Version: "7.19.4", Dev: false, - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 24, EndLine: 31, @@ -724,14 +724,14 @@ var ( Name: "body-parser", Version: "1.18.3", Dev: false, - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 38, EndLine: 57, @@ -743,14 +743,14 @@ var ( Name: "debug", Version: "2.5.2", Dev: true, - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 87, EndLine: 95, @@ -762,14 +762,14 @@ var ( Name: "finalhandler", Version: "1.1.1", Dev: false, - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 128, EndLine: 144, @@ -781,14 +781,14 @@ var ( Name: "ms", Version: "1.0.0", Dev: false, - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 215, EndLine: 219, @@ -800,14 +800,14 @@ var ( Name: "promise", Version: "8.3.0", Dev: false, - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 239, EndLine: 247, @@ -819,14 +819,14 @@ var ( Name: "asap", Version: "2.0.6", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 32, EndLine: 37, @@ -838,14 +838,14 @@ var ( Name: "bytes", Version: "3.0.0", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 71, EndLine: 78, @@ -857,14 +857,14 @@ var ( Name: "content-type", Version: "1.0.5", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 79, EndLine: 86, @@ -876,14 +876,14 @@ var ( Name: "debug", Version: "2.6.9", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 58, EndLine: 65, @@ -899,14 +899,14 @@ var ( Name: "depd", Version: "1.1.2", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 102, EndLine: 109, @@ -918,14 +918,14 @@ var ( Name: "ee-first", Version: "1.1.1", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 110, EndLine: 114, @@ -937,14 +937,14 @@ var ( Name: "encodeurl", Version: "1.0.2", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 115, EndLine: 122, @@ -956,14 +956,14 @@ var ( Name: "escape-html", Version: "1.0.3", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 123, EndLine: 127, @@ -975,14 +975,14 @@ var ( Name: "http-errors", Version: "1.6.3", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 158, EndLine: 171, @@ -994,14 +994,14 @@ var ( Name: "iconv-lite", Version: "0.4.23", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 172, EndLine: 182, @@ -1013,14 +1013,14 @@ var ( Name: "inherits", Version: "2.0.3", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 183, EndLine: 187, @@ -1032,14 +1032,14 @@ var ( Name: "media-typer", Version: "0.3.0", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 188, EndLine: 195, @@ -1051,14 +1051,14 @@ var ( Name: "mime-db", Version: "1.52.0", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 196, EndLine: 203, @@ -1070,14 +1070,14 @@ var ( Name: "mime-types", Version: "2.1.35", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 204, EndLine: 214, @@ -1089,14 +1089,14 @@ var ( Name: "ms", Version: "0.7.2", Dev: true, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 96, EndLine: 101, @@ -1108,14 +1108,14 @@ var ( Name: "ms", Version: "2.0.0", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 66, EndLine: 70, @@ -1131,14 +1131,14 @@ var ( Name: "on-finished", Version: "2.3.0", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 220, EndLine: 230, @@ -1150,14 +1150,14 @@ var ( Name: "parseurl", Version: "1.3.3", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 231, EndLine: 238, @@ -1169,14 +1169,14 @@ var ( Name: "qs", Version: "6.5.2", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 248, EndLine: 255, @@ -1188,14 +1188,14 @@ var ( Name: "raw-body", Version: "2.3.3", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 256, EndLine: 269, @@ -1207,14 +1207,14 @@ var ( Name: "safer-buffer", Version: "2.1.2", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 270, EndLine: 274, @@ -1226,14 +1226,14 @@ var ( Name: "setprototypeof", Version: "1.1.0", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 275, EndLine: 279, @@ -1245,14 +1245,14 @@ var ( Name: "statuses", Version: "1.4.0", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 280, EndLine: 287, @@ -1264,14 +1264,14 @@ var ( Name: "type-is", Version: "1.6.18", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 288, EndLine: 299, @@ -1283,14 +1283,14 @@ var ( Name: "unpipe", Version: "1.0.0", Dev: false, - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 300, EndLine: 307, @@ -1312,20 +1312,20 @@ var ( // grep -v "functions/func1" ./package.json > tmpfile && mv tmpfile ./package.json // sed -i 's/functions\/nested_func/functions\/*/g' package.json // npm update - // libraries are filled manually - npmV3WithWorkspaceLibs = []types.Library{ + // packages are filled manually + npmV3WithWorkspacePkgs = []ftypes.Package{ { ID: "debug@2.5.2", Name: "debug", Version: "2.5.2", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 39, EndLine: 46, @@ -1336,14 +1336,14 @@ var ( ID: "function1", Name: "function1", Version: "", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "functions/func1", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 18, EndLine: 23, @@ -1354,14 +1354,14 @@ var ( ID: "nested_func@1.0.0", Name: "nested_func", Version: "1.0.0", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "functions/nested_func", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 24, EndLine: 30, @@ -1372,14 +1372,14 @@ var ( ID: "debug@2.6.9", Name: "debug", Version: "2.6.9", - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 31, EndLine: 38, @@ -1390,14 +1390,14 @@ var ( ID: "ms@0.7.2", Name: "ms", Version: "0.7.2", - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 47, EndLine: 51, @@ -1408,14 +1408,14 @@ var ( ID: "ms@2.0.0", Name: "ms", Version: "2.0.0", - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 56, EndLine: 60, @@ -1424,7 +1424,7 @@ var ( }, } - npmV3WithWorkspaceDeps = []types.Dependency{ + npmV3WithWorkspaceDeps = []ftypes.Dependency{ { ID: "debug@2.5.2", DependsOn: []string{"ms@0.7.2"}, @@ -1448,20 +1448,20 @@ var ( // npm init --force // npm init -w ./functions/func1 --force // npm install --save debug@2.6.9 -w func1 - // libraries are filled manually - npmV3WithoutRootDepsField = []types.Library{ + // packages are filled manually + npmV3WithoutRootDepsField = []ftypes.Package{ { ID: "func1@1.0.0", Name: "func1", Version: "1.0.0", - Relationship: types.RelationshipDirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipDirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "functions/func1", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 15, EndLine: 21, @@ -1472,14 +1472,14 @@ var ( ID: "debug@2.6.9", Name: "debug", Version: "2.6.9", - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 22, EndLine: 29, @@ -1490,14 +1490,14 @@ var ( ID: "ms@2.0.0", Name: "ms", Version: "2.0.0", - Relationship: types.RelationshipIndirect, - ExternalReferences: []types.ExternalRef{ + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 34, EndLine: 38, @@ -1506,7 +1506,7 @@ var ( }, } - npmV3WithoutRootDepsFieldDeps = []types.Dependency{ + npmV3WithoutRootDepsFieldDeps = []ftypes.Dependency{ { ID: "debug@2.6.9", DependsOn: []string{"ms@2.0.0"}, @@ -1517,20 +1517,20 @@ var ( }, } - npmV3WithSameDevAndNonDevLibs = []types.Library{ + npmV3WithSameDevAndNonDevPkgs = []ftypes.Package{ { ID: "fsevents@1.2.9", Name: "fsevents", Version: "1.2.9", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, Dev: true, - ExternalReferences: []types.ExternalRef{ + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 18, EndLine: 37, @@ -1541,15 +1541,15 @@ var ( ID: "minimist@0.0.8", Name: "minimist", Version: "0.0.8", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, Dev: false, - ExternalReferences: []types.ExternalRef{ + ExternalReferences: []ftypes.ExternalRef{ { - Type: types.RefOther, + Type: ftypes.RefOther, URL: "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", }, }, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 38, EndLine: 43, @@ -1564,9 +1564,9 @@ var ( ID: "mkdirp@0.5.1", Name: "mkdirp", Version: "0.5.1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, Dev: true, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 44, EndLine: 55, @@ -1577,9 +1577,9 @@ var ( ID: "node-pre-gyp@0.12.0", Name: "node-pre-gyp", Version: "0.12.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, Dev: true, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 56, EndLine: 67, @@ -1588,7 +1588,7 @@ var ( }, } - npmV3WithSameDevAndNonDevDeps = []types.Dependency{ + npmV3WithSameDevAndNonDevDeps = []ftypes.Dependency{ { ID: "fsevents@1.2.9", DependsOn: []string{"node-pre-gyp@0.12.0"}, diff --git a/pkg/dependency/parser/nodejs/packagejson/parse.go b/pkg/dependency/parser/nodejs/packagejson/parse.go index 19a53679f2d0..80d11e2193bf 100644 --- a/pkg/dependency/parser/nodejs/packagejson/parse.go +++ b/pkg/dependency/parser/nodejs/packagejson/parse.go @@ -9,7 +9,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) @@ -26,7 +25,7 @@ type packageJSON struct { } type Package struct { - types.Library + ftypes.Package Dependencies map[string]string OptionalDependencies map[string]string DevDependencies map[string]string @@ -57,11 +56,11 @@ func (p *Parser) Parse(r io.Reader) (Package, error) { } return Package{ - Library: types.Library{ - ID: id, - Name: pkgJSON.Name, - Version: pkgJSON.Version, - License: parseLicense(pkgJSON.License), + Package: ftypes.Package{ + ID: id, + Name: pkgJSON.Name, + Version: pkgJSON.Version, + Licenses: parseLicense(pkgJSON.License), }, Dependencies: pkgJSON.Dependencies, OptionalDependencies: pkgJSON.OptionalDependencies, @@ -70,17 +69,21 @@ func (p *Parser) Parse(r io.Reader) (Package, error) { }, nil } -func parseLicense(val interface{}) string { +func parseLicense(val interface{}) []string { // the license isn't always a string, check for legacy struct if not string switch v := val.(type) { case string: - return v + if v != "" { + return []string{v} + } case map[string]interface{}: if license, ok := v["type"]; ok { - return license.(string) + if s, ok := license.(string); ok && s != "" { + return []string{s} + } } } - return "" + return nil } // parseWorkspaces returns slice of workspaces diff --git a/pkg/dependency/parser/nodejs/packagejson/parse_test.go b/pkg/dependency/parser/nodejs/packagejson/parse_test.go index 97a0027d22ef..1896186129ae 100644 --- a/pkg/dependency/parser/nodejs/packagejson/parse_test.go +++ b/pkg/dependency/parser/nodejs/packagejson/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { @@ -27,11 +27,11 @@ func TestParse(t *testing.T) { // npm install --save promise jquery // npm ls | grep -E -o "\S+@\S+" | awk -F@ 'NR>0 {printf("{\""$1"\", \""$2"\"},\n")}' want: packagejson.Package{ - Library: types.Library{ - ID: "bootstrap@5.0.2", - Name: "bootstrap", - Version: "5.0.2", - License: "MIT", + Package: ftypes.Package{ + ID: "bootstrap@5.0.2", + Name: "bootstrap", + Version: "5.0.2", + Licenses: []string{"MIT"}, }, Dependencies: map[string]string{ "js-tokens": "^4.0.0", @@ -53,11 +53,11 @@ func TestParse(t *testing.T) { name: "happy path - legacy license", inputFile: "testdata/legacy_package.json", want: packagejson.Package{ - Library: types.Library{ - ID: "angular@4.1.2", - Name: "angular", - Version: "4.1.2", - License: "ISC", + Package: ftypes.Package{ + ID: "angular@4.1.2", + Name: "angular", + Version: "4.1.2", + Licenses: []string{"ISC"}, }, Dependencies: map[string]string{}, DevDependencies: map[string]string{ @@ -70,7 +70,7 @@ func TestParse(t *testing.T) { name: "happy path - version doesn't exist", inputFile: "testdata/without_version_package.json", want: packagejson.Package{ - Library: types.Library{ + Package: ftypes.Package{ ID: "", Name: "angular", }, @@ -80,7 +80,7 @@ func TestParse(t *testing.T) { name: "happy path - workspace as struct", inputFile: "testdata/workspace_as_map_package.json", want: packagejson.Package{ - Library: types.Library{ + Package: ftypes.Package{ ID: "example@1.0.0", Name: "example", Version: "1.0.0", @@ -109,8 +109,8 @@ func TestParse(t *testing.T) { name: "without name and version", inputFile: "testdata/without_name_and_version_package.json", want: packagejson.Package{ - Library: types.Library{ - License: "MIT", + Package: ftypes.Package{ + Licenses: []string{"MIT"}, }, }, }, @@ -162,7 +162,8 @@ func TestIsValidName(t *testing.T) { { name: "test@package", want: false, - }, { + }, + { name: "test?package", want: false, }, diff --git a/pkg/dependency/parser/nodejs/pnpm/parse.go b/pkg/dependency/parser/nodejs/pnpm/parse.go index b11646851462..92fdc6131744 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse.go @@ -11,7 +11,6 @@ import ( "github.com/aquasecurity/go-version/pkg/semver" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -47,7 +46,7 @@ func NewParser() *Parser { } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var lockFile LockFile if err := yaml.NewDecoder(r).Decode(&lockFile); err != nil { return nil, nil, xerrors.Errorf("decode error: %w", err) @@ -58,14 +57,14 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return nil, nil, nil } - libs, deps := p.parse(lockVer, lockFile) + pkgs, deps := p.parse(lockVer, lockFile) - return libs, deps, nil + return pkgs, deps, nil } -func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]types.Library, []types.Dependency) { - var libs []types.Library - var deps []types.Dependency +func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]ftypes.Package, []ftypes.Dependency) { + var pkgs []ftypes.Package + var deps []ftypes.Dependency // Dependency path is a path to a dependency with a specific set of resolved subdependencies. // cf. https://github.com/pnpm/spec/blob/ad27a225f81d9215becadfa540ef05fa4ad6dd60/dependency-path.md @@ -90,22 +89,22 @@ func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]types.Library, []t dependencies = append(dependencies, packageID(depName, depVer)) } - libs = append(libs, types.Library{ + pkgs = append(pkgs, ftypes.Package{ ID: pkgID, Name: name, Version: version, - Relationship: lo.Ternary(isDirectLib(name, lockFile.Dependencies), types.RelationshipDirect, types.RelationshipIndirect), + Relationship: lo.Ternary(isDirectPkg(name, lockFile.Dependencies), ftypes.RelationshipDirect, ftypes.RelationshipIndirect), }) if len(dependencies) > 0 { - deps = append(deps, types.Dependency{ + deps = append(deps, ftypes.Dependency{ ID: pkgID, DependsOn: dependencies, }) } } - return libs, deps + return pkgs, deps } func (p *Parser) parseLockfileVersion(lockFile LockFile) float64 { @@ -179,7 +178,7 @@ func (p *Parser) parseDepPath(depPath, versionSep string) (string, string) { return name, version } -func isDirectLib(name string, directDeps map[string]interface{}) bool { +func isDirectPkg(name string, directDeps map[string]interface{}) bool { _, ok := directDeps[name] return ok } diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_test.go b/pkg/dependency/parser/nodejs/pnpm/parse_test.go index 606bc37de54d..b5d8816516fe 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_test.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_test.go @@ -3,21 +3,20 @@ package pnpm import ( "os" "sort" - "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { tests := []struct { name string file string // Test input file - want []types.Library - wantDeps []types.Dependency + want []ftypes.Package + wantDeps []ftypes.Dependency }{ { name: "normal", @@ -65,39 +64,25 @@ func TestParse(t *testing.T) { got, deps, err := NewParser().Parse(f) require.NoError(t, err) - sortLibs(got) - sortLibs(tt.want) - + sort.Sort(ftypes.Packages(got)) + sort.Sort(ftypes.Packages(tt.want)) assert.Equal(t, tt.want, got) + if tt.wantDeps != nil { - sortDeps(deps) - sortDeps(tt.wantDeps) + sort.Sort(ftypes.Dependencies(deps)) + sort.Sort(ftypes.Dependencies(tt.wantDeps)) + for _, dep := range deps { + sort.Strings(dep.DependsOn) + } + for _, dep := range tt.wantDeps { + sort.Strings(dep.DependsOn) + } assert.Equal(t, tt.wantDeps, deps) } }) } } -func sortDeps(deps []types.Dependency) { - sort.Slice(deps, func(i, j int) bool { - return strings.Compare(deps[i].ID, deps[j].ID) < 0 - }) - - for i := range deps { - sort.Strings(deps[i].DependsOn) - } -} - -func sortLibs(libs []types.Library) { - sort.Slice(libs, func(i, j int) bool { - ret := strings.Compare(libs[i].Name, libs[j].Name) - if ret == 0 { - return libs[i].Version < libs[j].Version - } - return ret < 0 - }) -} - func Test_parsePackage(t *testing.T) { tests := []struct { name string diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go index d41ac4c0cee7..45eb7a7202e7 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go @@ -1,33 +1,33 @@ package pnpm -import "github.com/aquasecurity/trivy/pkg/dependency/types" +import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( // docker run --name node --rm -it node:16-alpine sh // npm install -g pnpm // pnpm add promise jquery - // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u - pnpmNormal = []types.Library{ + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: ftypes.RelationshipIndirect},\n")}' | sort -u + pnpmNormal = []ftypes.Package{ { ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "jquery@3.6.0", Name: "jquery", Version: "3.6.0", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "promise@8.1.0", Name: "promise", Version: "8.1.0", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, } - pnpmNormalDeps = []types.Dependency{ + pnpmNormalDeps = []ftypes.Dependency{ { ID: "promise@8.1.0", DependsOn: []string{"asap@2.0.6"}, @@ -38,46 +38,46 @@ var ( // npm install -g pnpm // pnpm add react redux // pnpm add -D mocha - // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u - pnpmWithDev = []types.Library{ + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: ftypes.RelationshipIndirect},\n")}' | sort -u + pnpmWithDev = []ftypes.Package{ { ID: "@babel/runtime@7.18.3", Name: "@babel/runtime", Version: "7.18.3", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "react@18.1.0", Name: "react", Version: "18.1.0", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "redux@4.2.0", Name: "redux", Version: "4.2.0", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "regenerator-runtime@0.13.9", Name: "regenerator-runtime", Version: "0.13.9", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, } - pnpmWithDevDeps = []types.Dependency{ + pnpmWithDevDeps = []ftypes.Dependency{ { ID: "@babel/runtime@7.18.3", DependsOn: []string{"regenerator-runtime@0.13.9"}, @@ -100,346 +100,346 @@ var ( // npm install -g pnpm // pnpm add react redux lodash request chalk commander // pnpm add -D mocha - // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u - pnpmMany = []types.Library{ + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: ftypes.RelationshipIndirect},\n")}' | sort -u + pnpmMany = []ftypes.Package{ { ID: "@babel/runtime@7.18.3", Name: "@babel/runtime", Version: "7.18.3", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "ajv@6.12.6", Name: "ajv", Version: "6.12.6", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "asn1@0.2.6", Name: "asn1", Version: "0.2.6", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "assert-plus@1.0.0", Name: "assert-plus", Version: "1.0.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "asynckit@0.4.0", Name: "asynckit", Version: "0.4.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "aws-sign2@0.7.0", Name: "aws-sign2", Version: "0.7.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "aws4@1.11.0", Name: "aws4", Version: "1.11.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "bcrypt-pbkdf@1.0.2", Name: "bcrypt-pbkdf", Version: "1.0.2", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "caseless@0.12.0", Name: "caseless", Version: "0.12.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "chalk@5.0.1", Name: "chalk", Version: "5.0.1", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "combined-stream@1.0.8", Name: "combined-stream", Version: "1.0.8", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "commander@9.3.0", Name: "commander", Version: "9.3.0", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "core-util-is@1.0.2", Name: "core-util-is", Version: "1.0.2", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "dashdash@1.14.1", Name: "dashdash", Version: "1.14.1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "delayed-stream@1.0.0", Name: "delayed-stream", Version: "1.0.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "ecc-jsbn@0.1.2", Name: "ecc-jsbn", Version: "0.1.2", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "extend@3.0.2", Name: "extend", Version: "3.0.2", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "extsprintf@1.3.0", Name: "extsprintf", Version: "1.3.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "fast-deep-equal@3.1.3", Name: "fast-deep-equal", Version: "3.1.3", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "fast-json-stable-stringify@2.1.0", Name: "fast-json-stable-stringify", Version: "2.1.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "forever-agent@0.6.1", Name: "forever-agent", Version: "0.6.1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "form-data@2.3.3", Name: "form-data", Version: "2.3.3", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "getpass@0.1.7", Name: "getpass", Version: "0.1.7", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "har-schema@2.0.0", Name: "har-schema", Version: "2.0.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "har-validator@5.1.5", Name: "har-validator", Version: "5.1.5", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "http-signature@1.2.0", Name: "http-signature", Version: "1.2.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "is-typedarray@1.0.0", Name: "is-typedarray", Version: "1.0.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "isstream@0.1.2", Name: "isstream", Version: "0.1.2", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "jsbn@0.1.1", Name: "jsbn", Version: "0.1.1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "json-schema-traverse@0.4.1", Name: "json-schema-traverse", Version: "0.4.1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "json-schema@0.4.0", Name: "json-schema", Version: "0.4.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "json-stringify-safe@5.0.1", Name: "json-stringify-safe", Version: "5.0.1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "jsprim@1.4.2", Name: "jsprim", Version: "1.4.2", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "lodash@4.17.21", Name: "lodash", Version: "4.17.21", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "mime-db@1.52.0", Name: "mime-db", Version: "1.52.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "mime-types@2.1.35", Name: "mime-types", Version: "2.1.35", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "oauth-sign@0.9.0", Name: "oauth-sign", Version: "0.9.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "performance-now@2.1.0", Name: "performance-now", Version: "2.1.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "psl@1.8.0", Name: "psl", Version: "1.8.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "punycode@2.1.1", Name: "punycode", Version: "2.1.1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "qs@6.5.3", Name: "qs", Version: "6.5.3", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "react@18.1.0", Name: "react", Version: "18.1.0", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "redux@4.2.0", Name: "redux", Version: "4.2.0", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "regenerator-runtime@0.13.9", Name: "regenerator-runtime", Version: "0.13.9", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "request@2.88.2", Name: "request", Version: "2.88.2", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "safe-buffer@5.2.1", Name: "safe-buffer", Version: "5.2.1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "safer-buffer@2.1.2", Name: "safer-buffer", Version: "2.1.2", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "sshpk@1.17.0", Name: "sshpk", Version: "1.17.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "tough-cookie@2.5.0", Name: "tough-cookie", Version: "2.5.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "tunnel-agent@0.6.0", Name: "tunnel-agent", Version: "0.6.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "tweetnacl@0.14.5", Name: "tweetnacl", Version: "0.14.5", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "uri-js@4.4.1", Name: "uri-js", Version: "4.4.1", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "uuid@3.4.0", Name: "uuid", Version: "3.4.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "verror@1.10.0", Name: "verror", Version: "1.10.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, } - pnpmManyDeps = []types.Dependency{ + pnpmManyDeps = []ftypes.Dependency{ { ID: "@babel/runtime@7.18.3", DependsOn: []string{"regenerator-runtime@0.13.9"}, @@ -610,48 +610,48 @@ var ( // pnpm update // pnpm add https://github.com/debug-js/debug/tarball/4.3.4 // pnpm add https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5 - // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipDirect},\n")}' | sort -u + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: ftypes.RelationshipDirect},\n")}' | sort -u // manually update `Indirect` fields - pnpmArchives = []types.Library{ + pnpmArchives = []ftypes.Package{ { ID: "asynckit@0.4.0", Name: "asynckit", Version: "0.4.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "debug@4.3.4", Name: "debug", Version: "4.3.4", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "is-negative@2.0.1", Name: "is-negative", Version: "2.0.1", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "lodash@4.17.21", Name: "lodash", Version: "4.17.21", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "ms@2.1.2", Name: "ms", Version: "2.1.2", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "package1@1.0.0", Name: "package1", Version: "1.0.0", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, } - pnpmArchivesDeps = []types.Dependency{ + pnpmArchivesDeps = []ftypes.Dependency{ { ID: "debug@4.3.4", DependsOn: []string{"ms@2.1.2"}, @@ -665,7 +665,7 @@ var ( // docker run --name node --rm -it node@sha256:710a2c192ca426e03e4f3ec1869e5c29db855eb6969b74e6c50fd270ffccd3f1 sh // npm install -g pnpm@8.5.1 // pnpm add promise@8.1.0 jquery@3.6.0 - // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: ftypes.RelationshipIndirect},\n")}' | sort -u pnpmV6 = pnpmNormal pnpmV6Deps = pnpmNormalDeps @@ -673,46 +673,46 @@ var ( // npm install -g pnpm@8.5.1 // pnpm add react@18.1.0 redux@4.2.0 // pnpm add -D mocha@10.0.0 - // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: types.RelationshipIndirect},\n")}' | sort -u - pnpmV6WithDev = []types.Library{ + // pnpm list --prod --depth 10 | grep -E -o "\S+\s+[0-9]+(\.[0-9]+)+$" | awk '{printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\", Relationship: ftypes.RelationshipIndirect},\n")}' | sort -u + pnpmV6WithDev = []ftypes.Package{ { ID: "@babel/runtime@7.22.3", Name: "@babel/runtime", Version: "7.22.3", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, { ID: "react@18.1.0", Name: "react", Version: "18.1.0", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "redux@4.2.0", Name: "redux", Version: "4.2.0", - Relationship: types.RelationshipDirect, + Relationship: ftypes.RelationshipDirect, }, { ID: "regenerator-runtime@0.13.11", Name: "regenerator-runtime", Version: "0.13.11", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, }, } - pnpmV6WithDevDeps = []types.Dependency{ + pnpmV6WithDevDeps = []ftypes.Dependency{ { ID: "@babel/runtime@7.22.3", DependsOn: []string{"regenerator-runtime@0.13.11"}, diff --git a/pkg/dependency/parser/nodejs/yarn/parse.go b/pkg/dependency/parser/nodejs/yarn/parse.go index d1d195d59641..813554730bea 100644 --- a/pkg/dependency/parser/nodejs/yarn/parse.go +++ b/pkg/dependency/parser/nodejs/yarn/parse.go @@ -11,7 +11,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -31,7 +30,7 @@ type Library struct { Patterns []string Name string Version string - Location types.Location + Location ftypes.Location } type Dependency struct { Pattern string @@ -128,14 +127,14 @@ func ignoreProtocol(protocol string) bool { return false } -func parseResults(patternIDs map[string]string, dependsOn map[string][]string) (deps []types.Dependency) { +func parseResults(patternIDs map[string]string, dependsOn map[string][]string) (deps []ftypes.Dependency) { // find dependencies by patterns - for libID, depPatterns := range dependsOn { + for pkgID, depPatterns := range dependsOn { depIDs := lo.Map(depPatterns, func(pattern string, index int) string { return patternIDs[pattern] }) - deps = append(deps, types.Dependency{ - ID: libID, + deps = append(deps, ftypes.Dependency{ + ID: pkgID, DependsOn: depIDs, }) } @@ -146,7 +145,7 @@ type Parser struct { logger *log.Logger } -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("yarn"), } @@ -236,7 +235,7 @@ func (p *Parser) parseBlock(block []byte, lineNum int) (lib Library, deps []stri return Library{}, nil, scanner.LineNum(lineNum), nil } - lib.Location = types.Location{ + lib.Location = ftypes.Location{ StartLine: lineNum + emptyLines, EndLine: scanner.LineNum(lineNum), } @@ -270,9 +269,9 @@ func parseDependency(line string) (string, error) { } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { lineNumber := 1 - var libs []types.Library + var pkgs []ftypes.Package // patternIDs holds mapping between patterns and library IDs // e.g. ajv@^6.5.5 => ajv@6.10.0 @@ -291,21 +290,21 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, continue } - libID := packageID(lib.Name, lib.Version) - libs = append(libs, types.Library{ - ID: libID, + pkgID := packageID(lib.Name, lib.Version) + pkgs = append(pkgs, ftypes.Package{ + ID: pkgID, Name: lib.Name, Version: lib.Version, - Locations: []types.Location{lib.Location}, + Locations: []ftypes.Location{lib.Location}, }) for _, pattern := range lib.Patterns { // e.g. // combined-stream@^1.0.6 => combined-stream@1.0.8 // combined-stream@~1.0.6 => combined-stream@1.0.8 - patternIDs[pattern] = libID + patternIDs[pattern] = pkgID if len(deps) > 0 { - dependsOn[libID] = deps + dependsOn[pkgID] = deps } } } @@ -317,7 +316,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // Replace dependency patterns with library IDs // e.g. ajv@^6.5.5 => ajv@6.10.0 deps := parseResults(patternIDs, dependsOn) - return libs, deps, nil + return pkgs, deps, nil } func packageID(name, version string) string { diff --git a/pkg/dependency/parser/nodejs/yarn/parse_test.go b/pkg/dependency/parser/nodejs/yarn/parse_test.go index 90ce497ed9d8..275514351954 100644 --- a/pkg/dependency/parser/nodejs/yarn/parse_test.go +++ b/pkg/dependency/parser/nodejs/yarn/parse_test.go @@ -3,13 +3,12 @@ package yarn import ( "os" "sort" - "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParsePattern(t *testing.T) { @@ -255,8 +254,8 @@ func TestParse(t *testing.T) { tests := []struct { name string file string // Test input file - want []types.Library - wantDeps []types.Dependency + want []ftypes.Package + wantDeps []ftypes.Dependency }{ { name: "happy", @@ -305,10 +304,10 @@ func TestParse(t *testing.T) { got, deps, err := NewParser().Parse(f) require.NoError(t, err) - sortLibs(got) - sortLibs(tt.want) - + sortPkgs(got) + sortPkgs(tt.want) assert.Equal(t, tt.want, got) + if tt.wantDeps != nil { sortDeps(deps) sortDeps(tt.wantDeps) @@ -318,31 +317,16 @@ func TestParse(t *testing.T) { } } -func sortDeps(deps []types.Dependency) { - sort.Slice(deps, func(i, j int) bool { - return strings.Compare(deps[i].ID, deps[j].ID) < 0 - }) - - for i := range deps { - sort.Strings(deps[i].DependsOn) +func sortPkgs(pkgs ftypes.Packages) { + sort.Sort(pkgs) + for _, pkg := range pkgs { + sort.Sort(pkg.Locations) } } -func sortLibs(libs []types.Library) { - sort.Slice(libs, func(i, j int) bool { - ret := strings.Compare(libs[i].Name, libs[j].Name) - if ret == 0 { - return libs[i].Version < libs[j].Version - } - return ret < 0 - }) - for _, lib := range libs { - sortLocations(lib.Locations) +func sortDeps(deps ftypes.Dependencies) { + sort.Sort(deps) + for _, dep := range deps { + sort.Strings(dep.DependsOn) } } - -func sortLocations(locs []types.Location) { - sort.Slice(locs, func(i, j int) bool { - return locs[i].StartLine < locs[j].StartLine - }) -} diff --git a/pkg/dependency/parser/nodejs/yarn/parse_testcase.go b/pkg/dependency/parser/nodejs/yarn/parse_testcase.go index e9d48c246f29..8d829fb195cd 100644 --- a/pkg/dependency/parser/nodejs/yarn/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/yarn/parse_testcase.go @@ -1,30 +1,30 @@ package yarn -import "github.com/aquasecurity/trivy/pkg/dependency/types" +import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( - yarnHappy = []types.Library{ - {ID: "@babel/helper-regex@7.4.4", Name: "@babel/helper-regex", Version: "7.4.4", Locations: []types.Location{{StartLine: 4, EndLine: 9}}}, - {ID: "ansi-regex@2.1.1", Name: "ansi-regex", Version: "2.1.1", Locations: []types.Location{{StartLine: 11, EndLine: 14}}}, - {ID: "ansi-regex@3.0.0", Name: "ansi-regex", Version: "3.0.0", Locations: []types.Location{{StartLine: 16, EndLine: 19}}}, - {ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Locations: []types.Location{{StartLine: 21, EndLine: 24}}}, - {ID: "inherits@2.0.3", Name: "inherits", Version: "2.0.3", Locations: []types.Location{{StartLine: 26, EndLine: 29}}}, - {ID: "is-fullwidth-code-point@2.0.0", Name: "is-fullwidth-code-point", Version: "2.0.0", Locations: []types.Location{{StartLine: 31, EndLine: 34}}}, - {ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []types.Location{{StartLine: 41, EndLine: 44}}}, - {ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Locations: []types.Location{{StartLine: 36, EndLine: 39}}}, - {ID: "lodash@4.17.11", Name: "lodash", Version: "4.17.11", Locations: []types.Location{{StartLine: 46, EndLine: 49}}}, - {ID: "promise@8.0.3", Name: "promise", Version: "8.0.3", Locations: []types.Location{{StartLine: 51, EndLine: 56}}}, - {ID: "safe-buffer@5.1.2", Name: "safe-buffer", Version: "5.1.2", Locations: []types.Location{{StartLine: 58, EndLine: 61}}}, - {ID: "safer-buffer@2.1.2", Name: "safer-buffer", Version: "2.1.2", Locations: []types.Location{{StartLine: 63, EndLine: 66}}}, - {ID: "statuses@1.5.0", Name: "statuses", Version: "1.5.0", Locations: []types.Location{{StartLine: 68, EndLine: 71}}}, - {ID: "string-width@2.1.1", Name: "string-width", Version: "2.1.1", Locations: []types.Location{{StartLine: 73, EndLine: 79}}}, - {ID: "strip-ansi@3.0.1", Name: "strip-ansi", Version: "3.0.1", Locations: []types.Location{{StartLine: 82, EndLine: 87}}}, - {ID: "strip-ansi@4.0.0", Name: "strip-ansi", Version: "4.0.0", Locations: []types.Location{{StartLine: 89, EndLine: 94}}}, - {ID: "whatwg-fetch@3.0.0", Name: "whatwg-fetch", Version: "3.0.0", Locations: []types.Location{{StartLine: 96, EndLine: 99}}}, - {ID: "wide-align@1.1.3", Name: "wide-align", Version: "1.1.3", Locations: []types.Location{{StartLine: 101, EndLine: 106}}}, + yarnHappy = []ftypes.Package{ + {ID: "@babel/helper-regex@7.4.4", Name: "@babel/helper-regex", Version: "7.4.4", Locations: []ftypes.Location{{StartLine: 4, EndLine: 9}}}, + {ID: "ansi-regex@2.1.1", Name: "ansi-regex", Version: "2.1.1", Locations: []ftypes.Location{{StartLine: 11, EndLine: 14}}}, + {ID: "ansi-regex@3.0.0", Name: "ansi-regex", Version: "3.0.0", Locations: []ftypes.Location{{StartLine: 16, EndLine: 19}}}, + {ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Locations: []ftypes.Location{{StartLine: 21, EndLine: 24}}}, + {ID: "inherits@2.0.3", Name: "inherits", Version: "2.0.3", Locations: []ftypes.Location{{StartLine: 26, EndLine: 29}}}, + {ID: "is-fullwidth-code-point@2.0.0", Name: "is-fullwidth-code-point", Version: "2.0.0", Locations: []ftypes.Location{{StartLine: 31, EndLine: 34}}}, + {ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []ftypes.Location{{StartLine: 41, EndLine: 44}}}, + {ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Locations: []ftypes.Location{{StartLine: 36, EndLine: 39}}}, + {ID: "lodash@4.17.11", Name: "lodash", Version: "4.17.11", Locations: []ftypes.Location{{StartLine: 46, EndLine: 49}}}, + {ID: "promise@8.0.3", Name: "promise", Version: "8.0.3", Locations: []ftypes.Location{{StartLine: 51, EndLine: 56}}}, + {ID: "safe-buffer@5.1.2", Name: "safe-buffer", Version: "5.1.2", Locations: []ftypes.Location{{StartLine: 58, EndLine: 61}}}, + {ID: "safer-buffer@2.1.2", Name: "safer-buffer", Version: "2.1.2", Locations: []ftypes.Location{{StartLine: 63, EndLine: 66}}}, + {ID: "statuses@1.5.0", Name: "statuses", Version: "1.5.0", Locations: []ftypes.Location{{StartLine: 68, EndLine: 71}}}, + {ID: "string-width@2.1.1", Name: "string-width", Version: "2.1.1", Locations: []ftypes.Location{{StartLine: 73, EndLine: 79}}}, + {ID: "strip-ansi@3.0.1", Name: "strip-ansi", Version: "3.0.1", Locations: []ftypes.Location{{StartLine: 82, EndLine: 87}}}, + {ID: "strip-ansi@4.0.0", Name: "strip-ansi", Version: "4.0.0", Locations: []ftypes.Location{{StartLine: 89, EndLine: 94}}}, + {ID: "whatwg-fetch@3.0.0", Name: "whatwg-fetch", Version: "3.0.0", Locations: []ftypes.Location{{StartLine: 96, EndLine: 99}}}, + {ID: "wide-align@1.1.3", Name: "wide-align", Version: "1.1.3", Locations: []ftypes.Location{{StartLine: 101, EndLine: 106}}}, } - yarnHappyDeps = []types.Dependency{ + yarnHappyDeps = []ftypes.Dependency{ { ID: "@babel/helper-regex@7.4.4", DependsOn: []string{ @@ -64,26 +64,26 @@ var ( }, } - yarnV2Happy = []types.Library{ - {ID: "@types/color-name@1.1.1", Name: "@types/color-name", Version: "1.1.1", Locations: []types.Location{{StartLine: 8, EndLine: 13}}}, - {ID: "abbrev@1.1.1", Name: "abbrev", Version: "1.1.1", Locations: []types.Location{{StartLine: 15, EndLine: 20}}}, - {ID: "ansi-styles@3.2.1", Name: "ansi-styles", Version: "3.2.1", Locations: []types.Location{{StartLine: 22, EndLine: 29}}}, - {ID: "ansi-styles@4.2.1", Name: "ansi-styles", Version: "4.2.1", Locations: []types.Location{{StartLine: 31, EndLine: 39}}}, - {ID: "assert-plus@1.0.0", Name: "assert-plus", Version: "1.0.0", Locations: []types.Location{{StartLine: 41, EndLine: 46}}}, - {ID: "async@3.2.0", Name: "async", Version: "3.2.0", Locations: []types.Location{{StartLine: 48, EndLine: 53}}}, - {ID: "color-convert@1.9.3", Name: "color-convert", Version: "1.9.3", Locations: []types.Location{{StartLine: 63, EndLine: 70}}}, - {ID: "color-convert@2.0.1", Name: "color-convert", Version: "2.0.1", Locations: []types.Location{{StartLine: 72, EndLine: 79}}}, - {ID: "color-name@1.1.3", Name: "color-name", Version: "1.1.3", Locations: []types.Location{{StartLine: 81, EndLine: 86}}}, - {ID: "color-name@1.1.4", Name: "color-name", Version: "1.1.4", Locations: []types.Location{{StartLine: 88, EndLine: 93}}}, - {ID: "ipaddr.js@1.9.1", Name: "ipaddr.js", Version: "1.9.1", Locations: []types.Location{{StartLine: 104, EndLine: 109}}}, - {ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Locations: []types.Location{{StartLine: 111, EndLine: 116}}}, - {ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", Locations: []types.Location{{StartLine: 118, EndLine: 127}}}, - {ID: "node-gyp@7.1.0", Name: "node-gyp", Version: "7.1.0", Locations: []types.Location{{StartLine: 129, EndLine: 136}}}, - {ID: "once@1.4.0", Name: "once", Version: "1.4.0", Locations: []types.Location{{StartLine: 138, EndLine: 145}}}, - {ID: "wrappy@1.0.2", Name: "wrappy", Version: "1.0.2", Locations: []types.Location{{StartLine: 147, EndLine: 152}}}, + yarnV2Happy = []ftypes.Package{ + {ID: "@types/color-name@1.1.1", Name: "@types/color-name", Version: "1.1.1", Locations: []ftypes.Location{{StartLine: 8, EndLine: 13}}}, + {ID: "abbrev@1.1.1", Name: "abbrev", Version: "1.1.1", Locations: []ftypes.Location{{StartLine: 15, EndLine: 20}}}, + {ID: "ansi-styles@3.2.1", Name: "ansi-styles", Version: "3.2.1", Locations: []ftypes.Location{{StartLine: 22, EndLine: 29}}}, + {ID: "ansi-styles@4.2.1", Name: "ansi-styles", Version: "4.2.1", Locations: []ftypes.Location{{StartLine: 31, EndLine: 39}}}, + {ID: "assert-plus@1.0.0", Name: "assert-plus", Version: "1.0.0", Locations: []ftypes.Location{{StartLine: 41, EndLine: 46}}}, + {ID: "async@3.2.0", Name: "async", Version: "3.2.0", Locations: []ftypes.Location{{StartLine: 48, EndLine: 53}}}, + {ID: "color-convert@1.9.3", Name: "color-convert", Version: "1.9.3", Locations: []ftypes.Location{{StartLine: 63, EndLine: 70}}}, + {ID: "color-convert@2.0.1", Name: "color-convert", Version: "2.0.1", Locations: []ftypes.Location{{StartLine: 72, EndLine: 79}}}, + {ID: "color-name@1.1.3", Name: "color-name", Version: "1.1.3", Locations: []ftypes.Location{{StartLine: 81, EndLine: 86}}}, + {ID: "color-name@1.1.4", Name: "color-name", Version: "1.1.4", Locations: []ftypes.Location{{StartLine: 88, EndLine: 93}}}, + {ID: "ipaddr.js@1.9.1", Name: "ipaddr.js", Version: "1.9.1", Locations: []ftypes.Location{{StartLine: 104, EndLine: 109}}}, + {ID: "js-tokens@4.0.0", Name: "js-tokens", Version: "4.0.0", Locations: []ftypes.Location{{StartLine: 111, EndLine: 116}}}, + {ID: "loose-envify@1.4.0", Name: "loose-envify", Version: "1.4.0", Locations: []ftypes.Location{{StartLine: 118, EndLine: 127}}}, + {ID: "node-gyp@7.1.0", Name: "node-gyp", Version: "7.1.0", Locations: []ftypes.Location{{StartLine: 129, EndLine: 136}}}, + {ID: "once@1.4.0", Name: "once", Version: "1.4.0", Locations: []ftypes.Location{{StartLine: 138, EndLine: 145}}}, + {ID: "wrappy@1.0.2", Name: "wrappy", Version: "1.0.2", Locations: []ftypes.Location{{StartLine: 147, EndLine: 152}}}, } - yarnV2HappyDeps = []types.Dependency{ + yarnV2HappyDeps = []ftypes.Dependency{ { ID: "ansi-styles@3.2.1", DependsOn: []string{ @@ -123,13 +123,13 @@ var ( }, } - yarnWithLocal = []types.Library{ - {ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Locations: []types.Location{{StartLine: 5, EndLine: 8}}}, - {ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []types.Location{{StartLine: 10, EndLine: 13}}}, - {ID: "promise@8.0.3", Name: "promise", Version: "8.0.3", Locations: []types.Location{{StartLine: 15, EndLine: 20}}}, + yarnWithLocal = []ftypes.Package{ + {ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Locations: []ftypes.Location{{StartLine: 5, EndLine: 8}}}, + {ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []ftypes.Location{{StartLine: 10, EndLine: 13}}}, + {ID: "promise@8.0.3", Name: "promise", Version: "8.0.3", Locations: []ftypes.Location{{StartLine: 15, EndLine: 20}}}, } - yarnWithLocalDeps = []types.Dependency{ + yarnWithLocalDeps = []ftypes.Dependency{ { ID: "promise@8.0.3", DependsOn: []string{ @@ -138,20 +138,20 @@ var ( }, } - yarnWithNpm = []types.Library{ - {ID: "jquery@3.6.0", Name: "jquery", Version: "3.6.0", Locations: []types.Location{{StartLine: 1, EndLine: 4}}}, + yarnWithNpm = []ftypes.Package{ + {ID: "jquery@3.6.0", Name: "jquery", Version: "3.6.0", Locations: []ftypes.Location{{StartLine: 1, EndLine: 4}}}, } - yarnBadProtocol = []types.Library{ - {ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []types.Location{{StartLine: 4, EndLine: 7}}}, + yarnBadProtocol = []ftypes.Package{ + {ID: "jquery@3.4.1", Name: "jquery", Version: "3.4.1", Locations: []ftypes.Location{{StartLine: 4, EndLine: 7}}}, } - yarnV2DepsWithProtocol = []types.Library{ - {ID: "debug@4.3.4", Name: "debug", Version: "4.3.4", Locations: []types.Location{{StartLine: 16, EndLine: 26}}}, - {ID: "ms@2.1.2", Name: "ms", Version: "2.1.2", Locations: []types.Location{{StartLine: 28, EndLine: 33}}}, + yarnV2DepsWithProtocol = []ftypes.Package{ + {ID: "debug@4.3.4", Name: "debug", Version: "4.3.4", Locations: []ftypes.Location{{StartLine: 16, EndLine: 26}}}, + {ID: "ms@2.1.2", Name: "ms", Version: "2.1.2", Locations: []ftypes.Location{{StartLine: 28, EndLine: 33}}}, } - yarnV2DepsWithProtocolDeps = []types.Dependency{ + yarnV2DepsWithProtocolDeps = []ftypes.Dependency{ { ID: "debug@4.3.4", DependsOn: []string{"ms@2.1.2"}, diff --git a/pkg/dependency/parser/nuget/config/parse.go b/pkg/dependency/parser/nuget/config/parse.go index 6a43d30ddf94..561a21843207 100644 --- a/pkg/dependency/parser/nuget/config/parse.go +++ b/pkg/dependency/parser/nuget/config/parse.go @@ -6,7 +6,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -25,29 +25,27 @@ type config struct { type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var cfgData config if err := xml.NewDecoder(r).Decode(&cfgData); err != nil { return nil, nil, xerrors.Errorf("failed to decode .config file: %w", err) } - var libs []types.Library + var pkgs []ftypes.Package for _, pkg := range cfgData.Packages { if pkg.ID == "" || pkg.DevDependency { continue } - lib := types.Library{ + pkgs = append(pkgs, ftypes.Package{ Name: pkg.ID, Version: pkg.Version, - } - - libs = append(libs, lib) + }) } - return utils.UniqueLibraries(libs), nil, nil + return utils.UniquePackages(pkgs), nil, nil } diff --git a/pkg/dependency/parser/nuget/config/parse_test.go b/pkg/dependency/parser/nuget/config/parse_test.go index 864246bc9c44..b216fd81d703 100644 --- a/pkg/dependency/parser/nuget/config/parse_test.go +++ b/pkg/dependency/parser/nuget/config/parse_test.go @@ -8,20 +8,20 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/config" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { tests := []struct { name string // Test input file inputFile string - want []types.Library + want []ftypes.Package wantErr string }{ { name: "Config", inputFile: "testdata/packages.config", - want: []types.Library{ + want: []ftypes.Package{ {Name: "Newtonsoft.Json", Version: "6.0.4"}, {Name: "Microsoft.AspNet.WebApi", Version: "5.2.2"}, }, @@ -29,7 +29,7 @@ func TestParse(t *testing.T) { { name: "with development dependency", inputFile: "testdata/dev_dependency.config", - want: []types.Library{ + want: []ftypes.Package{ {Name: "Newtonsoft.Json", Version: "8.0.3"}, }, }, diff --git a/pkg/dependency/parser/nuget/lock/parse.go b/pkg/dependency/parser/nuget/lock/parse.go index d5bb54f0e21d..7852680f5749 100644 --- a/pkg/dependency/parser/nuget/lock/parse.go +++ b/pkg/dependency/parser/nuget/lock/parse.go @@ -9,7 +9,6 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -31,11 +30,11 @@ type Dependency struct { type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var lockFile LockFile input, err := io.ReadAll(r) if err != nil { @@ -45,7 +44,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return nil, nil, xerrors.Errorf("failed to decode packages.lock.json: %w", err) } - var libs []types.Library + var pkgs []ftypes.Package depsMap := make(map[string][]string) for _, targetContent := range lockFile.Targets { for packageName, packageContent := range targetContent { @@ -56,19 +55,19 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, depId := packageID(packageName, packageContent.Resolved) - lib := types.Library{ + pkg := ftypes.Package{ ID: depId, Name: packageName, Version: packageContent.Resolved, - Relationship: lo.Ternary(packageContent.Type == "Direct", types.RelationshipDirect, types.RelationshipIndirect), - Locations: []types.Location{ + Relationship: lo.Ternary(packageContent.Type == "Direct", ftypes.RelationshipDirect, ftypes.RelationshipIndirect), + Locations: []ftypes.Location{ { StartLine: packageContent.StartLine, EndLine: packageContent.EndLine, }, }, } - libs = append(libs, lib) + pkgs = append(pkgs, pkg) var dependsOn []string @@ -86,16 +85,16 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, } } - var deps []types.Dependency + var deps []ftypes.Dependency for depId, dependsOn := range depsMap { - dep := types.Dependency{ + dep := ftypes.Dependency{ ID: depId, DependsOn: dependsOn, } deps = append(deps, dep) } - return utils.UniqueLibraries(libs), deps, nil + return utils.UniquePackages(pkgs), deps, nil } // UnmarshalJSONWithMetadata needed to detect start and end lines of deps diff --git a/pkg/dependency/parser/nuget/lock/parse_test.go b/pkg/dependency/parser/nuget/lock/parse_test.go index 04ddb22244df..561eed5dfc88 100644 --- a/pkg/dependency/parser/nuget/lock/parse_test.go +++ b/pkg/dependency/parser/nuget/lock/parse_test.go @@ -10,14 +10,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { vectors := []struct { file string // Test input file - want []types.Library - wantDeps []types.Dependency + want []ftypes.Package + wantDeps []ftypes.Dependency }{ { file: "testdata/packages_lock_simple.json", @@ -49,21 +49,8 @@ func TestParse(t *testing.T) { got, deps, err := NewParser().Parse(f) require.NoError(t, err) - sort.Slice(got, func(i, j int) bool { - ret := strings.Compare(got[i].Name, got[j].Name) - if ret == 0 { - return got[i].Version < got[j].Version - } - return ret < 0 - }) - - sort.Slice(v.want, func(i, j int) bool { - ret := strings.Compare(v.want[i].Name, v.want[j].Name) - if ret == 0 { - return v.want[i].Version < v.want[j].Version - } - return ret < 0 - }) + sort.Sort(ftypes.Packages(got)) + sort.Sort(ftypes.Packages(v.want)) assert.Equal(t, v.want, got) @@ -76,7 +63,7 @@ func TestParse(t *testing.T) { } } -func sortDeps(deps []types.Dependency) { +func sortDeps(deps []ftypes.Dependency) { sort.Slice(deps, func(i, j int) bool { return strings.Compare(deps[i].ID, deps[j].ID) < 0 }) diff --git a/pkg/dependency/parser/nuget/lock/parse_testcase.go b/pkg/dependency/parser/nuget/lock/parse_testcase.go index a499fc158e3e..d528e2d2a421 100644 --- a/pkg/dependency/parser/nuget/lock/parse_testcase.go +++ b/pkg/dependency/parser/nuget/lock/parse_testcase.go @@ -1,6 +1,6 @@ package lock -import "github.com/aquasecurity/trivy/pkg/dependency/types" +import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( // docker run --rm -i -t mcr.microsoft.com/dotnet/sdk:latest @@ -11,13 +11,13 @@ var ( // dotnet add package NuGet.Frameworks // dotnet restore --use-lock-file // cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"' - nuGetSimple = []types.Library{ + nuGetSimple = []ftypes.Package{ { ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", Version: "12.0.3", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 5, EndLine: 10, @@ -28,8 +28,8 @@ var ( ID: "NuGet.Frameworks@5.7.0", Name: "NuGet.Frameworks", Version: "5.7.0", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 11, EndLine: 16, @@ -37,7 +37,7 @@ var ( }, }, } - nuGetSimpleDeps []types.Dependency + nuGetSimpleDeps []ftypes.Dependency // docker run --rm -i -t mcr.microsoft.com/dotnet/sdk:latest // apt -y update && apt -y install jq @@ -47,13 +47,13 @@ var ( // dotnet add package NuGet.Frameworks // dotnet restore --use-lock-file // cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"' - nuGetSubDependencies = []types.Library{ + nuGetSubDependencies = []ftypes.Package{ { ID: "Microsoft.Extensions.ApiDescription.Server@3.0.0", Name: "Microsoft.Extensions.ApiDescription.Server", Version: "3.0.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 29, EndLine: 33, @@ -64,8 +64,8 @@ var ( ID: "Microsoft.OpenApi@1.1.4", Name: "Microsoft.OpenApi", Version: "1.1.4", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 34, EndLine: 38, @@ -76,8 +76,8 @@ var ( ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", Version: "12.0.3", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 5, EndLine: 10, @@ -88,8 +88,8 @@ var ( ID: "NuGet.Frameworks@5.7.0", Name: "NuGet.Frameworks", Version: "5.7.0", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 11, EndLine: 16, @@ -100,8 +100,8 @@ var ( ID: "Swashbuckle.AspNetCore@5.5.1", Name: "Swashbuckle.AspNetCore", Version: "5.5.1", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 17, EndLine: 28, @@ -112,8 +112,8 @@ var ( ID: "Swashbuckle.AspNetCore.Swagger@5.5.1", Name: "Swashbuckle.AspNetCore.Swagger", Version: "5.5.1", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 39, EndLine: 46, @@ -124,8 +124,8 @@ var ( ID: "Swashbuckle.AspNetCore.SwaggerGen@5.5.1", Name: "Swashbuckle.AspNetCore.SwaggerGen", Version: "5.5.1", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 47, EndLine: 54, @@ -136,8 +136,8 @@ var ( ID: "Swashbuckle.AspNetCore.SwaggerUI@5.5.1", Name: "Swashbuckle.AspNetCore.SwaggerUI", Version: "5.5.1", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 55, EndLine: 59, @@ -145,7 +145,7 @@ var ( }, }, } - nuGetSubDependenciesDeps = []types.Dependency{ + nuGetSubDependenciesDeps = []ftypes.Dependency{ { ID: "Swashbuckle.AspNetCore.Swagger@5.5.1", DependsOn: []string{"Microsoft.OpenApi@1.1.4"}, @@ -173,13 +173,13 @@ var ( // dotnet add package AWSSDK.Core // dotnet restore --use-lock-file // cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"' - nuGetLegacy = []types.Library{ + nuGetLegacy = []ftypes.Package{ { ID: "AWSSDK.Core@3.5.1.30", Name: "AWSSDK.Core", Version: "3.5.1.30", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 5, EndLine: 10, @@ -190,8 +190,8 @@ var ( ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", Version: "12.0.3", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 11, EndLine: 16, @@ -199,7 +199,7 @@ var ( }, }, } - nuGetLegacyDeps []types.Dependency + nuGetLegacyDeps []ftypes.Dependency // docker run --rm -i -t mcr.microsoft.com/dotnet/sdk:latest // apt -y update && apt -y install jq @@ -210,13 +210,13 @@ var ( // dotnet restore --use-lock-file // dotnet add package AWSSDK.Core // cat packages.lock.json | jq -rc '.dependencies[] | keys[] as $k | "{\"\($k)\", \"\(.[$k] | .resolved)\", \"\"},"' | sort -u - nuGetMultiTarget = []types.Library{ + nuGetMultiTarget = []ftypes.Package{ { ID: "AWSSDK.Core@3.5.1.30", Name: "AWSSDK.Core", Version: "3.5.1.30", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 5, EndLine: 10, @@ -243,8 +243,8 @@ var ( ID: "Microsoft.Bcl.AsyncInterfaces@1.1.0", Name: "Microsoft.Bcl.AsyncInterfaces", Version: "1.1.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 460, EndLine: 467, @@ -255,8 +255,8 @@ var ( ID: "Microsoft.CSharp@4.3.0", Name: "Microsoft.CSharp", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 138, EndLine: 147, @@ -267,8 +267,8 @@ var ( ID: "Microsoft.NETCore.Platforms@1.1.0", Name: "Microsoft.NETCore.Platforms", Version: "1.1.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 148, EndLine: 152, @@ -283,8 +283,8 @@ var ( ID: "Microsoft.NETCore.Targets@1.1.0", Name: "Microsoft.NETCore.Targets", Version: "1.1.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 153, EndLine: 157, @@ -295,8 +295,8 @@ var ( ID: "Microsoft.NETFramework.ReferenceAssemblies@1.0.0", Name: "Microsoft.NETFramework.ReferenceAssemblies", Version: "1.0.0", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 11, EndLine: 19, @@ -315,8 +315,8 @@ var ( ID: "Microsoft.NETFramework.ReferenceAssemblies.net20@1.0.0", Name: "Microsoft.NETFramework.ReferenceAssemblies.net20", Version: "1.0.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 26, EndLine: 30, @@ -331,8 +331,8 @@ var ( ID: "Microsoft.NETFramework.ReferenceAssemblies.net40@1.0.0", Name: "Microsoft.NETFramework.ReferenceAssemblies.net40", Version: "1.0.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 82, EndLine: 86, @@ -343,8 +343,8 @@ var ( ID: "NETStandard.Library@1.6.1", Name: "NETStandard.Library", Version: "1.6.1", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 95, EndLine: 125, @@ -355,8 +355,8 @@ var ( ID: "NETStandard.Library@2.0.3", Name: "NETStandard.Library", Version: "2.0.3", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 445, EndLine: 453, @@ -367,8 +367,8 @@ var ( ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", Version: "12.0.3", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 20, EndLine: 25, @@ -395,8 +395,8 @@ var ( ID: "System.Collections@4.3.0", Name: "System.Collections", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 158, EndLine: 167, @@ -407,8 +407,8 @@ var ( ID: "System.ComponentModel@4.3.0", Name: "System.ComponentModel", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 168, EndLine: 175, @@ -418,9 +418,9 @@ var ( { ID: "System.ComponentModel.Primitives@4.3.0", Name: "System.ComponentModel.Primitives", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, Version: "4.3.0", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 176, EndLine: 185, @@ -430,9 +430,9 @@ var ( { ID: "System.ComponentModel.TypeConverter@4.3.0", Name: "System.ComponentModel.TypeConverter", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, Version: "4.3.0", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 186, EndLine: 203, @@ -443,8 +443,8 @@ var ( ID: "System.Diagnostics.Debug@4.3.0", Name: "System.Diagnostics.Debug", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 204, EndLine: 213, @@ -455,8 +455,8 @@ var ( ID: "System.Diagnostics.Tools@4.3.0", Name: "System.Diagnostics.Tools", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 214, EndLine: 223, @@ -467,8 +467,8 @@ var ( ID: "System.Dynamic.Runtime@4.3.0", Name: "System.Dynamic.Runtime", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 224, EndLine: 234, @@ -479,8 +479,8 @@ var ( ID: "System.Globalization@4.3.0", Name: "System.Globalization", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 235, EndLine: 244, @@ -491,8 +491,8 @@ var ( ID: "System.IO@4.3.0", Name: "System.IO", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 245, EndLine: 256, @@ -503,8 +503,8 @@ var ( ID: "System.Linq@4.3.0", Name: "System.Linq", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 257, EndLine: 265, @@ -515,8 +515,8 @@ var ( ID: "System.Linq.Expressions@4.3.0", Name: "System.Linq.Expressions", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 266, EndLine: 274, @@ -527,8 +527,8 @@ var ( ID: "System.Net.Primitives@4.3.0", Name: "System.Net.Primitives", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 275, EndLine: 284, @@ -539,8 +539,8 @@ var ( ID: "System.ObjectModel@4.3.0", Name: "System.ObjectModel", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 285, EndLine: 292, @@ -551,8 +551,8 @@ var ( ID: "System.Reflection@4.3.0", Name: "System.Reflection", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 293, EndLine: 304, @@ -562,9 +562,9 @@ var ( { ID: "System.Reflection.Extensions@4.3.0", Name: "System.Reflection.Extensions", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, Version: "4.3.0", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 305, EndLine: 315, @@ -574,9 +574,9 @@ var ( { ID: "System.Reflection.Primitives@4.3.0", Name: "System.Reflection.Primitives", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, Version: "4.3.0", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 316, EndLine: 325, @@ -586,9 +586,9 @@ var ( { ID: "System.Resources.ResourceManager@4.3.0", Name: "System.Resources.ResourceManager", - Relationship: types.RelationshipIndirect, + Relationship: ftypes.RelationshipIndirect, Version: "4.3.0", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 326, EndLine: 337, @@ -599,8 +599,8 @@ var ( ID: "System.Runtime@4.3.0", Name: "System.Runtime", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 338, EndLine: 346, @@ -611,8 +611,8 @@ var ( ID: "System.Runtime.CompilerServices.Unsafe@4.5.2", Name: "System.Runtime.CompilerServices.Unsafe", Version: "4.5.2", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 473, EndLine: 477, @@ -623,8 +623,8 @@ var ( ID: "System.Runtime.Extensions@4.3.0", Name: "System.Runtime.Extensions", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 347, EndLine: 356, @@ -635,8 +635,8 @@ var ( ID: "System.Runtime.Serialization.Primitives@4.3.0", Name: "System.Runtime.Serialization.Primitives", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 357, EndLine: 364, @@ -647,8 +647,8 @@ var ( ID: "System.Text.Encoding@4.3.0", Name: "System.Text.Encoding", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 365, EndLine: 374, @@ -659,8 +659,8 @@ var ( ID: "System.Text.Encoding.Extensions@4.3.0", Name: "System.Text.Encoding.Extensions", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 375, EndLine: 385, @@ -671,8 +671,8 @@ var ( ID: "System.Text.RegularExpressions@4.3.0", Name: "System.Text.RegularExpressions", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 386, EndLine: 393, @@ -683,8 +683,8 @@ var ( ID: "System.Threading@4.3.0", Name: "System.Threading", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 394, EndLine: 402, @@ -695,8 +695,8 @@ var ( ID: "System.Threading.Tasks@4.3.0", Name: "System.Threading.Tasks", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 403, EndLine: 412, @@ -707,8 +707,8 @@ var ( ID: "System.Threading.Tasks.Extensions@4.5.2", Name: "System.Threading.Tasks.Extensions", Version: "4.5.2", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 478, EndLine: 485, @@ -719,8 +719,8 @@ var ( ID: "System.Xml.ReaderWriter@4.3.0", Name: "System.Xml.ReaderWriter", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 413, EndLine: 423, @@ -731,8 +731,8 @@ var ( ID: "System.Xml.XDocument@4.3.0", Name: "System.Xml.XDocument", Version: "4.3.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 424, EndLine: 433, @@ -740,7 +740,7 @@ var ( }, }, } - nuGetMultiTargetDeps = []types.Dependency{ + nuGetMultiTargetDeps = []ftypes.Dependency{ { ID: "AWSSDK.Core@3.5.1.30", DependsOn: []string{"Microsoft.Bcl.AsyncInterfaces@1.1.0"}, diff --git a/pkg/dependency/parser/nuget/packagesprops/parse.go b/pkg/dependency/parser/nuget/packagesprops/parse.go index 5e4c6831d1a1..606261703311 100644 --- a/pkg/dependency/parser/nuget/packagesprops/parse.go +++ b/pkg/dependency/parser/nuget/packagesprops/parse.go @@ -8,12 +8,11 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) -type pkg struct { +type Pkg struct { Version string `xml:"Version,attr"` UpdatePackageName string `xml:"Update,attr"` IncludePackageName string `xml:"Include,attr"` @@ -21,8 +20,8 @@ type pkg struct { // https://github.com/dotnet/roslyn-tools/blob/b4c5220f5dfc4278847b6d38eff91cc1188f8066/src/RoslynInsertionTool/RoslynInsertionTool/CoreXT.cs#L150 type itemGroup struct { - PackageReferenceEntry []pkg `xml:"PackageReference"` - PackageVersionEntry []pkg `xml:"PackageVersion"` + PackageReferenceEntry []Pkg `xml:"PackageReference"` + PackageVersionEntry []Pkg `xml:"PackageVersion"` } type project struct { @@ -32,11 +31,11 @@ type project struct { type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } -func (p pkg) library() types.Library { +func (p Pkg) Package() ftypes.Package { // Update attribute is considered legacy, so preferring Include name := p.UpdatePackageName if p.IncludePackageName != "" { @@ -45,20 +44,20 @@ func (p pkg) library() types.Library { name = strings.TrimSpace(name) version := strings.TrimSpace(p.Version) - return types.Library{ + return ftypes.Package{ ID: dependency.ID(ftypes.NuGet, name, version), Name: name, Version: version, } } -func shouldSkipLib(lib types.Library) bool { - if lib.Name == "" || lib.Version == "" { +func shouldSkipPkg(pkg ftypes.Package) bool { + if pkg.Name == "" || pkg.Version == "" { return true } // *packages.props files don't contain variable resolution information. // So we need to skip them. - if isVariable(lib.Name) || isVariable(lib.Version) { + if isVariable(pkg.Name) || isVariable(pkg.Version) { return true } return false @@ -68,20 +67,20 @@ func isVariable(s string) bool { return strings.HasPrefix(s, "$(") && strings.HasSuffix(s, ")") } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var configData project if err := xml.NewDecoder(r).Decode(&configData); err != nil { return nil, nil, xerrors.Errorf("failed to decode '*.packages.props' file: %w", err) } - var libs []types.Library + var pkgs []ftypes.Package for _, item := range configData.ItemGroups { for _, pkg := range append(item.PackageReferenceEntry, item.PackageVersionEntry...) { - lib := pkg.library() - if !shouldSkipLib(lib) { - libs = append(libs, lib) + pkg := pkg.Package() + if !shouldSkipPkg(pkg) { + pkgs = append(pkgs, pkg) } } } - return utils.UniqueLibraries(libs), nil, nil + return utils.UniquePackages(pkgs), nil, nil } diff --git a/pkg/dependency/parser/nuget/packagesprops/parse_test.go b/pkg/dependency/parser/nuget/packagesprops/parse_test.go index 96a50716d7ef..58c5209da333 100644 --- a/pkg/dependency/parser/nuget/packagesprops/parse_test.go +++ b/pkg/dependency/parser/nuget/packagesprops/parse_test.go @@ -8,20 +8,20 @@ import ( "github.com/stretchr/testify/require" config "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/packagesprops" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { tests := []struct { name string // Test input file inputFile string - want []types.Library + want []ftypes.Package wantErr string }{ { name: "PackagesProps", inputFile: "testdata/packages.props", - want: []types.Library{ + want: []ftypes.Package{ {Name: "Microsoft.Extensions.Configuration", Version: "2.1.1", ID: "Microsoft.Extensions.Configuration@2.1.1"}, {Name: "Microsoft.Extensions.DependencyInjection.Abstractions", Version: "2.2.1", ID: "Microsoft.Extensions.DependencyInjection.Abstractions@2.2.1"}, {Name: "Microsoft.Extensions.Http", Version: "3.2.1", ID: "Microsoft.Extensions.Http@3.2.1"}, @@ -30,7 +30,7 @@ func TestParse(t *testing.T) { { name: "DirectoryPackagesProps", inputFile: "testdata/Directory.Packages.props", - want: []types.Library{ + want: []ftypes.Package{ {Name: "PackageOne", Version: "6.2.3", ID: "PackageOne@6.2.3"}, {Name: "PackageThree", Version: "2.4.1", ID: "PackageThree@2.4.1"}, {Name: "PackageTwo", Version: "6.0.0", ID: "PackageTwo@6.0.0"}, @@ -39,7 +39,7 @@ func TestParse(t *testing.T) { { name: "SeveralItemGroupElements", inputFile: "testdata/several_item_groups", - want: []types.Library{ + want: []ftypes.Package{ {Name: "PackageOne", Version: "6.2.3", ID: "PackageOne@6.2.3"}, {Name: "PackageThree", Version: "2.4.1", ID: "PackageThree@2.4.1"}, {Name: "PackageTwo", Version: "6.0.0", ID: "PackageTwo@6.0.0"}, @@ -48,14 +48,14 @@ func TestParse(t *testing.T) { { name: "VariablesAsNamesOrVersion", inputFile: "testdata/variables_and_empty", - want: []types.Library{ + want: []ftypes.Package{ {Name: "PackageFour", Version: "2.4.1", ID: "PackageFour@2.4.1"}, }, }, { name: "NoItemGroupInXMLStructure", inputFile: "testdata/no_item_group.props", - want: []types.Library(nil), + want: []ftypes.Package(nil), }, { name: "NoProject", diff --git a/pkg/dependency/parser/php/composer/parse.go b/pkg/dependency/parser/php/composer/parse.go index 49b73c7994c8..1b1e72bb7a10 100644 --- a/pkg/dependency/parser/php/composer/parse.go +++ b/pkg/dependency/parser/php/composer/parse.go @@ -10,13 +10,12 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) -type lockFile struct { +type LockFile struct { Packages []packageInfo `json:"packages"` } type packageInfo struct { @@ -32,14 +31,14 @@ type Parser struct { logger *log.Logger } -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("composer"), } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { - var lockFile lockFile +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { + var lockFile LockFile input, err := io.ReadAll(r) if err != nil { return nil, nil, xerrors.Errorf("read error: %w", err) @@ -48,61 +47,61 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return nil, nil, xerrors.Errorf("decode error: %w", err) } - libs := make(map[string]types.Library) + pkgs := make(map[string]ftypes.Package) foundDeps := make(map[string][]string) - for _, pkg := range lockFile.Packages { - lib := types.Library{ - ID: dependency.ID(ftypes.Composer, pkg.Name, pkg.Version), - Name: pkg.Name, - Version: pkg.Version, - Relationship: types.RelationshipUnknown, // composer.lock file doesn't have info about direct/indirect dependencies - License: strings.Join(pkg.License, ", "), - Locations: []types.Location{ + for _, lpkg := range lockFile.Packages { + pkg := ftypes.Package{ + ID: dependency.ID(ftypes.Composer, lpkg.Name, lpkg.Version), + Name: lpkg.Name, + Version: lpkg.Version, + Relationship: ftypes.RelationshipUnknown, // composer.lock file doesn't have info about direct/indirect dependencies + Licenses: lpkg.License, + Locations: []ftypes.Location{ { - StartLine: pkg.StartLine, - EndLine: pkg.EndLine, + StartLine: lpkg.StartLine, + EndLine: lpkg.EndLine, }, }, } - libs[lib.Name] = lib + pkgs[pkg.Name] = pkg var dependsOn []string - for depName := range pkg.Require { + for depName := range lpkg.Require { // Require field includes required php version, skip this // Also skip PHP extensions if depName == "php" || strings.HasPrefix(depName, "ext") { continue } - dependsOn = append(dependsOn, depName) // field uses range of versions, so later we will fill in the versions from the libraries + dependsOn = append(dependsOn, depName) // field uses range of versions, so later we will fill in the versions from the packages } if len(dependsOn) > 0 { - foundDeps[lib.ID] = dependsOn + foundDeps[pkg.ID] = dependsOn } } // fill deps versions - var deps []types.Dependency - for libID, depsOn := range foundDeps { + var deps ftypes.Dependencies + for pkgID, depsOn := range foundDeps { var dependsOn []string for _, depName := range depsOn { - if lib, ok := libs[depName]; ok { - dependsOn = append(dependsOn, lib.ID) + if pkg, ok := pkgs[depName]; ok { + dependsOn = append(dependsOn, pkg.ID) continue } p.logger.Debug("Unable to find version", log.String("name", depName)) } sort.Strings(dependsOn) - deps = append(deps, types.Dependency{ - ID: libID, + deps = append(deps, ftypes.Dependency{ + ID: pkgID, DependsOn: dependsOn, }) } - libSlice := maps.Values(libs) - sort.Sort(types.Libraries(libSlice)) - sort.Sort(types.Dependencies(deps)) + pkgSlice := maps.Values(pkgs) + sort.Sort(ftypes.Packages(pkgSlice)) + sort.Sort(deps) - return libSlice, deps, nil + return pkgSlice, deps, nil } // UnmarshalJSONWithMetadata needed to detect start and end lines of deps diff --git a/pkg/dependency/parser/php/composer/parse_test.go b/pkg/dependency/parser/php/composer/parse_test.go index 8c80899bc4ba..726ac4676b37 100644 --- a/pkg/dependency/parser/php/composer/parse_test.go +++ b/pkg/dependency/parser/php/composer/parse_test.go @@ -1,7 +1,7 @@ package composer import ( - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "os" @@ -13,15 +13,15 @@ var ( // apk add jq // composer require guzzlehttp/guzzle:6.5.8 // composer require pear/log:1.13.3 --dev - // composer show -i --no-dev -f json | jq --sort-keys -rc '.installed[] | "{ID: \"\(.name)@\(.version)\", Name: \"\(.name)\", Version: \"\(.version)\", License: \"MIT\", Locations: []types.Location{{StartLine: , EndLine: }}},"' + // composer show -i --no-dev -f json | jq --sort-keys -rc '.installed[] | "{ID: \"\(.name)@\(.version)\", Name: \"\(.name)\", Version: \"\(.version)\", License: \"MIT\", Locations: []ftypes.Location{{StartLine: , EndLine: }}},"' // locations are filled manually - composerLibs = []types.Library{ + composerPkgs = []ftypes.Package{ { - ID: "guzzlehttp/guzzle@6.5.8", - Name: "guzzlehttp/guzzle", - Version: "6.5.8", - License: "MIT", - Locations: []types.Location{ + ID: "guzzlehttp/guzzle@6.5.8", + Name: "guzzlehttp/guzzle", + Version: "6.5.8", + Licenses: []string{"MIT"}, + Locations: []ftypes.Location{ { StartLine: 9, EndLine: 123, @@ -29,11 +29,11 @@ var ( }, }, { - ID: "guzzlehttp/promises@1.5.2", - Name: "guzzlehttp/promises", - Version: "1.5.2", - License: "MIT", - Locations: []types.Location{ + ID: "guzzlehttp/promises@1.5.2", + Name: "guzzlehttp/promises", + Version: "1.5.2", + Licenses: []string{"MIT"}, + Locations: []ftypes.Location{ { StartLine: 124, EndLine: 207, @@ -41,11 +41,11 @@ var ( }, }, { - ID: "guzzlehttp/psr7@1.9.0", - Name: "guzzlehttp/psr7", - Version: "1.9.0", - License: "MIT", - Locations: []types.Location{ + ID: "guzzlehttp/psr7@1.9.0", + Name: "guzzlehttp/psr7", + Version: "1.9.0", + Licenses: []string{"MIT"}, + Locations: []ftypes.Location{ { StartLine: 208, EndLine: 317, @@ -53,11 +53,11 @@ var ( }, }, { - ID: "psr/http-message@1.0.1", - Name: "psr/http-message", - Version: "1.0.1", - License: "MIT", - Locations: []types.Location{ + ID: "psr/http-message@1.0.1", + Name: "psr/http-message", + Version: "1.0.1", + Licenses: []string{"MIT"}, + Locations: []ftypes.Location{ { StartLine: 318, EndLine: 370, @@ -65,11 +65,11 @@ var ( }, }, { - ID: "ralouphie/getallheaders@3.0.3", - Name: "ralouphie/getallheaders", - Version: "3.0.3", - License: "MIT", - Locations: []types.Location{ + ID: "ralouphie/getallheaders@3.0.3", + Name: "ralouphie/getallheaders", + Version: "3.0.3", + Licenses: []string{"MIT"}, + Locations: []ftypes.Location{ { StartLine: 371, EndLine: 414, @@ -77,11 +77,11 @@ var ( }, }, { - ID: "symfony/polyfill-intl-idn@v1.27.0", - Name: "symfony/polyfill-intl-idn", - Version: "v1.27.0", - License: "MIT", - Locations: []types.Location{ + ID: "symfony/polyfill-intl-idn@v1.27.0", + Name: "symfony/polyfill-intl-idn", + Version: "v1.27.0", + Licenses: []string{"MIT"}, + Locations: []ftypes.Location{ { StartLine: 415, EndLine: 501, @@ -89,11 +89,11 @@ var ( }, }, { - ID: "symfony/polyfill-intl-normalizer@v1.27.0", - Name: "symfony/polyfill-intl-normalizer", - Version: "v1.27.0", - License: "MIT", - Locations: []types.Location{ + ID: "symfony/polyfill-intl-normalizer@v1.27.0", + Name: "symfony/polyfill-intl-normalizer", + Version: "v1.27.0", + Licenses: []string{"MIT"}, + Locations: []ftypes.Location{ { StartLine: 502, EndLine: 585, @@ -101,11 +101,11 @@ var ( }, }, { - ID: "symfony/polyfill-php72@v1.27.0", - Name: "symfony/polyfill-php72", - Version: "v1.27.0", - License: "MIT", - Locations: []types.Location{ + ID: "symfony/polyfill-php72@v1.27.0", + Name: "symfony/polyfill-php72", + Version: "v1.27.0", + Licenses: []string{"MIT"}, + Locations: []ftypes.Location{ { StartLine: 586, EndLine: 661, @@ -114,7 +114,7 @@ var ( }, } // dependencies are filled manually - composerDeps = []types.Dependency{ + composerDeps = []ftypes.Dependency{ { ID: "guzzlehttp/guzzle@6.5.8", DependsOn: []string{ @@ -144,13 +144,13 @@ func TestParse(t *testing.T) { tests := []struct { name string file string - wantLibs []types.Library - wantDeps []types.Dependency + wantPkgs []ftypes.Package + wantDeps []ftypes.Dependency }{ { name: "happy path", file: "testdata/composer_happy.lock", - wantLibs: composerLibs, + wantPkgs: composerPkgs, wantDeps: composerDeps, }, } @@ -161,10 +161,10 @@ func TestParse(t *testing.T) { require.NoError(t, err) defer f.Close() - gotLibs, gotDeps, err := NewParser().Parse(f) + gotPkgs, gotDeps, err := NewParser().Parse(f) require.NoError(t, err) - assert.Equal(t, tt.wantLibs, gotLibs) + assert.Equal(t, tt.wantPkgs, gotPkgs) assert.Equal(t, tt.wantDeps, gotDeps) }) } diff --git a/pkg/dependency/parser/python/packaging/parse.go b/pkg/dependency/parser/python/packaging/parse.go index 495e0d4d78ab..c8376a8066f2 100644 --- a/pkg/dependency/parser/python/packaging/parse.go +++ b/pkg/dependency/parser/python/packaging/parse.go @@ -7,9 +7,10 @@ import ( "net/textproto" "strings" + "github.com/samber/lo" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -18,7 +19,7 @@ type Parser struct { logger *log.Logger } -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("python"), } @@ -26,7 +27,7 @@ func NewParser() types.Parser { // Parse parses egg and wheel metadata. // e.g. .egg-info/PKG-INFO and dist-info/METADATA -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { rd := textproto.NewReader(bufio.NewReader(r)) h, err := rd.ReadMIMEHeader() if e := textproto.ProtocolError(""); errors.As(err, &e) { @@ -82,11 +83,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, license = "file://" + h.Get("License-File") } - return []types.Library{ + return []ftypes.Package{ { - Name: name, - Version: version, - License: license, + Name: name, + Version: version, + Licenses: lo.Ternary(license != "", []string{license}, nil), }, }, nil, nil } diff --git a/pkg/dependency/parser/python/packaging/parse_test.go b/pkg/dependency/parser/python/packaging/parse_test.go index ee08c5bdca82..cde70dea19ce 100644 --- a/pkg/dependency/parser/python/packaging/parse_test.go +++ b/pkg/dependency/parser/python/packaging/parse_test.go @@ -8,14 +8,14 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/python/packaging" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { tests := []struct { name string input string - want []types.Library + want []ftypes.Package wantErr bool }{ // listing dependencies based on METADATA/PKG-INFO files @@ -33,16 +33,22 @@ func TestParse(t *testing.T) { // cd /usr/lib/python3.9/site-packages/setuptools-52.0.0-py3.9.egg-info/ // cat PKG-INFO | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | \ // tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}' - want: []types.Library{{Name: "setuptools", Version: "51.3.3", License: "UNKNOWN"}}, + want: []ftypes.Package{ + { + Name: "setuptools", + Version: "51.3.3", + Licenses: []string{"UNKNOWN"}, + }, + }, }, { name: "egg PKG-INFO with description containing non-RFC 7230 bytes", input: "testdata/unidecode-egg-info.PKG-INFO", - want: []types.Library{ + want: []ftypes.Package{ { - Name: "Unidecode", - Version: "0.4.1", - License: "UNKNOWN", + Name: "Unidecode", + Version: "0.4.1", + Licenses: []string{"UNKNOWN"}, }, }, }, @@ -55,7 +61,13 @@ func TestParse(t *testing.T) { // cd /usr/lib/python3.9/site-packages/ // cat distlib-0.3.1-py3.9.egg-info | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | \ // tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}' - want: []types.Library{{Name: "distlib", Version: "0.3.1", License: "Python license"}}, + want: []ftypes.Package{ + { + Name: "distlib", + Version: "0.3.1", + Licenses: []string{"Python license"}, + }, + }, }, { name: "wheel METADATA", @@ -67,31 +79,53 @@ func TestParse(t *testing.T) { // find dist-infos/ | grep -v METADATA | xargs rm -R // for single METADATA file with known name - // cat "{{ libname }}.METADATA | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}' - want: []types.Library{{Name: "simple", Version: "0.1.0", License: ""}}, + // cat "{{ libname }}.METADATA | grep -e "^Name:" -e "^Version:" -e "^Licenses: []string{" | cut -d" " -f2- | tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\"}, \""$2"\", \""$3"\"\}\n")}' + want: []ftypes.Package{ + { + Name: "simple", + Version: "0.1.0", + Licenses: nil, + }, + }, }, { name: "wheel METADATA", // for single METADATA file with known name - // cat "{{ libname }}.METADATA | grep -e "^Name:" -e "^Version:" -e "^License:" | cut -d" " -f2- | tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}' + // cat "{{ libname }}.METADATA | grep -e "^Name:" -e "^Version:" -e "^Licenses: []string{" | cut -d" " -f2- | tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\"}, \""$2"\", \""$3"\"\}\n")}' input: "testdata/distlib-0.3.1.METADATA", - want: []types.Library{{Name: "distlib", Version: "0.3.1", License: "Python Software Foundation License"}}, + want: []ftypes.Package{ + { + Name: "distlib", + Version: "0.3.1", + Licenses: []string{"Python Software Foundation License"}, + }, + }, }, { name: "wheel METADATA", // Input defines "Classifier: License" but it ends at "OSI Approved" which doesn't define any specific license, thus "License" field is added to results input: "testdata/asyncssh-2.14.2.METADATA", - want: []types.Library{{Name: "asyncssh", Version: "2.14.2", License: "Eclipse Public License v2.0"}}, + want: []ftypes.Package{ + { + Name: "asyncssh", + Version: "2.14.2", + Licenses: []string{"Eclipse Public License v2.0"}, + }, + }, }, { name: "wheel METADATA", // Input defines multiple "Classifier: License" input: "testdata/pyphen-0.14.0.METADATA", - want: []types.Library{ - {Name: "pyphen", Version: "0.14.0", License: "GNU General Public License v2 or later (GPLv2+), GNU Lesser General Public License v2 or later (LGPLv2+), Mozilla Public License 1.1 (MPL 1.1)"}, + want: []ftypes.Package{ + { + Name: "pyphen", + Version: "0.14.0", + Licenses: []string{"GNU General Public License v2 or later (GPLv2+), GNU Lesser General Public License v2 or later (LGPLv2+), Mozilla Public License 1.1 (MPL 1.1)"}, + }, }, }, { @@ -102,33 +136,33 @@ func TestParse(t *testing.T) { { name: "with License-Expression field", input: "testdata/iniconfig-2.0.0.METADATA", - want: []types.Library{ + want: []ftypes.Package{ { - Name: "iniconfig", - Version: "2.0.0", - License: "MIT", + Name: "iniconfig", + Version: "2.0.0", + Licenses: []string{"MIT"}, }, }, }, { name: "with an empty license field but with license in Classifier", input: "testdata/zipp-3.12.1.METADATA", - want: []types.Library{ + want: []ftypes.Package{ { - Name: "zipp", - Version: "3.12.1", - License: "MIT License", + Name: "zipp", + Version: "3.12.1", + Licenses: []string{"MIT License"}, }, }, }, { name: "without licenses, but with a license file (a license in Classifier was removed)", input: "testdata/networkx-3.0.METADATA", - want: []types.Library{ + want: []ftypes.Package{ { - Name: "networkx", - Version: "3.0", - License: "file://LICENSE.txt", + Name: "networkx", + Version: "3.0", + Licenses: []string{"file://LICENSE.txt"}, }, }, }, diff --git a/pkg/dependency/parser/python/pip/parse.go b/pkg/dependency/parser/python/pip/parse.go index 4d4f893d63c0..00eee4349b0b 100644 --- a/pkg/dependency/parser/python/pip/parse.go +++ b/pkg/dependency/parser/python/pip/parse.go @@ -10,7 +10,7 @@ import ( "golang.org/x/text/transform" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -24,11 +24,11 @@ const ( type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { // `requirements.txt` can use byte order marks (BOM) // e.g. on Windows `requirements.txt` can use UTF-16LE with BOM // We need to override them to avoid the file being read incorrectly @@ -36,7 +36,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, decodedReader := transform.NewReader(r, transformer) scanner := bufio.NewScanner(decodedReader) - var libs []types.Library + var pkgs []ftypes.Package for scanner.Scan() { line := scanner.Text() line = strings.ReplaceAll(line, " ", "") @@ -49,7 +49,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, if len(s) != 2 { continue } - libs = append(libs, types.Library{ + pkgs = append(pkgs, ftypes.Package{ Name: s[0], Version: s[1], }) @@ -57,7 +57,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, if err := scanner.Err(); err != nil { return nil, nil, xerrors.Errorf("scan error: %w", err) } - return libs, nil, nil + return pkgs, nil, nil } func rStripByKey(line, key string) string { diff --git a/pkg/dependency/parser/python/pip/parse_test.go b/pkg/dependency/parser/python/pip/parse_test.go index a3a183f94a8e..d887205fb148 100644 --- a/pkg/dependency/parser/python/pip/parse_test.go +++ b/pkg/dependency/parser/python/pip/parse_test.go @@ -8,13 +8,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { vectors := []struct { file string - want []types.Library + want []ftypes.Package }{ { file: "testdata/requirements_flask.txt", diff --git a/pkg/dependency/parser/python/pip/parse_testcase.go b/pkg/dependency/parser/python/pip/parse_testcase.go index 45642d47f2fa..dc119c3ba054 100644 --- a/pkg/dependency/parser/python/pip/parse_testcase.go +++ b/pkg/dependency/parser/python/pip/parse_testcase.go @@ -1,9 +1,9 @@ package pip -import "github.com/aquasecurity/trivy/pkg/dependency/types" +import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( - requirementsFlask = []types.Library{ + requirementsFlask = []ftypes.Package{ {Name: "click", Version: "8.0.0"}, {Name: "Flask", Version: "2.0.0"}, {Name: "itsdangerous", Version: "2.0.0"}, @@ -12,45 +12,45 @@ var ( {Name: "Werkzeug", Version: "2.0.0"}, } - requirementsComments = []types.Library{ + requirementsComments = []ftypes.Package{ {Name: "click", Version: "8.0.0"}, {Name: "Flask", Version: "2.0.0"}, {Name: "Jinja2", Version: "3.0.0"}, {Name: "MarkupSafe", Version: "2.0.0"}, } - requirementsSpaces = []types.Library{ + requirementsSpaces = []ftypes.Package{ {Name: "click", Version: "8.0.0"}, {Name: "Flask", Version: "2.0.0"}, {Name: "itsdangerous", Version: "2.0.0"}, {Name: "Jinja2", Version: "3.0.0"}, } - requirementsNoVersion = []types.Library{ + requirementsNoVersion = []ftypes.Package{ {Name: "Flask", Version: "2.0.0"}, } - requirementsOperator = []types.Library{ + requirementsOperator = []ftypes.Package{ {Name: "Django", Version: "2.3.4"}, {Name: "SomeProject", Version: "5.4"}, } - requirementsHash = []types.Library{ + requirementsHash = []ftypes.Package{ {Name: "FooProject", Version: "1.2"}, {Name: "Jinja2", Version: "3.0.0"}, } - requirementsHyphens = []types.Library{ + requirementsHyphens = []ftypes.Package{ {Name: "oauth2-client", Version: "4.0.0"}, {Name: "python-gitlab", Version: "2.0.0"}, } - requirementsExtras = []types.Library{ + requirementsExtras = []ftypes.Package{ {Name: "pyjwt", Version: "2.1.0"}, {Name: "celery", Version: "4.4.7"}, } - requirementsUtf16le = []types.Library{ + requirementsUtf16le = []ftypes.Package{ {Name: "attrs", Version: "20.3.0"}, } ) diff --git a/pkg/dependency/parser/python/pipenv/parse.go b/pkg/dependency/parser/python/pipenv/parse.go index 70332764195e..8fbb70132c05 100644 --- a/pkg/dependency/parser/python/pipenv/parse.go +++ b/pkg/dependency/parser/python/pipenv/parse.go @@ -7,7 +7,7 @@ import ( "github.com/liamg/jfather" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -22,11 +22,11 @@ type dependency struct { type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var lockFile lockFile input, err := io.ReadAll(r) if err != nil { @@ -36,20 +36,20 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return nil, nil, xerrors.Errorf("failed to decode Pipenv.lock: %w", err) } - var libs []types.Library - for pkgName, dependency := range lockFile.Default { - libs = append(libs, types.Library{ + var pkgs []ftypes.Package + for pkgName, dep := range lockFile.Default { + pkgs = append(pkgs, ftypes.Package{ Name: pkgName, - Version: strings.TrimLeft(dependency.Version, "="), - Locations: []types.Location{ + Version: strings.TrimLeft(dep.Version, "="), + Locations: []ftypes.Location{ { - StartLine: dependency.StartLine, - EndLine: dependency.EndLine, + StartLine: dep.StartLine, + EndLine: dep.EndLine, }, }, }) } - return libs, nil, nil + return pkgs, nil, nil } // UnmarshalJSONWithMetadata needed to detect start and end lines of deps diff --git a/pkg/dependency/parser/python/pipenv/parse_test.go b/pkg/dependency/parser/python/pipenv/parse_test.go index 03fbe573ee7b..db578110a626 100644 --- a/pkg/dependency/parser/python/pipenv/parse_test.go +++ b/pkg/dependency/parser/python/pipenv/parse_test.go @@ -4,19 +4,18 @@ import ( "os" "path" "sort" - "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { vectors := []struct { file string // Test input file - want []types.Library + want []ftypes.Package }{ { file: "testdata/Pipfile_normal.lock", @@ -40,21 +39,8 @@ func TestParse(t *testing.T) { got, _, err := NewParser().Parse(f) require.NoError(t, err) - sort.Slice(got, func(i, j int) bool { - ret := strings.Compare(got[i].Name, got[j].Name) - if ret == 0 { - return got[i].Version < got[j].Version - } - return ret < 0 - }) - - sort.Slice(v.want, func(i, j int) bool { - ret := strings.Compare(v.want[i].Name, v.want[j].Name) - if ret == 0 { - return v.want[i].Version < v.want[j].Version - } - return ret < 0 - }) + sort.Sort(ftypes.Packages(got)) + sort.Sort(ftypes.Packages(v.want)) assert.Equal(t, v.want, got) }) diff --git a/pkg/dependency/parser/python/pipenv/parse_testcase.go b/pkg/dependency/parser/python/pipenv/parse_testcase.go index 6a611944d3eb..6d52afbfd861 100644 --- a/pkg/dependency/parser/python/pipenv/parse_testcase.go +++ b/pkg/dependency/parser/python/pipenv/parse_testcase.go @@ -1,6 +1,6 @@ package pipenv -import "github.com/aquasecurity/trivy/pkg/dependency/types" +import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( // docker run --name pipenv --rm -it python:3.9-alpine sh @@ -11,13 +11,13 @@ var ( // pipenv graph --json | jq -rc '.[] | "{\"\(.package.package_name | ascii_downcase)\", \"\(.package.installed_version)\", \"\"},"' // graph doesn't contain information about location of dependency in lock file. // add locations manually - pipenvNormal = []types.Library{ - {Name: "urllib3", Version: "1.24.2", Locations: []types.Location{{StartLine: 65, EndLine: 71}}}, - {Name: "requests", Version: "2.21.0", Locations: []types.Location{{StartLine: 57, EndLine: 64}}}, - {Name: "pyyaml", Version: "5.1", Locations: []types.Location{{StartLine: 40, EndLine: 56}}}, - {Name: "idna", Version: "2.8", Locations: []types.Location{{StartLine: 33, EndLine: 39}}}, - {Name: "chardet", Version: "3.0.4", Locations: []types.Location{{StartLine: 26, EndLine: 32}}}, - {Name: "certifi", Version: "2019.3.9", Locations: []types.Location{{StartLine: 19, EndLine: 25}}}, + pipenvNormal = []ftypes.Package{ + {Name: "urllib3", Version: "1.24.2", Locations: []ftypes.Location{{StartLine: 65, EndLine: 71}}}, + {Name: "requests", Version: "2.21.0", Locations: []ftypes.Location{{StartLine: 57, EndLine: 64}}}, + {Name: "pyyaml", Version: "5.1", Locations: []ftypes.Location{{StartLine: 40, EndLine: 56}}}, + {Name: "idna", Version: "2.8", Locations: []ftypes.Location{{StartLine: 33, EndLine: 39}}}, + {Name: "chardet", Version: "3.0.4", Locations: []ftypes.Location{{StartLine: 26, EndLine: 32}}}, + {Name: "certifi", Version: "2019.3.9", Locations: []ftypes.Location{{StartLine: 19, EndLine: 25}}}, } // docker run --name pipenv --rm -it python:3.9-alpine bash @@ -28,17 +28,17 @@ var ( // pipenv graph --json | jq -rc '.[] | "{\"\(.package.package_name | ascii_downcase)\", \"\(.package.installed_version)\", \"\"},"' // graph doesn't contain information about location of dependency in lock file. // add locations manually - pipenvDjango = []types.Library{ - {Name: "urllib3", Version: "1.24.2", Locations: []types.Location{{StartLine: 95, EndLine: 101}}}, - {Name: "sqlparse", Version: "0.3.0", Locations: []types.Location{{StartLine: 88, EndLine: 94}}}, - {Name: "requests", Version: "2.21.0", Locations: []types.Location{{StartLine: 80, EndLine: 87}}}, - {Name: "pyyaml", Version: "5.1", Locations: []types.Location{{StartLine: 63, EndLine: 79}}}, - {Name: "pytz", Version: "2019.1", Locations: []types.Location{{StartLine: 56, EndLine: 62}}}, - {Name: "idna", Version: "2.8", Locations: []types.Location{{StartLine: 49, EndLine: 55}}}, - {Name: "djangorestframework", Version: "3.9.3", Locations: []types.Location{{StartLine: 41, EndLine: 48}}}, - {Name: "django", Version: "2.2", Locations: []types.Location{{StartLine: 33, EndLine: 40}}}, - {Name: "chardet", Version: "3.0.4", Locations: []types.Location{{StartLine: 26, EndLine: 32}}}, - {Name: "certifi", Version: "2019.3.9", Locations: []types.Location{{StartLine: 19, EndLine: 25}}}, + pipenvDjango = []ftypes.Package{ + {Name: "urllib3", Version: "1.24.2", Locations: []ftypes.Location{{StartLine: 95, EndLine: 101}}}, + {Name: "sqlparse", Version: "0.3.0", Locations: []ftypes.Location{{StartLine: 88, EndLine: 94}}}, + {Name: "requests", Version: "2.21.0", Locations: []ftypes.Location{{StartLine: 80, EndLine: 87}}}, + {Name: "pyyaml", Version: "5.1", Locations: []ftypes.Location{{StartLine: 63, EndLine: 79}}}, + {Name: "pytz", Version: "2019.1", Locations: []ftypes.Location{{StartLine: 56, EndLine: 62}}}, + {Name: "idna", Version: "2.8", Locations: []ftypes.Location{{StartLine: 49, EndLine: 55}}}, + {Name: "djangorestframework", Version: "3.9.3", Locations: []ftypes.Location{{StartLine: 41, EndLine: 48}}}, + {Name: "django", Version: "2.2", Locations: []ftypes.Location{{StartLine: 33, EndLine: 40}}}, + {Name: "chardet", Version: "3.0.4", Locations: []ftypes.Location{{StartLine: 26, EndLine: 32}}}, + {Name: "certifi", Version: "2019.3.9", Locations: []ftypes.Location{{StartLine: 19, EndLine: 25}}}, } // docker run --name pipenv --rm -it python:3.9-alpine bash @@ -49,30 +49,30 @@ var ( // pipenv graph --json | jq -rc '.[] | "{\"\(.package.package_name | ascii_downcase)\", \"\(.package.installed_version)\", \"\"},"' // graph doesn't contain information about location of dependency in lock file. // add locations manually - pipenvMany = []types.Library{ - {Name: "urllib3", Version: "1.24.2", Locations: []types.Location{{StartLine: 237, EndLine: 244}}}, - {Name: "sqlparse", Version: "0.3.0", Locations: []types.Location{{StartLine: 230, EndLine: 236}}}, - {Name: "six", Version: "1.12.0", Locations: []types.Location{{StartLine: 222, EndLine: 229}}}, - {Name: "simplejson", Version: "3.16.0", Locations: []types.Location{{StartLine: 204, EndLine: 221}}}, - {Name: "s3transfer", Version: "0.2.0", Locations: []types.Location{{StartLine: 197, EndLine: 203}}}, - {Name: "rsa", Version: "3.4.2", Locations: []types.Location{{StartLine: 190, EndLine: 196}}}, - {Name: "requests", Version: "2.21.0", Locations: []types.Location{{StartLine: 182, EndLine: 189}}}, - {Name: "pyyaml", Version: "3.13", Locations: []types.Location{{StartLine: 165, EndLine: 181}}}, - {Name: "pytz", Version: "2019.1", Locations: []types.Location{{StartLine: 158, EndLine: 164}}}, - {Name: "python-dateutil", Version: "2.8.0", Locations: []types.Location{{StartLine: 150, EndLine: 157}}}, - {Name: "pyasn1", Version: "0.4.5", Locations: []types.Location{{StartLine: 142, EndLine: 149}}}, - {Name: "markupsafe", Version: "1.1.1", Locations: []types.Location{{StartLine: 109, EndLine: 141}}}, - {Name: "jmespath", Version: "0.9.4", Locations: []types.Location{{StartLine: 102, EndLine: 108}}}, - {Name: "jinja2", Version: "2.10.1", Locations: []types.Location{{StartLine: 94, EndLine: 101}}}, - {Name: "idna", Version: "2.8", Locations: []types.Location{{StartLine: 87, EndLine: 93}}}, - {Name: "framework", Version: "0.1.0", Locations: []types.Location{{StartLine: 80, EndLine: 86}}}, - {Name: "docutils", Version: "0.14", Locations: []types.Location{{StartLine: 72, EndLine: 79}}}, - {Name: "djangorestframework", Version: "3.9.3", Locations: []types.Location{{StartLine: 64, EndLine: 71}}}, - {Name: "django", Version: "2.2", Locations: []types.Location{{StartLine: 56, EndLine: 63}}}, - {Name: "colorama", Version: "0.3.9", Locations: []types.Location{{StartLine: 49, EndLine: 55}}}, - {Name: "chardet", Version: "3.0.4", Locations: []types.Location{{StartLine: 42, EndLine: 48}}}, - {Name: "certifi", Version: "2019.3.9", Locations: []types.Location{{StartLine: 35, EndLine: 41}}}, - {Name: "botocore", Version: "1.12.137", Locations: []types.Location{{StartLine: 27, EndLine: 34}}}, - {Name: "awscli", Version: "1.16.147", Locations: []types.Location{{StartLine: 19, EndLine: 26}}}, + pipenvMany = []ftypes.Package{ + {Name: "urllib3", Version: "1.24.2", Locations: []ftypes.Location{{StartLine: 237, EndLine: 244}}}, + {Name: "sqlparse", Version: "0.3.0", Locations: []ftypes.Location{{StartLine: 230, EndLine: 236}}}, + {Name: "six", Version: "1.12.0", Locations: []ftypes.Location{{StartLine: 222, EndLine: 229}}}, + {Name: "simplejson", Version: "3.16.0", Locations: []ftypes.Location{{StartLine: 204, EndLine: 221}}}, + {Name: "s3transfer", Version: "0.2.0", Locations: []ftypes.Location{{StartLine: 197, EndLine: 203}}}, + {Name: "rsa", Version: "3.4.2", Locations: []ftypes.Location{{StartLine: 190, EndLine: 196}}}, + {Name: "requests", Version: "2.21.0", Locations: []ftypes.Location{{StartLine: 182, EndLine: 189}}}, + {Name: "pyyaml", Version: "3.13", Locations: []ftypes.Location{{StartLine: 165, EndLine: 181}}}, + {Name: "pytz", Version: "2019.1", Locations: []ftypes.Location{{StartLine: 158, EndLine: 164}}}, + {Name: "python-dateutil", Version: "2.8.0", Locations: []ftypes.Location{{StartLine: 150, EndLine: 157}}}, + {Name: "pyasn1", Version: "0.4.5", Locations: []ftypes.Location{{StartLine: 142, EndLine: 149}}}, + {Name: "markupsafe", Version: "1.1.1", Locations: []ftypes.Location{{StartLine: 109, EndLine: 141}}}, + {Name: "jmespath", Version: "0.9.4", Locations: []ftypes.Location{{StartLine: 102, EndLine: 108}}}, + {Name: "jinja2", Version: "2.10.1", Locations: []ftypes.Location{{StartLine: 94, EndLine: 101}}}, + {Name: "idna", Version: "2.8", Locations: []ftypes.Location{{StartLine: 87, EndLine: 93}}}, + {Name: "framework", Version: "0.1.0", Locations: []ftypes.Location{{StartLine: 80, EndLine: 86}}}, + {Name: "docutils", Version: "0.14", Locations: []ftypes.Location{{StartLine: 72, EndLine: 79}}}, + {Name: "djangorestframework", Version: "3.9.3", Locations: []ftypes.Location{{StartLine: 64, EndLine: 71}}}, + {Name: "django", Version: "2.2", Locations: []ftypes.Location{{StartLine: 56, EndLine: 63}}}, + {Name: "colorama", Version: "0.3.9", Locations: []ftypes.Location{{StartLine: 49, EndLine: 55}}}, + {Name: "chardet", Version: "3.0.4", Locations: []ftypes.Location{{StartLine: 42, EndLine: 48}}}, + {Name: "certifi", Version: "2019.3.9", Locations: []ftypes.Location{{StartLine: 35, EndLine: 41}}}, + {Name: "botocore", Version: "1.12.137", Locations: []ftypes.Location{{StartLine: 27, EndLine: 34}}}, + {Name: "awscli", Version: "1.16.147", Locations: []ftypes.Location{{StartLine: 19, EndLine: 26}}}, } ) diff --git a/pkg/dependency/parser/python/poetry/parse.go b/pkg/dependency/parser/python/poetry/parse.go index 30708cc67add..b7a365a17488 100644 --- a/pkg/dependency/parser/python/poetry/parse.go +++ b/pkg/dependency/parser/python/poetry/parse.go @@ -9,7 +9,6 @@ import ( version "github.com/aquasecurity/go-pep440-version" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -39,61 +38,61 @@ func NewParser() *Parser { } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var lockfile Lockfile if _, err := toml.NewDecoder(r).Decode(&lockfile); err != nil { return nil, nil, xerrors.Errorf("failed to decode poetry.lock: %w", err) } // Keep all installed versions - libVersions := p.parseVersions(lockfile) + pkgVersions := p.parseVersions(lockfile) - var libs []types.Library - var deps []types.Dependency + var pkgs []ftypes.Package + var deps []ftypes.Dependency for _, pkg := range lockfile.Packages { if pkg.Category == "dev" { continue } pkgID := packageID(pkg.Name, pkg.Version) - libs = append(libs, types.Library{ + pkgs = append(pkgs, ftypes.Package{ ID: pkgID, Name: pkg.Name, Version: pkg.Version, }) - dependsOn := p.parseDependencies(pkg.Dependencies, libVersions) + dependsOn := p.parseDependencies(pkg.Dependencies, pkgVersions) if len(dependsOn) != 0 { - deps = append(deps, types.Dependency{ + deps = append(deps, ftypes.Dependency{ ID: pkgID, DependsOn: dependsOn, }) } } - return libs, deps, nil + return pkgs, deps, nil } -// parseVersions stores all installed versions of libraries for use in dependsOn -// as the dependencies of libraries use version range. +// parseVersions stores all installed versions of packages for use in dependsOn +// as the dependencies of packages use version range. func (p *Parser) parseVersions(lockfile Lockfile) map[string][]string { - libVersions := make(map[string][]string) + pkgVersions := make(map[string][]string) for _, pkg := range lockfile.Packages { if pkg.Category == "dev" { continue } - if vers, ok := libVersions[pkg.Name]; ok { - libVersions[pkg.Name] = append(vers, pkg.Version) + if vers, ok := pkgVersions[pkg.Name]; ok { + pkgVersions[pkg.Name] = append(vers, pkg.Version) } else { - libVersions[pkg.Name] = []string{pkg.Version} + pkgVersions[pkg.Name] = []string{pkg.Version} } } - return libVersions + return pkgVersions } -func (p *Parser) parseDependencies(deps map[string]any, libVersions map[string][]string) []string { +func (p *Parser) parseDependencies(deps map[string]any, pkgVersions map[string][]string) []string { var dependsOn []string for name, versRange := range deps { - if dep, err := p.parseDependency(name, versRange, libVersions); err != nil { + if dep, err := p.parseDependency(name, versRange, pkgVersions); err != nil { p.logger.Debug("Failed to parse poetry dependency", log.Err(err)) } else if dep != "" { dependsOn = append(dependsOn, dep) @@ -105,9 +104,9 @@ func (p *Parser) parseDependencies(deps map[string]any, libVersions map[string][ return dependsOn } -func (p *Parser) parseDependency(name string, versRange any, libVersions map[string][]string) (string, error) { +func (p *Parser) parseDependency(name string, versRange any, pkgVersions map[string][]string) (string, error) { name = normalizePkgName(name) - vers, ok := libVersions[name] + vers, ok := pkgVersions[name] if !ok { return "", xerrors.Errorf("no version found for %q", name) } diff --git a/pkg/dependency/parser/python/poetry/parse_test.go b/pkg/dependency/parser/python/poetry/parse_test.go index d7f7adf630eb..5ce44ddcea8f 100644 --- a/pkg/dependency/parser/python/poetry/parse_test.go +++ b/pkg/dependency/parser/python/poetry/parse_test.go @@ -8,34 +8,34 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParser_Parse(t *testing.T) { tests := []struct { name string file string - wantLibs []types.Library - wantDeps []types.Dependency + wantPkgs []ftypes.Package + wantDeps []ftypes.Dependency wantErr assert.ErrorAssertionFunc }{ { name: "normal", file: "testdata/poetry_normal.lock", - wantLibs: poetryNormal, + wantPkgs: poetryNormal, wantErr: assert.NoError, }, { name: "many", file: "testdata/poetry_many.lock", - wantLibs: poetryMany, + wantPkgs: poetryMany, wantDeps: poetryManyDeps, wantErr: assert.NoError, }, { name: "flask", file: "testdata/poetry_flask.lock", - wantLibs: poetryFlask, + wantPkgs: poetryFlask, wantDeps: poetryFlaskDeps, wantErr: assert.NoError, }, @@ -47,11 +47,11 @@ func TestParser_Parse(t *testing.T) { defer f.Close() p := NewParser() - gotLibs, gotDeps, err := p.Parse(f) + gotPkgs, gotDeps, err := p.Parse(f) if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.file)) { return } - assert.Equalf(t, tt.wantLibs, gotLibs, "Parse(%v)", tt.file) + assert.Equalf(t, tt.wantPkgs, gotPkgs, "Parse(%v)", tt.file) assert.Equalf(t, tt.wantDeps, gotDeps, "Parse(%v)", tt.file) }) } @@ -62,7 +62,7 @@ func TestParseDependency(t *testing.T) { name string packageName string versionRange interface{} - libsVersions map[string][]string + pkgsVersions map[string][]string want string wantErr string }{ @@ -70,7 +70,7 @@ func TestParseDependency(t *testing.T) { name: "handle package name", packageName: "Test_project.Name", versionRange: "*", - libsVersions: map[string][]string{ + pkgsVersions: map[string][]string{ "test-project-name": {"1.0.0"}, }, want: "test-project-name@1.0.0", @@ -79,7 +79,7 @@ func TestParseDependency(t *testing.T) { name: "version range as string", packageName: "test", versionRange: ">=1.0.0", - libsVersions: map[string][]string{ + pkgsVersions: map[string][]string{ "test": {"2.0.0"}, }, want: "test@2.0.0", @@ -88,7 +88,7 @@ func TestParseDependency(t *testing.T) { name: "version range == *", packageName: "test", versionRange: "*", - libsVersions: map[string][]string{ + pkgsVersions: map[string][]string{ "test": {"3.0.0"}, }, want: "test@3.0.0", @@ -100,23 +100,23 @@ func TestParseDependency(t *testing.T) { "version": ">=4.8.3", "markers": "python_version < \"3.8\"", }, - libsVersions: map[string][]string{ + pkgsVersions: map[string][]string{ "test": {"5.0.0"}, }, want: "test@5.0.0", }, { - name: "libsVersions doesn't contain required version", + name: "pkgsVersions doesn't contain required version", packageName: "test", versionRange: ">=1.0.0", - libsVersions: map[string][]string{}, + pkgsVersions: map[string][]string{}, wantErr: "no version found", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := NewParser().parseDependency(tt.packageName, tt.versionRange, tt.libsVersions) + got, err := NewParser().parseDependency(tt.packageName, tt.versionRange, tt.pkgsVersions) if tt.wantErr != "" { assert.ErrorContains(t, err, tt.wantErr) return diff --git a/pkg/dependency/parser/python/poetry/parse_testcase.go b/pkg/dependency/parser/python/poetry/parse_testcase.go index c6511c0bd089..3e54a465c2f1 100644 --- a/pkg/dependency/parser/python/poetry/parse_testcase.go +++ b/pkg/dependency/parser/python/poetry/parse_testcase.go @@ -1,6 +1,6 @@ package poetry -import "github.com/aquasecurity/trivy/pkg/dependency/types" +import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( // docker run --name pipenv --rm -it python@sha256:e1141f10176d74d1a0e87a7c0a0a5a98dd98ec5ac12ce867768f40c6feae2fd9 sh @@ -10,7 +10,7 @@ var ( // poetry new normal && cd normal // poetry add pypi@2.1 // poetry show -a | awk '{gsub(/\(!\)/, ""); printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\"},\n") }' - poetryNormal = []types.Library{ + poetryNormal = []ftypes.Package{ {ID: "pypi@2.1", Name: "pypi", Version: "2.1"}, } @@ -24,7 +24,7 @@ var ( // poetry show -a | awk '{gsub(/\(!\)/, ""); printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\"},\n") }' // `--no-dev` flag uncorrected returns deps. Then need to remove `dev` deps manually // list of dev deps - cat poetry.lock | grep 'category = "dev"' -B 3 - poetryMany = []types.Library{ + poetryMany = []ftypes.Package{ {ID: "attrs@22.2.0", Name: "attrs", Version: "22.2.0"}, {ID: "backports-cached-property@1.0.2", Name: "backports-cached-property", Version: "1.0.2"}, {ID: "build@0.10.0", Name: "build", Version: "0.10.0"}, @@ -82,7 +82,7 @@ var ( } // cat poetry.lock | grep "\[package.dependencies\]" -B 3 -A 8 - it might help to complete this slice - poetryManyDeps = []types.Dependency{ + poetryManyDeps = []ftypes.Dependency{ {ID: "build@0.10.0", DependsOn: []string{"colorama@0.4.6", "importlib-metadata@6.0.0", "packaging@23.0", "pyproject-hooks@1.0.0", "tomli@2.0.1"}}, {ID: "cachecontrol@0.12.11", DependsOn: []string{"lockfile@0.12.2", "msgpack@1.0.4", "requests@2.28.2"}}, {ID: "cffi@1.15.1", DependsOn: []string{"pycparser@2.21"}}, @@ -115,7 +115,7 @@ var ( // poetry new web && cd web // poetry add flask@1.0.3 // poetry show -a | awk '{gsub(/\(!\)/, ""); printf("{ID: \""$1"@"$2"\", Name: \""$1"\", Version: \""$2"\"},\n") }' - poetryFlask = []types.Library{ + poetryFlask = []ftypes.Package{ {ID: "click@8.1.3", Name: "click", Version: "8.1.3"}, {ID: "colorama@0.4.6", Name: "colorama", Version: "0.4.6"}, {ID: "flask@1.0.3", Name: "flask", Version: "1.0.3"}, @@ -126,7 +126,7 @@ var ( } // cat poetry.lock | grep "\[package.dependencies\]" -B 3 -A 8 - it might help to complete this slice - poetryFlaskDeps = []types.Dependency{ + poetryFlaskDeps = []ftypes.Dependency{ {ID: "click@8.1.3", DependsOn: []string{"colorama@0.4.6"}}, {ID: "flask@1.0.3", DependsOn: []string{"click@8.1.3", "itsdangerous@2.1.2", "jinja2@3.1.2", "werkzeug@2.2.3"}}, {ID: "jinja2@3.1.2", DependsOn: []string{"markupsafe@2.1.2"}}, diff --git a/pkg/dependency/parser/ruby/bundler/parse.go b/pkg/dependency/parser/ruby/bundler/parse.go index 6c59eeca2d49..89f3a9ab4ab8 100644 --- a/pkg/dependency/parser/ruby/bundler/parse.go +++ b/pkg/dependency/parser/ruby/bundler/parse.go @@ -9,21 +9,20 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { - libs := make(map[string]types.Library) +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { + pkgs := make(map[string]ftypes.Package) var dependsOn, directDeps []string - var deps []types.Dependency + var deps []ftypes.Dependency var pkgID string lineNum := 1 @@ -34,7 +33,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // Parse dependencies if countLeadingSpace(line) == 4 { if len(dependsOn) > 0 { - deps = append(deps, types.Dependency{ + deps = append(deps, ftypes.Dependency{ ID: pkgID, DependsOn: dependsOn, }) @@ -49,12 +48,12 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, version = strings.SplitN(version, "-", 2)[0] // drop platform (e.g. 1.13.6-x86_64-linux => 1.13.6) name := s[0] pkgID = packageID(name, version) - libs[name] = types.Library{ + pkgs[name] = ftypes.Package{ ID: pkgID, Name: name, Version: version, - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: lineNum, EndLine: lineNum, @@ -77,7 +76,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, } // append last dependency (if any) if len(dependsOn) > 0 { - deps = append(deps, types.Dependency{ + deps = append(deps, ftypes.Dependency{ ID: pkgID, DependsOn: dependsOn, }) @@ -85,17 +84,17 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // Identify which are direct dependencies for _, d := range directDeps { - if l, ok := libs[d]; ok { - l.Relationship = types.RelationshipDirect - libs[d] = l + if l, ok := pkgs[d]; ok { + l.Relationship = ftypes.RelationshipDirect + pkgs[d] = l } } for i, dep := range deps { dependsOn = make([]string, 0) for _, pkgName := range dep.DependsOn { - if lib, ok := libs[pkgName]; ok { - dependsOn = append(dependsOn, packageID(pkgName, lib.Version)) + if pkg, ok := pkgs[pkgName]; ok { + dependsOn = append(dependsOn, packageID(pkgName, pkg.Version)) } } deps[i].DependsOn = dependsOn @@ -104,11 +103,9 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return nil, nil, xerrors.Errorf("scan error: %w", err) } - libSlice := maps.Values(libs) - sort.Slice(libSlice, func(i, j int) bool { - return libSlice[i].Name < libSlice[j].Name - }) - return libSlice, deps, nil + pkgSlice := maps.Values(pkgs) + sort.Sort(ftypes.Packages(pkgSlice)) + return pkgSlice, deps, nil } func countLeadingSpace(line string) int { diff --git a/pkg/dependency/parser/ruby/bundler/parse_test.go b/pkg/dependency/parser/ruby/bundler/parse_test.go index f172225e6f02..0668bbd5bfbc 100644 --- a/pkg/dependency/parser/ruby/bundler/parse_test.go +++ b/pkg/dependency/parser/ruby/bundler/parse_test.go @@ -9,41 +9,17 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/ruby/bundler" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) var ( - NormalLibs = []types.Library{ - { - ID: "coderay@1.1.2", - Name: "coderay", - Version: "1.1.2", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 4, - EndLine: 4, - }, - }, - }, - { - ID: "concurrent-ruby@1.1.5", - Name: "concurrent-ruby", - Version: "1.1.5", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 5, - EndLine: 5, - }, - }, - }, + NormalPkgs = []ftypes.Package{ { ID: "dotenv@2.7.2", Name: "dotenv", Version: "2.7.2", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 6, EndLine: 6, @@ -54,20 +30,56 @@ var ( ID: "faker@1.9.3", Name: "faker", Version: "1.9.3", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 7, EndLine: 7, }, }, }, + { + ID: "pry@0.12.2", + Name: "pry", + Version: "0.12.2", + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ + { + StartLine: 12, + EndLine: 12, + }, + }, + }, + { + ID: "coderay@1.1.2", + Name: "coderay", + Version: "1.1.2", + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, + { + ID: "concurrent-ruby@1.1.5", + Name: "concurrent-ruby", + Version: "1.1.5", + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, { ID: "i18n@1.6.0", Name: "i18n", Version: "1.6.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 9, EndLine: 9, @@ -78,28 +90,16 @@ var ( ID: "method_source@0.9.2", Name: "method_source", Version: "0.9.2", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ { StartLine: 11, EndLine: 11, }, }, }, - { - ID: "pry@0.12.2", - Name: "pry", - Version: "0.12.2", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ - { - StartLine: 12, - EndLine: 12, - }, - }, - }, } - NormalDeps = []types.Dependency{ + NormalDeps = []ftypes.Dependency{ { ID: "faker@1.9.3", DependsOn: []string{"i18n@1.6.0"}, @@ -116,37 +116,13 @@ var ( }, }, } - Bundler2Libs = []types.Library{ - { - ID: "coderay@1.1.3", - Name: "coderay", - Version: "1.1.3", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 4, - EndLine: 4, - }, - }, - }, - { - ID: "concurrent-ruby@1.1.10", - Name: "concurrent-ruby", - Version: "1.1.10", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 5, - EndLine: 5, - }, - }, - }, + Bundler2Pkgs = []ftypes.Package{ { ID: "dotenv@2.7.6", Name: "dotenv", Version: "2.7.6", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 6, EndLine: 6, @@ -157,64 +133,88 @@ var ( ID: "faker@2.21.0", Name: "faker", Version: "2.21.0", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 7, EndLine: 7, }, }, }, - { - ID: "i18n@1.10.0", - Name: "i18n", - Version: "1.10.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 9, - EndLine: 9, - }, - }, - }, { ID: "json@2.6.2", Name: "json", Version: "2.6.2", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 11, EndLine: 11, }, }, }, - { - ID: "method_source@1.0.0", - Name: "method_source", - Version: "1.0.0", - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 12, - EndLine: 12, - }, - }, - }, { ID: "pry@0.14.1", Name: "pry", Version: "0.14.1", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ { StartLine: 13, EndLine: 13, }, }, }, + { + ID: "coderay@1.1.3", + Name: "coderay", + Version: "1.1.3", + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, + { + ID: "concurrent-ruby@1.1.10", + Name: "concurrent-ruby", + Version: "1.1.10", + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + { + ID: "i18n@1.10.0", + Name: "i18n", + Version: "1.10.0", + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ + { + StartLine: 9, + EndLine: 9, + }, + }, + }, + { + ID: "method_source@1.0.0", + Name: "method_source", + Version: "1.0.0", + Relationship: ftypes.RelationshipIndirect, + Locations: []ftypes.Location{ + { + StartLine: 12, + EndLine: 12, + }, + }, + }, } - Bundler2Deps = []types.Dependency{ + Bundler2Deps = []ftypes.Dependency{ { ID: "faker@2.21.0", DependsOn: []string{"i18n@1.10.0"}, @@ -237,28 +237,28 @@ func TestParser_Parse(t *testing.T) { tests := []struct { name string file string - wantLibs []types.Library - wantDeps []types.Dependency + wantPkgs []ftypes.Package + wantDeps []ftypes.Dependency wantErr assert.ErrorAssertionFunc }{ { name: "normal", file: "testdata/Gemfile_normal.lock", - wantLibs: NormalLibs, + wantPkgs: NormalPkgs, wantDeps: NormalDeps, wantErr: assert.NoError, }, { name: "bundler2", file: "testdata/Gemfile_bundler2.lock", - wantLibs: Bundler2Libs, + wantPkgs: Bundler2Pkgs, wantDeps: Bundler2Deps, wantErr: assert.NoError, }, { name: "malformed", file: "testdata/Gemfile_malformed.lock", - wantLibs: []types.Library{}, + wantPkgs: []ftypes.Package{}, wantErr: assert.NoError, }, } @@ -269,11 +269,11 @@ func TestParser_Parse(t *testing.T) { defer f.Close() p := &bundler.Parser{} - gotLibs, gotDeps, err := p.Parse(f) + gotPkgs, gotDeps, err := p.Parse(f) if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.file)) { return } - assert.Equalf(t, tt.wantLibs, gotLibs, "Parse(%v)", tt.file) + assert.Equalf(t, tt.wantPkgs, gotPkgs, "Parse(%v)", tt.file) assert.Equalf(t, tt.wantDeps, gotDeps, "Parse(%v)", tt.file) }) } diff --git a/pkg/dependency/parser/ruby/gemspec/parse.go b/pkg/dependency/parser/ruby/gemspec/parse.go index e458f6bacd0e..6fa0b140deda 100644 --- a/pkg/dependency/parser/ruby/gemspec/parse.go +++ b/pkg/dependency/parser/ruby/gemspec/parse.go @@ -8,7 +8,8 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -43,11 +44,11 @@ var ( type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } -func (p *Parser) Parse(r xio.ReadSeekerAt) (libs []types.Library, deps []types.Dependency, err error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) (pkgs []ftypes.Package, deps []ftypes.Dependency, err error) { var newVar, name, version, license string scanner := bufio.NewScanner(r) @@ -94,11 +95,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) (libs []types.Library, deps []types.D return nil, nil, xerrors.New("failed to parse gemspec") } - return []types.Library{ + return []ftypes.Package{ { - Name: name, - Version: version, - License: license, + Name: name, + Version: version, + Licenses: licensing.SplitLicenses(license), }, }, nil, nil } diff --git a/pkg/dependency/parser/ruby/gemspec/parse_test.go b/pkg/dependency/parser/ruby/gemspec/parse_test.go index 586c0ee6b941..292b6e7a74d7 100644 --- a/pkg/dependency/parser/ruby/gemspec/parse_test.go +++ b/pkg/dependency/parser/ruby/gemspec/parse_test.go @@ -8,50 +8,62 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/ruby/gemspec" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { tests := []struct { name string inputFile string - want []types.Library + want []ftypes.Package wantErr string }{ { name: "happy", inputFile: "testdata/normal00.gemspec", - want: []types.Library{{ - Name: "rake", - Version: "13.0.3", - License: "MIT", - }}, + want: []ftypes.Package{ + { + Name: "rake", + Version: "13.0.3", + Licenses: []string{"MIT"}, + }, + }, }, { name: "another variable name", inputFile: "testdata/normal01.gemspec", - want: []types.Library{{ - Name: "async", - Version: "1.25.0", - }}, + want: []ftypes.Package{ + { + Name: "async", + Version: "1.25.0", + }, + }, }, { name: "license", inputFile: "testdata/license.gemspec", - want: []types.Library{{ - Name: "async", - Version: "1.25.0", - License: "MIT", - }}, + want: []ftypes.Package{ + { + Name: "async", + Version: "1.25.0", + Licenses: []string{"MIT"}, + }, + }, }, { name: "multiple licenses", inputFile: "testdata/multiple_licenses.gemspec", - want: []types.Library{{ - Name: "test-unit", - Version: "3.3.7", - License: "Ruby, BSDL, PSFL", - }}, + want: []ftypes.Package{ + { + Name: "test-unit", + Version: "3.3.7", + Licenses: []string{ + "Ruby", + "BSDL", + "PSFL", + }, + }, + }, }, { name: "malformed variable name", diff --git a/pkg/dependency/parser/rust/binary/parse.go b/pkg/dependency/parser/rust/binary/parse.go index 793b626aa8d2..093299d0a4a0 100644 --- a/pkg/dependency/parser/rust/binary/parse.go +++ b/pkg/dependency/parser/rust/binary/parse.go @@ -7,7 +7,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -32,30 +31,30 @@ func convertError(err error) error { type Parser struct{} -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{} } // Parse scans files to try to report Rust crates and version injected into Rust binaries // via https://github.com/rust-secure-code/cargo-auditable -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { info, err := rustaudit.GetDependencyInfo(r) if err != nil { return nil, nil, convertError(err) } - var libs []types.Library - var deps []types.Dependency + var pkgs []ftypes.Package + var deps []ftypes.Dependency for _, pkg := range info.Packages { if pkg.Kind != rustaudit.Runtime { continue } pkgID := packageID(pkg.Name, pkg.Version) - libs = append(libs, types.Library{ + pkgs = append(pkgs, ftypes.Package{ ID: pkgID, Name: pkg.Name, Version: pkg.Version, - Relationship: lo.Ternary(pkg.Root, types.RelationshipRoot, types.RelationshipUnknown), // TODO: Determine the direct dependencies by checking the dependencies of the root crate + Relationship: lo.Ternary(pkg.Root, ftypes.RelationshipRoot, ftypes.RelationshipUnknown), // TODO: Determine the direct dependencies by checking the dependencies of the root crate }) var childDeps []string @@ -66,14 +65,14 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, } } if len(childDeps) > 0 { - deps = append(deps, types.Dependency{ + deps = append(deps, ftypes.Dependency{ ID: pkgID, DependsOn: childDeps, }) } } - return libs, deps, nil + return pkgs, deps, nil } func packageID(name, version string) string { diff --git a/pkg/dependency/parser/rust/binary/parse_test.go b/pkg/dependency/parser/rust/binary/parse_test.go index 8275a7ea2b72..d914a93ffb4b 100644 --- a/pkg/dependency/parser/rust/binary/parse_test.go +++ b/pkg/dependency/parser/rust/binary/parse_test.go @@ -8,28 +8,28 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/rust/binary" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) // Test binaries generated from cargo-auditable test fixture // https://github.com/rust-secure-code/cargo-auditable/tree/6b77151/cargo-auditable/tests/fixtures/workspace var ( - libs = []types.Library{ + pkgs = []ftypes.Package{ { ID: "crate_with_features@0.1.0", Name: "crate_with_features", Version: "0.1.0", - Relationship: types.RelationshipRoot, + Relationship: ftypes.RelationshipRoot, }, { ID: "library_crate@0.1.0", Name: "library_crate", Version: "0.1.0", - Relationship: types.RelationshipUnknown, + Relationship: ftypes.RelationshipUnknown, }, } - deps = []types.Dependency{ + deps = []ftypes.Dependency{ { ID: "crate_with_features@0.1.0", DependsOn: []string{"library_crate@0.1.0"}, @@ -41,26 +41,26 @@ func TestParse(t *testing.T) { tests := []struct { name string inputFile string - want []types.Library - wantDeps []types.Dependency + want []ftypes.Package + wantDeps []ftypes.Dependency wantErr string }{ { name: "ELF", inputFile: "testdata/test.elf", - want: libs, + want: pkgs, wantDeps: deps, }, { name: "PE", inputFile: "testdata/test.exe", - want: libs, + want: pkgs, wantDeps: deps, }, { name: "Mach-O", inputFile: "testdata/test.macho", - want: libs, + want: pkgs, wantDeps: deps, }, { diff --git a/pkg/dependency/parser/rust/cargo/parse.go b/pkg/dependency/parser/rust/cargo/parse.go index 2fd6686224bc..d3fbbce00bb4 100644 --- a/pkg/dependency/parser/rust/cargo/parse.go +++ b/pkg/dependency/parser/rust/cargo/parse.go @@ -10,7 +10,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -30,13 +29,13 @@ type Parser struct { logger *log.Logger } -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("cargo"), } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var lockfile Lockfile decoder := toml.NewDecoder(r) if _, err := decoder.Decode(&lockfile); err != nil { @@ -52,21 +51,21 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, lineNumIdx := pkgParser.parse() // We need to get version for unique dependencies for lockfile v3 from lockfile.Packages - pkgs := lo.SliceToMap(lockfile.Packages, func(pkg cargoPkg) (string, cargoPkg) { + pkgMap := lo.SliceToMap(lockfile.Packages, func(pkg cargoPkg) (string, cargoPkg) { return pkg.Name, pkg }) - var libs []types.Library - var deps []types.Dependency - for _, pkg := range lockfile.Packages { - pkgID := packageID(pkg.Name, pkg.Version) - lib := types.Library{ + var pkgs ftypes.Packages + var deps ftypes.Dependencies + for _, lpkg := range lockfile.Packages { + pkgID := packageID(lpkg.Name, lpkg.Version) + pkg := ftypes.Package{ ID: pkgID, - Name: pkg.Name, - Version: pkg.Version, + Name: lpkg.Name, + Version: lpkg.Version, } if pos, ok := lineNumIdx[pkgID]; ok { - lib.Locations = []types.Location{ + pkg.Locations = []ftypes.Location{ { StartLine: pos.start, EndLine: pos.end, @@ -74,17 +73,17 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, } } - libs = append(libs, lib) - dep := p.parseDependencies(pkgID, pkg, pkgs) + pkgs = append(pkgs, pkg) + dep := p.parseDependencies(pkgID, lpkg, pkgMap) if dep != nil { deps = append(deps, *dep) } } - sort.Sort(types.Libraries(libs)) - sort.Sort(types.Dependencies(deps)) - return libs, deps, nil + sort.Sort(pkgs) + sort.Sort(deps) + return pkgs, deps, nil } -func (p *Parser) parseDependencies(pkgId string, pkg cargoPkg, pkgs map[string]cargoPkg) *types.Dependency { +func (p *Parser) parseDependencies(pkgId string, pkg cargoPkg, pkgs map[string]cargoPkg) *ftypes.Dependency { var dependOn []string for _, pkgDep := range pkg.Dependencies { @@ -118,7 +117,7 @@ func (p *Parser) parseDependencies(pkgId string, pkg cargoPkg, pkgs map[string]c } if len(dependOn) > 0 { sort.Strings(dependOn) - return &types.Dependency{ + return &ftypes.Dependency{ ID: pkgId, DependsOn: dependOn, } diff --git a/pkg/dependency/parser/rust/cargo/parse_test.go b/pkg/dependency/parser/rust/cargo/parse_test.go index 903cf0190a7d..8472f801d9d0 100644 --- a/pkg/dependency/parser/rust/cargo/parse_test.go +++ b/pkg/dependency/parser/rust/cargo/parse_test.go @@ -5,77 +5,339 @@ import ( "os" "path" "sort" - "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) var ( - cargoNormalLibs = []types.Library{ - {ID: "normal@0.1.0", Name: "normal", Version: "0.1.0", Locations: []types.Location{{StartLine: 8, EndLine: 13}}}, - {ID: "libc@0.2.54", Name: "libc", Version: "0.2.54", Locations: []types.Location{{StartLine: 3, EndLine: 6}}}, - {ID: "typemap@0.3.3", Name: "typemap", Version: "0.3.3", Locations: []types.Location{{StartLine: 20, EndLine: 26}}}, - {ID: "url@1.7.2", Name: "url", Version: "1.7.2", Locations: []types.Location{{StartLine: 43, EndLine: 51}}}, - {ID: "unsafe-any@0.4.2", Name: "unsafe-any", Version: "0.4.2", Locations: []types.Location{{StartLine: 15, EndLine: 18}}}, - {ID: "matches@0.1.8", Name: "matches", Version: "0.1.8", Locations: []types.Location{{StartLine: 33, EndLine: 36}}}, - {ID: "idna@0.1.5", Name: "idna", Version: "0.1.5", Locations: []types.Location{{StartLine: 28, EndLine: 31}}}, - {ID: "percent-encoding@1.0.1", Name: "percent-encoding", Version: "1.0.1", Locations: []types.Location{{StartLine: 38, EndLine: 41}}}, + cargoNormalPkgs = []ftypes.Package{ + { + ID: "normal@0.1.0", + Name: "normal", + Version: "0.1.0", + Locations: []ftypes.Location{ + { + StartLine: 8, + EndLine: 13, + }, + }, + }, + { + ID: "libc@0.2.54", + Name: "libc", + Version: "0.2.54", + Locations: []ftypes.Location{ + { + StartLine: 3, + EndLine: 6, + }, + }, + }, + { + ID: "typemap@0.3.3", + Name: "typemap", + Version: "0.3.3", + Locations: []ftypes.Location{ + { + StartLine: 20, + EndLine: 26, + }, + }, + }, + { + ID: "url@1.7.2", + Name: "url", + Version: "1.7.2", + Locations: []ftypes.Location{ + { + StartLine: 43, + EndLine: 51, + }, + }, + }, + { + ID: "unsafe-any@0.4.2", + Name: "unsafe-any", + Version: "0.4.2", + Locations: []ftypes.Location{ + { + StartLine: 15, + EndLine: 18, + }, + }, + }, + { + ID: "matches@0.1.8", + Name: "matches", + Version: "0.1.8", + Locations: []ftypes.Location{ + { + StartLine: 33, + EndLine: 36, + }, + }, + }, + { + ID: "idna@0.1.5", + Name: "idna", + Version: "0.1.5", + Locations: []ftypes.Location{ + { + StartLine: 28, + EndLine: 31, + }, + }, + }, + { + ID: "percent-encoding@1.0.1", + Name: "percent-encoding", + Version: "1.0.1", + Locations: []ftypes.Location{ + { + StartLine: 38, + EndLine: 41, + }, + }, + }, } - cargoNormalDeps = []types.Dependency{ + cargoNormalDeps = []ftypes.Dependency{ { ID: "normal@0.1.0", - DependsOn: []string{"libc@0.2.54"}}, + DependsOn: []string{"libc@0.2.54"}, + }, { ID: "typemap@0.3.3", DependsOn: []string{"unsafe-any@0.4.2"}, }, { - ID: "url@1.7.2", - DependsOn: []string{"idna@0.1.5", "matches@0.1.8", "percent-encoding@1.0.1"}, + ID: "url@1.7.2", + DependsOn: []string{ + "idna@0.1.5", + "matches@0.1.8", + "percent-encoding@1.0.1", + }, }, } - cargoMixedLibs = []types.Library{ - {ID: "normal@0.1.0", Name: "normal", Version: "0.1.0", Locations: []types.Location{{StartLine: 17, EndLine: 22}}}, - {ID: "libc@0.2.54", Name: "libc", Version: "0.2.54", Locations: []types.Location{{StartLine: 3, EndLine: 6}}}, - {ID: "typemap@0.3.3", Name: "typemap", Version: "0.3.3", Locations: []types.Location{{StartLine: 55, EndLine: 61}}}, - {ID: "url@1.7.2", Name: "url", Version: "1.7.2", Locations: []types.Location{{StartLine: 26, EndLine: 34}}}, - {ID: "unsafe-any@0.4.2", Name: "unsafe-any", Version: "0.4.2", Locations: []types.Location{{StartLine: 9, EndLine: 12}}}, - {ID: "matches@0.1.8", Name: "matches", Version: "0.1.8", Locations: []types.Location{{StartLine: 41, EndLine: 44}}}, - {ID: "idna@0.1.5", Name: "idna", Version: "0.1.5", Locations: []types.Location{{StartLine: 36, EndLine: 39}}}, - {ID: "percent-encoding@1.0.1", Name: "percent-encoding", Version: "1.0.1", Locations: []types.Location{{StartLine: 46, EndLine: 49}}}, + cargoMixedPkgs = []ftypes.Package{ + { + ID: "normal@0.1.0", + Name: "normal", + Version: "0.1.0", + Locations: []ftypes.Location{ + { + StartLine: 17, + EndLine: 22, + }, + }, + }, + { + ID: "libc@0.2.54", + Name: "libc", + Version: "0.2.54", + Locations: []ftypes.Location{ + { + StartLine: 3, + EndLine: 6, + }, + }, + }, + { + ID: "typemap@0.3.3", + Name: "typemap", + Version: "0.3.3", + Locations: []ftypes.Location{ + { + StartLine: 55, + EndLine: 61, + }, + }, + }, + { + ID: "url@1.7.2", + Name: "url", + Version: "1.7.2", + Locations: []ftypes.Location{ + { + StartLine: 26, + EndLine: 34, + }, + }, + }, + { + ID: "unsafe-any@0.4.2", + Name: "unsafe-any", + Version: "0.4.2", + Locations: []ftypes.Location{ + { + StartLine: 9, + EndLine: 12, + }, + }, + }, + { + ID: "matches@0.1.8", + Name: "matches", + Version: "0.1.8", + Locations: []ftypes.Location{ + { + StartLine: 41, + EndLine: 44, + }, + }, + }, + { + ID: "idna@0.1.5", + Name: "idna", + Version: "0.1.5", + Locations: []ftypes.Location{ + { + StartLine: 36, + EndLine: 39, + }, + }, + }, + { + ID: "percent-encoding@1.0.1", + Name: "percent-encoding", + Version: "1.0.1", + Locations: []ftypes.Location{ + { + StartLine: 46, + EndLine: 49, + }, + }, + }, } - - cargoV3Libs = []types.Library{ - {ID: "aho-corasick@0.7.20", Name: "aho-corasick", Version: "0.7.20", Locations: []types.Location{{StartLine: 5, EndLine: 12}}}, - {ID: "app@0.1.0", Name: "app", Version: "0.1.0", Locations: []types.Location{{StartLine: 14, EndLine: 21}}}, - {ID: "libc@0.2.140", Name: "libc", Version: "0.2.140", Locations: []types.Location{{StartLine: 23, EndLine: 27}}}, - {ID: "memchr@1.0.2", Name: "memchr", Version: "1.0.2", Locations: []types.Location{{StartLine: 29, EndLine: 36}}}, - {ID: "memchr@2.5.0", Name: "memchr", Version: "2.5.0", Locations: []types.Location{{StartLine: 38, EndLine: 42}}}, - {ID: "regex@1.7.3", Name: "regex", Version: "1.7.3", Locations: []types.Location{{StartLine: 44, EndLine: 53}}}, - {ID: "regex-syntax@0.5.6", Name: "regex-syntax", Version: "0.5.6", Locations: []types.Location{{StartLine: 55, EndLine: 62}}}, - {ID: "regex-syntax@0.6.29", Name: "regex-syntax", Version: "0.6.29", Locations: []types.Location{{StartLine: 64, EndLine: 68}}}, - {ID: "ucd-util@0.1.10", Name: "ucd-util", Version: "0.1.10", Locations: []types.Location{{StartLine: 70, EndLine: 74}}}, + cargoV3Pkgs = []ftypes.Package{ + { + ID: "aho-corasick@0.7.20", + Name: "aho-corasick", + Version: "0.7.20", + Locations: []ftypes.Location{ + { + StartLine: 5, + EndLine: 12, + }, + }, + }, + { + ID: "app@0.1.0", + Name: "app", + Version: "0.1.0", + Locations: []ftypes.Location{ + { + StartLine: 14, + EndLine: 21, + }, + }, + }, + { + ID: "libc@0.2.140", + Name: "libc", + Version: "0.2.140", + Locations: []ftypes.Location{ + { + StartLine: 23, + EndLine: 27, + }, + }, + }, + { + ID: "memchr@1.0.2", + Name: "memchr", + Version: "1.0.2", + Locations: []ftypes.Location{ + { + StartLine: 29, + EndLine: 36, + }, + }, + }, + { + ID: "memchr@2.5.0", + Name: "memchr", + Version: "2.5.0", + Locations: []ftypes.Location{ + { + StartLine: 38, + EndLine: 42, + }, + }, + }, + { + ID: "regex@1.7.3", + Name: "regex", + Version: "1.7.3", + Locations: []ftypes.Location{ + { + StartLine: 44, + EndLine: 53, + }, + }, + }, + { + ID: "regex-syntax@0.5.6", + Name: "regex-syntax", + Version: "0.5.6", + Locations: []ftypes.Location{ + { + StartLine: 55, + EndLine: 62, + }, + }, + }, + { + ID: "regex-syntax@0.6.29", + Name: "regex-syntax", + Version: "0.6.29", + Locations: []ftypes.Location{ + { + StartLine: 64, + EndLine: 68, + }, + }, + }, + { + ID: "ucd-util@0.1.10", + Name: "ucd-util", + Version: "0.1.10", + Locations: []ftypes.Location{ + { + StartLine: 70, + EndLine: 74, + }, + }, + }, } - cargoV3Deps = []types.Dependency{ + cargoV3Deps = []ftypes.Dependency{ { ID: "aho-corasick@0.7.20", - DependsOn: []string{"memchr@2.5.0"}}, + DependsOn: []string{"memchr@2.5.0"}, + }, { - ID: "app@0.1.0", - DependsOn: []string{"memchr@1.0.2", "regex-syntax@0.5.6", "regex@1.7.3"}, + ID: "app@0.1.0", + DependsOn: []string{ + "memchr@1.0.2", + "regex-syntax@0.5.6", + "regex@1.7.3", + }, }, { ID: "memchr@1.0.2", DependsOn: []string{"libc@0.2.140"}, }, { - ID: "regex@1.7.3", - DependsOn: []string{"aho-corasick@0.7.20", "memchr@2.5.0", "regex-syntax@0.6.29"}, + ID: "regex@1.7.3", + DependsOn: []string{ + "aho-corasick@0.7.20", + "memchr@2.5.0", + "regex-syntax@0.6.29", + }, }, { ID: "regex-syntax@0.5.6", @@ -87,25 +349,25 @@ var ( func TestParse(t *testing.T) { vectors := []struct { file string // Test input file - wantLibs []types.Library - wantDeps []types.Dependency + wantPkgs []ftypes.Package + wantDeps []ftypes.Dependency wantErr assert.ErrorAssertionFunc }{ { file: "testdata/cargo_normal.lock", - wantLibs: cargoNormalLibs, + wantPkgs: cargoNormalPkgs, wantDeps: cargoNormalDeps, wantErr: assert.NoError, }, { file: "testdata/cargo_mixed.lock", - wantLibs: cargoMixedLibs, + wantPkgs: cargoMixedPkgs, wantDeps: cargoNormalDeps, wantErr: assert.NoError, }, { file: "testdata/cargo_v3.lock", - wantLibs: cargoV3Libs, + wantPkgs: cargoV3Pkgs, wantDeps: cargoV3Deps, wantErr: assert.NoError, }, @@ -120,7 +382,7 @@ func TestParse(t *testing.T) { f, err := os.Open(v.file) require.NoError(t, err) - gotLibs, gotDeps, err := NewParser().Parse(f) + gotPkgs, gotDeps, err := NewParser().Parse(f) if !v.wantErr(t, err, fmt.Sprintf("Parse(%v)", v.file)) { return @@ -130,23 +392,10 @@ func TestParse(t *testing.T) { return } - sortLibs(v.wantLibs) - sortDeps(v.wantDeps) - - assert.Equalf(t, v.wantLibs, gotLibs, "Parse libraries(%v)", v.file) + sort.Sort(ftypes.Packages(v.wantPkgs)) + sort.Sort(ftypes.Dependencies(v.wantDeps)) + assert.Equalf(t, v.wantPkgs, gotPkgs, "Parse libraries(%v)", v.file) assert.Equalf(t, v.wantDeps, gotDeps, "Parse dependencies(%v)", v.file) }) } } - -func sortLibs(libs []types.Library) { - sort.Slice(libs, func(i, j int) bool { - return strings.Compare(libs[i].ID, libs[j].ID) < 0 - }) -} - -func sortDeps(deps []types.Dependency) { - sort.Slice(deps, func(i, j int) bool { - return strings.Compare(deps[i].ID, deps[j].ID) < 0 - }) -} diff --git a/pkg/dependency/parser/swift/cocoapods/parse.go b/pkg/dependency/parser/swift/cocoapods/parse.go index ae71bc09a3d7..eb5a960679f7 100644 --- a/pkg/dependency/parser/swift/cocoapods/parse.go +++ b/pkg/dependency/parser/swift/cocoapods/parse.go @@ -10,7 +10,6 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -20,7 +19,7 @@ type Parser struct { logger *log.Logger } -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("cocoapods"), } @@ -30,32 +29,32 @@ type lockFile struct { Pods []any `yaml:"PODS"` // pod can be string or map[string]interface{} } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { lock := &lockFile{} decoder := yaml.NewDecoder(r) if err := decoder.Decode(&lock); err != nil { return nil, nil, xerrors.Errorf("failed to decode cocoapods lock file: %s", err.Error()) } - parsedDeps := make(map[string]types.Library) // dependency name => Library - directDeps := make(map[string][]string) // dependency name => slice of child dependency names + parsedDeps := make(map[string]ftypes.Package) // dependency name => Package + directDeps := make(map[string][]string) // dependency name => slice of child dependency names for _, pod := range lock.Pods { switch dep := pod.(type) { case string: // dependency with version number - lib, err := parseDep(dep) + pkg, err := parseDep(dep) if err != nil { p.logger.Debug("Dependency parse error", log.Err(err)) continue } - parsedDeps[lib.Name] = lib + parsedDeps[pkg.Name] = pkg case map[string]interface{}: // dependency with its child dependencies for dep, childDeps := range dep { - lib, err := parseDep(dep) + pkg, err := parseDep(dep) if err != nil { p.logger.Debug("Dependency parse error", log.Err(err)) continue } - parsedDeps[lib.Name] = lib + parsedDeps[pkg.Name] = pkg children, ok := childDeps.([]interface{}) if !ok { @@ -67,30 +66,30 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, if !ok { return nil, nil, xerrors.Errorf("must be string: %q", childDep) } - directDeps[lib.Name] = append(directDeps[lib.Name], strings.Fields(s)[0]) + directDeps[pkg.Name] = append(directDeps[pkg.Name], strings.Fields(s)[0]) } } } } - var deps []types.Dependency + var deps ftypes.Dependencies for dep, childDeps := range directDeps { var dependsOn []string // find versions for child dependencies for _, childDep := range childDeps { dependsOn = append(dependsOn, packageID(childDep, parsedDeps[childDep].Version)) } - deps = append(deps, types.Dependency{ + deps = append(deps, ftypes.Dependency{ ID: parsedDeps[dep].ID, DependsOn: dependsOn, }) } - sort.Sort(types.Dependencies(deps)) - return utils.UniqueLibraries(maps.Values(parsedDeps)), deps, nil + sort.Sort(deps) + return utils.UniquePackages(maps.Values(parsedDeps)), deps, nil } -func parseDep(dep string) (types.Library, error) { +func parseDep(dep string) (ftypes.Package, error) { // dep example: // 'AppCenter (4.2.0)' // direct dep examples: @@ -99,18 +98,18 @@ func parseDep(dep string) (types.Library, error) { // 'AppCenter/Analytics (-> 4.2.0)' ss := strings.Split(dep, " (") if len(ss) != 2 { - return types.Library{}, xerrors.Errorf("Unable to determine cocoapods dependency: %q", dep) + return ftypes.Package{}, xerrors.Errorf("Unable to determine cocoapods dependency: %q", dep) } name := ss[0] version := strings.Trim(strings.TrimSpace(ss[1]), "()") - lib := types.Library{ + pkg := ftypes.Package{ ID: packageID(name, version), Name: name, Version: version, } - return lib, nil + return pkg, nil } func packageID(name, version string) string { diff --git a/pkg/dependency/parser/swift/cocoapods/parse_test.go b/pkg/dependency/parser/swift/cocoapods/parse_test.go index 3a0823338713..f81b81929654 100644 --- a/pkg/dependency/parser/swift/cocoapods/parse_test.go +++ b/pkg/dependency/parser/swift/cocoapods/parse_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/aquasecurity/trivy/pkg/dependency/parser/swift/cocoapods" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -14,13 +14,18 @@ func TestParse(t *testing.T) { tests := []struct { name string inputFile string // Test input file - wantLibs []types.Library - wantDeps []types.Dependency + wantPkgs []ftypes.Package + wantDeps []ftypes.Dependency }{ { name: "happy path", inputFile: "testdata/happy.lock", - wantLibs: []types.Library{ + wantPkgs: []ftypes.Package{ + { + ID: "AppCenter@4.2.0", + Name: "AppCenter", + Version: "4.2.0", + }, { ID: "AppCenter/Analytics@4.2.0", Name: "AppCenter/Analytics", @@ -36,18 +41,13 @@ func TestParse(t *testing.T) { Name: "AppCenter/Crashes", Version: "4.2.0", }, - { - ID: "AppCenter@4.2.0", - Name: "AppCenter", - Version: "4.2.0", - }, { ID: "KeychainAccess@4.2.1", Name: "KeychainAccess", Version: "4.2.1", }, }, - wantDeps: []types.Dependency{ + wantDeps: []ftypes.Dependency{ { ID: "AppCenter/Analytics@4.2.0", DependsOn: []string{ @@ -85,10 +85,10 @@ func TestParse(t *testing.T) { require.NoError(t, err) defer f.Close() - gotLibs, gotDeps, err := cocoapods.NewParser().Parse(f) + gotPkgs, gotDeps, err := cocoapods.NewParser().Parse(f) require.NoError(t, err) - assert.Equal(t, tt.wantLibs, gotLibs) + assert.Equal(t, tt.wantPkgs, gotPkgs) assert.Equal(t, tt.wantDeps, gotDeps) }) } diff --git a/pkg/dependency/parser/swift/swift/parse.go b/pkg/dependency/parser/swift/swift/parse.go index 74a507f847fb..aefa2a066faf 100644 --- a/pkg/dependency/parser/swift/swift/parse.go +++ b/pkg/dependency/parser/swift/swift/parse.go @@ -10,7 +10,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" - "github.com/aquasecurity/trivy/pkg/dependency/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -21,13 +20,13 @@ type Parser struct { logger *log.Logger } -func NewParser() types.Parser { +func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("swift"), } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { var lockFile LockFile input, err := io.ReadAll(r) if err != nil { @@ -37,13 +36,13 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return nil, nil, xerrors.Errorf("decode error: %w", err) } - var libs types.Libraries + var pkgs ftypes.Packages pins := lockFile.Object.Pins if lockFile.Version > 1 { pins = lockFile.Pins } for _, pin := range pins { - name := libraryName(pin, lockFile.Version) + name := pkgName(pin, lockFile.Version) // Skip packages for which we cannot resolve the version if pin.State.Version == "" && pin.State.Branch == "" { @@ -55,11 +54,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // e.g. https://github.com/element-hq/element-ios/blob/6a9bcc88ea37147efba8f0a7bcf3ec187f4a4011/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved#L84-L92 version := lo.Ternary(pin.State.Version != "", pin.State.Version, pin.State.Branch) - libs = append(libs, types.Library{ + pkgs = append(pkgs, ftypes.Package{ ID: dependency.ID(ftypes.Swift, name, version), Name: name, Version: version, - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: pin.StartLine, EndLine: pin.EndLine, @@ -67,11 +66,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, }, }) } - sort.Sort(libs) - return libs, nil, nil + sort.Sort(pkgs) + return pkgs, nil, nil } -func libraryName(pin Pin, lockVersion int) string { +func pkgName(pin Pin, lockVersion int) string { // Package.resolved v1 uses `RepositoryURL` // v2 uses `Location` name := pin.RepositoryURL diff --git a/pkg/dependency/parser/swift/swift/parse_test.go b/pkg/dependency/parser/swift/swift/parse_test.go index b1d3d127a85a..cd90d26fc6e4 100644 --- a/pkg/dependency/parser/swift/swift/parse_test.go +++ b/pkg/dependency/parser/swift/swift/parse_test.go @@ -1,7 +1,7 @@ package swift import ( - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" "os" "testing" @@ -11,7 +11,7 @@ func TestParser_Parse(t *testing.T) { tests := []struct { name string inputFile string - want []types.Library + want []ftypes.Package }{ // docker run -it --rm swift@sha256:3c62ac97506ecf19ca15e4db57d7930e6a71559b23b19aa57e13d380133a54db // mkdir app && cd app @@ -22,60 +22,60 @@ func TestParser_Parse(t *testing.T) { { name: "happy path v1", inputFile: "testdata/happy-v1-Package.resolved", - want: []types.Library{ + want: []ftypes.Package{ { ID: "github.com/Quick/Nimble@9.2.1", Name: "github.com/Quick/Nimble", Version: "9.2.1", - Locations: []types.Location{{StartLine: 4, EndLine: 12}}, + Locations: []ftypes.Location{{StartLine: 4, EndLine: 12}}, }, { ID: "github.com/ReactiveCocoa/ReactiveSwift@7.1.1", Name: "github.com/ReactiveCocoa/ReactiveSwift", Version: "7.1.1", - Locations: []types.Location{{StartLine: 13, EndLine: 21}}, + Locations: []ftypes.Location{{StartLine: 13, EndLine: 21}}, }, }, }, { name: "happy path v2", inputFile: "testdata/happy-v2-Package.resolved", - want: []types.Library{ + want: []ftypes.Package{ { ID: "github.com/Quick/Nimble@9.2.1", Name: "github.com/Quick/Nimble", Version: "9.2.1", - Locations: []types.Location{{StartLine: 21, EndLine: 29}}, + Locations: []ftypes.Location{{StartLine: 21, EndLine: 29}}, }, { ID: "github.com/Quick/Quick@7.2.0", Name: "github.com/Quick/Quick", Version: "7.2.0", - Locations: []types.Location{{StartLine: 30, EndLine: 38}}, + Locations: []ftypes.Location{{StartLine: 30, EndLine: 38}}, }, { ID: "github.com/ReactiveCocoa/ReactiveSwift@7.1.1", Name: "github.com/ReactiveCocoa/ReactiveSwift", Version: "7.1.1", - Locations: []types.Location{{StartLine: 39, EndLine: 47}}, + Locations: []ftypes.Location{{StartLine: 39, EndLine: 47}}, }, { ID: "github.com/element-hq/swift-ogg@0.0.1", Name: "github.com/element-hq/swift-ogg", Version: "0.0.1", - Locations: []types.Location{{StartLine: 48, EndLine: 56}}, + Locations: []ftypes.Location{{StartLine: 48, EndLine: 56}}, }, { ID: "github.com/mattgallagher/CwlCatchException@2.1.2", Name: "github.com/mattgallagher/CwlCatchException", Version: "2.1.2", - Locations: []types.Location{{StartLine: 3, EndLine: 11}}, + Locations: []ftypes.Location{{StartLine: 3, EndLine: 11}}, }, { ID: "github.com/mattgallagher/CwlPreconditionTesting@2.1.2", Name: "github.com/mattgallagher/CwlPreconditionTesting", Version: "2.1.2", - Locations: []types.Location{{StartLine: 12, EndLine: 20}}, + Locations: []ftypes.Location{{StartLine: 12, EndLine: 20}}, }, }, }, @@ -92,9 +92,9 @@ func TestParser_Parse(t *testing.T) { f, err := os.Open(tt.inputFile) assert.NoError(t, err) - libs, _, err := parser.Parse(f) + gotPkgs, _, err := parser.Parse(f) assert.NoError(t, err) - assert.Equal(t, tt.want, libs) + assert.Equal(t, tt.want, gotPkgs) }) } } diff --git a/pkg/dependency/parser/utils/utils.go b/pkg/dependency/parser/utils/utils.go index e89bc4ca3b65..f22e994a7cb0 100644 --- a/pkg/dependency/parser/utils/utils.go +++ b/pkg/dependency/parser/utils/utils.go @@ -6,7 +6,7 @@ import ( "golang.org/x/exp/maps" - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func UniqueStrings(ss []string) []string { @@ -22,36 +22,36 @@ func UniqueStrings(ss []string) []string { return results } -func UniqueLibraries(libs []types.Library) []types.Library { - if len(libs) == 0 { +func UniquePackages(pkgs []ftypes.Package) []ftypes.Package { + if len(pkgs) == 0 { return nil } - unique := make(map[string]types.Library) - for _, lib := range libs { - identifier := fmt.Sprintf("%s@%s", lib.Name, lib.Version) + unique := make(map[string]ftypes.Package) + for _, pkg := range pkgs { + identifier := fmt.Sprintf("%s@%s", pkg.Name, pkg.Version) if l, ok := unique[identifier]; !ok { - unique[identifier] = lib + unique[identifier] = pkg } else { - // There are times when we get 2 same libraries as root and dev dependencies. + // There are times when we get 2 same packages as root and dev dependencies. // https://github.com/aquasecurity/trivy/issues/5532 // In these cases, we need to mark the dependency as a root dependency. - if !lib.Dev { - l.Dev = lib.Dev + if !pkg.Dev { + l.Dev = pkg.Dev unique[identifier] = l } - if len(lib.Locations) > 0 { + if len(pkg.Locations) > 0 { // merge locations - l.Locations = append(l.Locations, lib.Locations...) + l.Locations = append(l.Locations, pkg.Locations...) sort.Sort(l.Locations) unique[identifier] = l } } } - libSlice := maps.Values(unique) - sort.Sort(types.Libraries(libSlice)) + pkgSlice := maps.Values(unique) + sort.Sort(ftypes.Packages(pkgSlice)) - return libSlice + return pkgSlice } func MergeMaps(parent, child map[string]string) map[string]string { diff --git a/pkg/dependency/parser/utils/utils_test.go b/pkg/dependency/parser/utils/utils_test.go index ca8c8ab67568..ed5d84135c19 100644 --- a/pkg/dependency/parser/utils/utils_test.go +++ b/pkg/dependency/parser/utils/utils_test.go @@ -1,7 +1,7 @@ package utils import ( - "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/require" "testing" ) @@ -9,17 +9,17 @@ import ( func TestUniqueLibraries(t *testing.T) { tests := []struct { name string - libs []types.Library - wantLibs []types.Library + pkgs []ftypes.Package + wantPkgs []ftypes.Package }{ { name: "happy path merge locations", - libs: []types.Library{ + pkgs: []ftypes.Package{ { ID: "asn1@0.2.6", Name: "asn1", Version: "0.2.6", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 10, EndLine: 14, @@ -30,7 +30,7 @@ func TestUniqueLibraries(t *testing.T) { ID: "asn1@0.2.6", Name: "asn1", Version: "0.2.6", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 24, EndLine: 30, @@ -38,12 +38,12 @@ func TestUniqueLibraries(t *testing.T) { }, }, }, - wantLibs: []types.Library{ + wantPkgs: []ftypes.Package{ { ID: "asn1@0.2.6", Name: "asn1", Version: "0.2.6", - Locations: []types.Location{ + Locations: []ftypes.Location{ { StartLine: 10, EndLine: 14, @@ -58,7 +58,7 @@ func TestUniqueLibraries(t *testing.T) { }, { name: "happy path Dev and Root deps", - libs: []types.Library{ + pkgs: []ftypes.Package{ { ID: "asn1@0.2.6", Name: "asn1", @@ -72,7 +72,7 @@ func TestUniqueLibraries(t *testing.T) { Dev: false, }, }, - wantLibs: []types.Library{ + wantPkgs: []ftypes.Package{ { ID: "asn1@0.2.6", Name: "asn1", @@ -83,7 +83,7 @@ func TestUniqueLibraries(t *testing.T) { }, { name: "happy path Root and Dev deps", - libs: []types.Library{ + pkgs: []ftypes.Package{ { ID: "asn1@0.2.6", Name: "asn1", @@ -97,7 +97,7 @@ func TestUniqueLibraries(t *testing.T) { Dev: true, }, }, - wantLibs: []types.Library{ + wantPkgs: []ftypes.Package{ { ID: "asn1@0.2.6", Name: "asn1", @@ -110,8 +110,8 @@ func TestUniqueLibraries(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotLibs := UniqueLibraries(tt.libs) - require.Equal(t, tt.wantLibs, gotLibs) + gotPkgs := UniquePackages(tt.pkgs) + require.Equal(t, tt.wantPkgs, gotPkgs) }) } } diff --git a/pkg/dependency/types/types.go b/pkg/dependency/types/types.go deleted file mode 100644 index be7022fc301a..000000000000 --- a/pkg/dependency/types/types.go +++ /dev/null @@ -1,127 +0,0 @@ -package types - -import ( - "encoding/json" - - "golang.org/x/xerrors" - - xio "github.com/aquasecurity/trivy/pkg/x/io" -) - -type Library struct { - ID string `json:",omitempty"` - Name string - Version string - Dev bool `json:",omitempty"` - Relationship Relationship `json:",omitempty"` - License string `json:",omitempty"` - ExternalReferences []ExternalRef `json:",omitempty"` - Locations Locations `json:",omitempty"` - FilePath string `json:",omitempty"` // Required to show nested jars -} - -type Libraries []Library - -func (libs Libraries) Len() int { return len(libs) } -func (libs Libraries) Less(i, j int) bool { - switch { - case libs[i].Relationship != libs[j].Relationship: - if libs[i].Relationship == RelationshipUnknown { - return false - } else if libs[j].Relationship == RelationshipUnknown { - return true - } - return libs[i].Relationship < libs[j].Relationship - case libs[i].ID != libs[j].ID: // ID could be empty - return libs[i].ID < libs[j].ID - case libs[i].Name != libs[j].Name: // Name could be the same - return libs[i].Name < libs[j].Name - } - return libs[i].Version < libs[j].Version -} -func (libs Libraries) Swap(i, j int) { libs[i], libs[j] = libs[j], libs[i] } - -// Location in lock file -type Location struct { - StartLine int `json:",omitempty"` - EndLine int `json:",omitempty"` -} - -type Locations []Location - -func (locs Locations) Len() int { return len(locs) } -func (locs Locations) Less(i, j int) bool { - return locs[i].StartLine < locs[j].StartLine -} -func (locs Locations) Swap(i, j int) { locs[i], locs[j] = locs[j], locs[i] } - -type ExternalRef struct { - Type RefType - URL string -} - -type Dependency struct { - ID string - DependsOn []string -} - -type Dependencies []Dependency - -func (deps Dependencies) Len() int { return len(deps) } -func (deps Dependencies) Less(i, j int) bool { - return deps[i].ID < deps[j].ID -} -func (deps Dependencies) Swap(i, j int) { deps[i], deps[j] = deps[j], deps[i] } - -type Parser interface { - // Parse parses the dependency file - Parse(r xio.ReadSeekerAt) ([]Library, []Dependency, error) -} - -type RefType string - -const ( - RefVCS RefType = "vcs" - RefOther RefType = "other" -) - -type Relationship int - -const ( - RelationshipUnknown Relationship = iota - RelationshipRoot - RelationshipDirect - RelationshipIndirect -) - -var relationshipNames = [...]string{ - "unknown", - "root", - "direct", - "indirect", -} - -func (r Relationship) String() string { - if r <= RelationshipUnknown || int(r) >= len(relationshipNames) { - return "unknown" - } - return relationshipNames[r] -} - -func (r Relationship) MarshalJSON() ([]byte, error) { - return json.Marshal(r.String()) -} - -func (r *Relationship) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - for i, name := range relationshipNames { - if s == name { - *r = Relationship(i) - return nil - } - } - return xerrors.Errorf("invalid relationship (%s)", s) -} diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go index 9312a90ad283..56bb518f1b73 100644 --- a/pkg/fanal/analyzer/analyzer.go +++ b/pkg/fanal/analyzer/analyzer.go @@ -202,7 +202,7 @@ func (r *AnalysisResult) Sort() { }) for _, app := range r.Applications { - sort.Sort(app.Libraries) + sort.Sort(app.Packages) } // Custom resources @@ -475,12 +475,12 @@ func (ag AnalyzerGroup) PostAnalyze(ctx context.Context, compositeFS *CompositeF skippedFiles := result.SystemInstalledFiles for _, app := range result.Applications { skippedFiles = append(skippedFiles, app.FilePath) - for _, lib := range app.Libraries { + for _, pkg := range app.Packages { // The analysis result could contain packages listed in SBOM. // The files of those packages don't have to be analyzed. // This is especially helpful for expensive post-analyzers such as the JAR analyzer. - if lib.FilePath != "" { - skippedFiles = append(skippedFiles, lib.FilePath) + if pkg.FilePath != "" { + skippedFiles = append(skippedFiles, pkg.FilePath) } } } diff --git a/pkg/fanal/analyzer/analyzer_test.go b/pkg/fanal/analyzer/analyzer_test.go index df1398155b2c..169bbce3e8e6 100644 --- a/pkg/fanal/analyzer/analyzer_test.go +++ b/pkg/fanal/analyzer/analyzer_test.go @@ -69,7 +69,7 @@ func TestAnalysisResult_Merge(t *testing.T) { { Type: "bundler", FilePath: "app/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "rails", Version: "5.0.0", @@ -95,7 +95,7 @@ func TestAnalysisResult_Merge(t *testing.T) { { Type: "bundler", FilePath: "app2/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "nokogiri", Version: "1.0.0", @@ -134,7 +134,7 @@ func TestAnalysisResult_Merge(t *testing.T) { { Type: "bundler", FilePath: "app/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "rails", Version: "5.0.0", @@ -144,7 +144,7 @@ func TestAnalysisResult_Merge(t *testing.T) { { Type: "bundler", FilePath: "app2/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "nokogiri", Version: "1.0.0", @@ -378,7 +378,7 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) { { Type: "bundler", FilePath: "/app/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "actioncable@5.2.3", Name: "actioncable", @@ -441,7 +441,7 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) { { Type: "bundler", FilePath: "/app/Gemfile-dev.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "actioncable@5.2.3", Name: "actioncable", @@ -576,7 +576,7 @@ func TestAnalyzerGroup_PostAnalyze(t *testing.T) { { Type: types.Jar, FilePath: "testdata/post-apps/jar/jackson-annotations-2.15.0-rc2.jar", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "com.fasterxml.jackson.core:jackson-annotations", Version: "2.15.0-rc2", @@ -596,7 +596,7 @@ func TestAnalyzerGroup_PostAnalyze(t *testing.T) { { Type: types.Poetry, FilePath: "testdata/post-apps/poetry/happy/poetry.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "certifi@2022.12.7", Name: "certifi", diff --git a/pkg/fanal/analyzer/language/analyze.go b/pkg/fanal/analyzer/language/analyze.go index 84b0261f86b7..6ecbaba65752 100644 --- a/pkg/fanal/analyzer/language/analyze.go +++ b/pkg/fanal/analyzer/language/analyze.go @@ -2,11 +2,9 @@ package language import ( "io" - "strings" "golang.org/x/xerrors" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/digest" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -15,8 +13,13 @@ import ( xio "github.com/aquasecurity/trivy/pkg/x/io" ) +type Parser interface { + // Parse parses the dependency file + Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, error) +} + // Analyze returns an analysis result of the lock file -func Analyze(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser godeptypes.Parser) (*analyzer.AnalysisResult, error) { +func Analyze(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser Parser) (*analyzer.AnalysisResult, error) { app, err := Parse(fileType, filePath, r, parser) if err != nil { return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err) @@ -30,7 +33,7 @@ func Analyze(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parse } // AnalyzePackage returns an analysis result of the package file other than lock files -func AnalyzePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser godeptypes.Parser, checksum bool) (*analyzer.AnalysisResult, error) { +func AnalyzePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser Parser, checksum bool) (*analyzer.AnalysisResult, error) { app, err := ParsePackage(fileType, filePath, r, parser, checksum) if err != nil { return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err) @@ -44,24 +47,24 @@ func AnalyzePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt } // Parse returns a parsed result of the lock file -func Parse(fileType types.LangType, filePath string, r io.Reader, parser godeptypes.Parser) (*types.Application, error) { +func Parse(fileType types.LangType, filePath string, r io.Reader, parser Parser) (*types.Application, error) { rr, err := xio.NewReadSeekerAt(r) if err != nil { return nil, xerrors.Errorf("reader error: %w", err) } - parsedLibs, parsedDependencies, err := parser.Parse(rr) + parsedPkgs, parsedDependencies, err := parser.Parse(rr) if err != nil { return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err) } // The file path of each library should be empty in case of dependency list such as lock file // since they all will be the same path. - return toApplication(fileType, filePath, "", nil, parsedLibs, parsedDependencies), nil + return toApplication(fileType, filePath, "", nil, parsedPkgs, parsedDependencies), nil } // ParsePackage returns a parsed result of the package file -func ParsePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser godeptypes.Parser, checksum bool) (*types.Application, error) { - parsedLibs, parsedDependencies, err := parser.Parse(r) +func ParsePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt, parser Parser, checksum bool) (*types.Application, error) { + parsedPkgs, parsedDependencies, err := parser.Parse(r) if err != nil { return nil, xerrors.Errorf("failed to parse %s: %w", filePath, err) } @@ -73,11 +76,11 @@ func ParsePackage(fileType types.LangType, filePath string, r xio.ReadSeekerAt, // The file path of each library should be empty in case of dependency list such as lock file // since they all will be the same path. - return toApplication(fileType, filePath, filePath, r, parsedLibs, parsedDependencies), nil + return toApplication(fileType, filePath, filePath, r, parsedPkgs, parsedDependencies), nil } -func toApplication(fileType types.LangType, filePath, libFilePath string, r xio.ReadSeekerAt, libs []godeptypes.Library, depGraph []godeptypes.Dependency) *types.Application { - if len(libs) == 0 { +func toApplication(fileType types.LangType, filePath, libFilePath string, r xio.ReadSeekerAt, pkgs []types.Package, depGraph []types.Dependency) *types.Application { + if len(pkgs) == 0 { return nil } @@ -92,50 +95,24 @@ func toApplication(fileType types.LangType, filePath, libFilePath string, r xio. deps[dep.ID] = dep.DependsOn } - var pkgs []types.Package - for _, lib := range libs { - var licenses []string - if lib.License != "" { - licenses = licensing.SplitLicenses(lib.License) - for i, license := range licenses { - licenses[i] = licensing.Normalize(strings.TrimSpace(license)) - } - } - var locs []types.Location - for _, loc := range lib.Locations { - l := types.Location{ - StartLine: loc.StartLine, - EndLine: loc.EndLine, - } - locs = append(locs, l) - } - + for i, pkg := range pkgs { // This file path is populated for virtual file paths within archives, such as nested JAR files. - libPath := libFilePath - if lib.FilePath != "" { - libPath = lib.FilePath + if pkg.FilePath == "" { + pkgs[i].FilePath = libFilePath } + pkgs[i].DependsOn = deps[pkg.ID] + pkgs[i].Digest = d + pkgs[i].Indirect = isIndirect(pkg.Relationship) // For backward compatibility - newPkg := types.Package{ - ID: lib.ID, - Name: lib.Name, - Version: lib.Version, - Dev: lib.Dev, - FilePath: libPath, - Indirect: isIndirect(lib.Relationship), // For backward compatibility - Relationship: lib.Relationship, - Licenses: licenses, - DependsOn: deps[lib.ID], - Locations: locs, - Digest: d, + for j, license := range pkg.Licenses { + pkgs[i].Licenses[j] = licensing.Normalize(license) } - pkgs = append(pkgs, newPkg) } return &types.Application{ - Type: fileType, - FilePath: filePath, - Libraries: pkgs, + Type: fileType, + FilePath: filePath, + Packages: pkgs, } } diff --git a/pkg/fanal/analyzer/language/analyze_test.go b/pkg/fanal/analyzer/language/analyze_test.go index 260c86b59ae8..8b6c9dad44de 100644 --- a/pkg/fanal/analyzer/language/analyze_test.go +++ b/pkg/fanal/analyzer/language/analyze_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/xerrors" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -20,13 +19,13 @@ type mockParser struct { t *testing.T } -func (p *mockParser) Parse(r xio.ReadSeekerAt) ([]godeptypes.Library, []godeptypes.Dependency, error) { +func (p *mockParser) Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, error) { b, err := io.ReadAll(r) require.NoError(p.t, err) switch string(b) { case "happy": - return []godeptypes.Library{ + return []types.Package{ { Name: "test", Version: "1.2.3", @@ -63,7 +62,7 @@ func TestAnalyze(t *testing.T) { { Type: types.GoBinary, FilePath: "app/myweb", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "test", Version: "1.2.3", diff --git a/pkg/fanal/analyzer/language/c/conan/conan.go b/pkg/fanal/analyzer/language/c/conan/conan.go index 891ebac08695..50252c2bb603 100644 --- a/pkg/fanal/analyzer/language/c/conan/conan.go +++ b/pkg/fanal/analyzer/language/c/conan/conan.go @@ -14,7 +14,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/c/conan" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -33,7 +32,7 @@ const ( // conanLockAnalyzer analyzes conan.lock type conanLockAnalyzer struct { logger *log.Logger - parser godeptypes.Parser + parser language.Parser } func newConanLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { @@ -65,15 +64,15 @@ func (a conanLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAna } // Fill licenses - for i, lib := range app.Libraries { + for i, lib := range app.Packages { if license, ok := licenses[lib.Name]; ok { - app.Libraries[i].Licenses = []string{ + app.Packages[i].Licenses = []string{ license, } } } - sort.Sort(app.Libraries) + sort.Sort(app.Packages) apps = append(apps, *app) return nil }); err != nil { diff --git a/pkg/fanal/analyzer/language/c/conan/conan_test.go b/pkg/fanal/analyzer/language/c/conan/conan_test.go index 622632c39c1f..bfc2054ebe66 100644 --- a/pkg/fanal/analyzer/language/c/conan/conan_test.go +++ b/pkg/fanal/analyzer/language/c/conan/conan_test.go @@ -27,7 +27,7 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) { { Type: types.Conan, FilePath: "conan.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "openssl/3.0.5", Name: "openssl", @@ -70,7 +70,7 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) { { Type: types.Conan, FilePath: "conan.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "openssl/3.0.5", Name: "openssl", diff --git a/pkg/fanal/analyzer/language/conda/environment/environment_test.go b/pkg/fanal/analyzer/language/conda/environment/environment_test.go index d511ac3e50a1..c9610289584e 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment_test.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment_test.go @@ -25,7 +25,7 @@ func Test_environmentAnalyzer_Analyze(t *testing.T) { { Type: types.CondaEnv, FilePath: "testdata/environment.yaml", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "_libgcc_mutex", Locations: []types.Location{ diff --git a/pkg/fanal/analyzer/language/conda/meta/meta_test.go b/pkg/fanal/analyzer/language/conda/meta/meta_test.go index 0b7a988e9ade..3cb4f18e4b2b 100644 --- a/pkg/fanal/analyzer/language/conda/meta/meta_test.go +++ b/pkg/fanal/analyzer/language/conda/meta/meta_test.go @@ -27,7 +27,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Type: types.CondaPkg, FilePath: "testdata/pip-22.2.2-py38h06a4308_0.json", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "pip", Version: "22.2.2", diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec.go b/pkg/fanal/analyzer/language/dart/pub/pubspec.go index 1981e08a023d..ad8a5396e255 100644 --- a/pkg/fanal/analyzer/language/dart/pub/pubspec.go +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec.go @@ -16,7 +16,6 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/dart/pub" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -36,7 +35,7 @@ const ( // pubSpecLockAnalyzer analyzes `pubspec.lock` type pubSpecLockAnalyzer struct { logger *log.Logger - parser godeptypes.Parser + parser language.Parser } func newPubSpecLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { @@ -72,22 +71,22 @@ func (a pubSpecLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostA if allDependsOn != nil { // Required to search for library versions for DependsOn. - libs := lo.SliceToMap(app.Libraries, func(lib types.Package) (string, string) { + pkgs := lo.SliceToMap(app.Packages, func(lib types.Package) (string, string) { return lib.Name, lib.ID }) - for i, lib := range app.Libraries { + for i, lib := range app.Packages { var dependsOn []string for _, depName := range allDependsOn[lib.ID] { - if depID, ok := libs[depName]; ok { + if depID, ok := pkgs[depName]; ok { dependsOn = append(dependsOn, depID) } } - app.Libraries[i].DependsOn = dependsOn + app.Packages[i].DependsOn = dependsOn } } - sort.Sort(app.Libraries) + sort.Sort(app.Packages) apps = append(apps, *app) return nil }) diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go b/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go index 2c57e1b3e75a..b464ddf5cd57 100644 --- a/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go @@ -32,14 +32,7 @@ func Test_pubSpecLockAnalyzer_Analyze(t *testing.T) { { Type: types.Pub, FilePath: "pubspec.lock", - Libraries: types.Packages{ - { - ID: "collection@1.17.0", - Name: "collection", - Version: "1.17.0", - Indirect: true, - Relationship: types.RelationshipIndirect, - }, + Packages: types.Packages{ { ID: "crypto@3.0.3", Name: "crypto", @@ -55,6 +48,13 @@ func Test_pubSpecLockAnalyzer_Analyze(t *testing.T) { Version: "1.11.0", Relationship: types.RelationshipDirect, }, + { + ID: "collection@1.17.0", + Name: "collection", + Version: "1.17.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + }, { ID: "typed_data@1.3.2", Name: "typed_data", @@ -80,14 +80,7 @@ func Test_pubSpecLockAnalyzer_Analyze(t *testing.T) { { Type: types.Pub, FilePath: "pubspec.lock", - Libraries: types.Packages{ - { - ID: "collection@1.17.0", - Name: "collection", - Version: "1.17.0", - Indirect: true, - Relationship: types.RelationshipIndirect, - }, + Packages: types.Packages{ { ID: "crypto@3.0.3", Name: "crypto", @@ -100,6 +93,13 @@ func Test_pubSpecLockAnalyzer_Analyze(t *testing.T) { Version: "1.11.0", Relationship: types.RelationshipDirect, }, + { + ID: "collection@1.17.0", + Name: "collection", + Version: "1.17.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + }, { ID: "typed_data@1.3.2", Name: "typed_data", diff --git a/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go b/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go index 37f23d2774f2..e3cdf2730e72 100644 --- a/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go +++ b/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go @@ -27,7 +27,7 @@ func Test_depsLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.DotNetCore, FilePath: "testdata/datacollector.deps.json", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "Newtonsoft.Json", Version: "9.0.1", diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go index 6411f4d1bc8d..2e24610719e4 100644 --- a/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go @@ -14,7 +14,6 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/config" "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/lock" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -31,11 +30,14 @@ const ( configFile = types.NuGetPkgsConfig ) -var requiredFiles = []string{lockFile, configFile} +var requiredFiles = []string{ + lockFile, + configFile, +} type nugetLibraryAnalyzer struct { - lockParser godeptypes.Parser - configParser godeptypes.Parser + lockParser language.Parser + configParser language.Parser licenseParser nuspecParser } @@ -76,7 +78,7 @@ func (a *nugetLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.Pos return nil } - for i, lib := range app.Libraries { + for i, lib := range app.Packages { license, ok := foundLicenses[lib.ID] if !ok { license, err = a.licenseParser.findLicense(lib.Name, lib.Version) @@ -86,10 +88,10 @@ func (a *nugetLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.Pos foundLicenses[lib.ID] = license } - app.Libraries[i].Licenses = license + app.Packages[i].Licenses = license } - sort.Sort(app.Libraries) + sort.Sort(app.Packages) apps = append(apps, *app) return nil }) diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go index f0494b69d97d..07b5023a392d 100644 --- a/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go @@ -30,7 +30,7 @@ func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { { Type: types.NuGet, FilePath: "packages.config", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "Microsoft.AspNet.WebApi", Version: "5.2.2", @@ -55,7 +55,7 @@ func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { { Type: types.NuGet, FilePath: "packages.lock.json", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", @@ -98,7 +98,7 @@ func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { { Type: types.NuGet, FilePath: "packages.lock.json", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", @@ -141,7 +141,7 @@ func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { { Type: types.NuGet, FilePath: "packages.lock.json", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "Newtonsoft.Json@12.0.3", Name: "Newtonsoft.Json", diff --git a/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops_test.go b/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops_test.go index 0fb12d4ba837..ee398028de6e 100644 --- a/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops_test.go +++ b/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops_test.go @@ -27,7 +27,7 @@ func Test_packagesPropsAnalyzer_Analyze(t *testing.T) { { Type: types.PackagesProps, FilePath: "testdata/Packages.props", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "Package1@22.1.4", Name: "Package1", @@ -51,7 +51,7 @@ func Test_packagesPropsAnalyzer_Analyze(t *testing.T) { { Type: types.PackagesProps, FilePath: "testdata/Directory.Packages.props", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "Package1@4.2.1", Name: "Package1", diff --git a/pkg/fanal/analyzer/language/elixir/mix/mix_test.go b/pkg/fanal/analyzer/language/elixir/mix/mix_test.go index 5c836c260555..d375014fd97a 100644 --- a/pkg/fanal/analyzer/language/elixir/mix/mix_test.go +++ b/pkg/fanal/analyzer/language/elixir/mix/mix_test.go @@ -25,7 +25,7 @@ func Test_mixLockAnalyzer_Analyze(t *testing.T) { { Type: types.Hex, FilePath: "testdata/happy.mix.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "bunt@0.2.0", Name: "bunt", diff --git a/pkg/fanal/analyzer/language/golang/binary/binary_test.go b/pkg/fanal/analyzer/language/golang/binary/binary_test.go index 839084be36bd..7c2b678ac0a5 100644 --- a/pkg/fanal/analyzer/language/golang/binary/binary_test.go +++ b/pkg/fanal/analyzer/language/golang/binary/binary_test.go @@ -28,7 +28,7 @@ func Test_gobinaryLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.GoBinary, FilePath: "testdata/executable_gobinary", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "github.com/aquasecurity/test", Version: "", diff --git a/pkg/fanal/analyzer/language/golang/mod/mod.go b/pkg/fanal/analyzer/language/golang/mod/mod.go index bc2c564429ab..f97d9bed5add 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod.go @@ -19,7 +19,6 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/sum" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -45,11 +44,11 @@ var ( type gomodAnalyzer struct { // root go.mod/go.sum - modParser godeptypes.Parser - sumParser godeptypes.Parser + modParser language.Parser + sumParser language.Parser // go.mod/go.sum in dependencies - leafModParser godeptypes.Parser + leafModParser language.Parser licenseClassifierConfidenceLevel float64 @@ -139,13 +138,13 @@ func (a *gomodAnalyzer) fillAdditionalData(apps []types.Application) error { licenses := make(map[string][]string) for i, app := range apps { // Actually used dependencies - usedLibs := lo.SliceToMap(app.Libraries, func(pkg types.Package) (string, types.Package) { + usedPkgs := lo.SliceToMap(app.Packages, func(pkg types.Package) (string, types.Package) { return pkg.Name, pkg }) - for j, lib := range app.Libraries { + for j, lib := range app.Packages { if l, ok := licenses[lib.ID]; ok { // Fill licenses - apps[i].Libraries[j].Licenses = l + apps[i].Packages[j].Licenses = l continue } @@ -160,7 +159,7 @@ func (a *gomodAnalyzer) fillAdditionalData(apps []types.Application) error { licenses[lib.ID] = licenseNames // Fill licenses - apps[i].Libraries[j].Licenses = licenseNames + apps[i].Packages[j].Licenses = licenseNames } // Collect dependencies of the direct dependency @@ -171,8 +170,8 @@ func (a *gomodAnalyzer) fillAdditionalData(apps []types.Application) error { continue } else { // Filter out unused dependencies and convert module names to module IDs - apps[i].Libraries[j].DependsOn = lo.FilterMap(dep.DependsOn, func(modName string, _ int) (string, bool) { - if m, ok := usedLibs[modName]; !ok { + apps[i].Packages[j].DependsOn = lo.FilterMap(dep.DependsOn, func(modName string, _ int) (string, bool) { + if m, ok := usedPkgs[modName]; !ok { return "", false } else { return m.ID, true @@ -184,37 +183,37 @@ func (a *gomodAnalyzer) fillAdditionalData(apps []types.Application) error { return nil } -func (a *gomodAnalyzer) collectDeps(modDir, pkgID string) (godeptypes.Dependency, error) { +func (a *gomodAnalyzer) collectDeps(modDir, pkgID string) (types.Dependency, error) { // e.g. $GOPATH/pkg/mod/github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237/go.mod modPath := filepath.Join(modDir, "go.mod") f, err := os.Open(modPath) if errors.Is(err, fs.ErrNotExist) { a.logger.Debug("Unable to identify dependencies as it doesn't support Go modules", log.String("module", pkgID)) - return godeptypes.Dependency{}, nil + return types.Dependency{}, nil } else if err != nil { - return godeptypes.Dependency{}, xerrors.Errorf("file open error: %w", err) + return types.Dependency{}, xerrors.Errorf("file open error: %w", err) } defer f.Close() // Parse go.mod under $GOPATH/pkg/mod - libs, _, err := a.leafModParser.Parse(f) + pkgs, _, err := a.leafModParser.Parse(f) if err != nil { - return godeptypes.Dependency{}, xerrors.Errorf("%s parse error: %w", modPath, err) + return types.Dependency{}, xerrors.Errorf("%s parse error: %w", modPath, err) } // Filter out indirect dependencies - dependsOn := lo.FilterMap(libs, func(lib godeptypes.Library, index int) (string, bool) { + dependsOn := lo.FilterMap(pkgs, func(lib types.Package, index int) (string, bool) { return lib.Name, lib.Relationship == types.RelationshipDirect }) - return godeptypes.Dependency{ + return types.Dependency{ ID: pkgID, DependsOn: dependsOn, }, nil } -func parse(fsys fs.FS, path string, parser godeptypes.Parser) (*types.Application, error) { +func parse(fsys fs.FS, path string, parser language.Parser) (*types.Application, error) { f, err := fsys.Open(path) if err != nil { return nil, xerrors.Errorf("file open error: %w", err) @@ -231,7 +230,7 @@ func parse(fsys fs.FS, path string, parser godeptypes.Parser) (*types.Applicatio } func lessThanGo117(gomod *types.Application) bool { - for _, lib := range gomod.Libraries { + for _, lib := range gomod.Packages { // The indirect field is populated only in Go 1.17+ if lib.Relationship == types.RelationshipIndirect { return false @@ -245,13 +244,13 @@ func mergeGoSum(gomod, gosum *types.Application) { return } uniq := make(map[string]types.Package) - for _, lib := range gomod.Libraries { + for _, lib := range gomod.Packages { // It will be used for merging go.sum. uniq[lib.Name] = lib } // For Go 1.16 or less, we need to merge go.sum into go.mod. - for _, lib := range gosum.Libraries { + for _, lib := range gosum.Packages { // Skip dependencies in go.mod so that go.mod should be preferred. if _, ok := uniq[lib.Name]; ok { continue @@ -263,7 +262,7 @@ func mergeGoSum(gomod, gosum *types.Application) { uniq[lib.Name] = lib } - gomod.Libraries = maps.Values(uniq) + gomod.Packages = maps.Values(uniq) } func findLicense(dir string, classifierConfidenceLevel float64) ([]string, error) { diff --git a/pkg/fanal/analyzer/language/golang/mod/mod_test.go b/pkg/fanal/analyzer/language/golang/mod/mod_test.go index c667170af7a6..02ed84c91ed7 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod_test.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod_test.go @@ -31,19 +31,29 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { { Type: types.GoModule, FilePath: "go.mod", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "github.com/org/repo", Name: "github.com/org/repo", Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20220406074731-71021a481237", Name: "github.com/aquasecurity/go-dep-parser", Version: "0.0.0-20220406074731-71021a481237", Relationship: types.RelationshipDirect, - Licenses: []string{ - "MIT", + Licenses: []string{"MIT"}, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, }, DependsOn: []string{ "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", @@ -71,17 +81,29 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { { Type: types.GoModule, FilePath: "go.mod", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "github.com/org/repo", Name: "github.com/org/repo", Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, }, { ID: "github.com/sad/sad@v0.0.1", Name: "github.com/sad/sad", Version: "0.0.1", Relationship: types.RelationshipDirect, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/sad/sad", + }, + }, }, }, }, @@ -99,11 +121,17 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { { Type: types.GoModule, FilePath: "go.mod", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "github.com/org/repo", Name: "github.com/org/repo", Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd", @@ -113,6 +141,12 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { DependsOn: []string{ "github.com/BurntSushi/toml@v0.3.1", }, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, }, { ID: "github.com/BurntSushi/toml@v0.3.1", @@ -139,11 +173,17 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { { Type: types.GoModule, FilePath: "go.mod", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "github.com/org/repo", Name: "github.com/org/repo", Relationship: types.RelationshipRoot, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/org/repo", + }, + }, }, { ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20230219131432-590b1dfb6edd", @@ -151,6 +191,12 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { Version: "0.0.0-20230219131432-590b1dfb6edd", Relationship: types.RelationshipDirect, DependsOn: []string{}, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefVCS, + URL: "https://github.com/aquasecurity/go-dep-parser", + }, + }, }, }, }, @@ -188,8 +234,8 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { assert.NoError(t, err) if len(got.Applications) > 0 { - sort.Sort(got.Applications[0].Libraries) - sort.Sort(tt.want.Applications[0].Libraries) + sort.Sort(got.Applications[0].Packages) + sort.Sort(tt.want.Applications[0].Packages) } assert.NoError(t, err) assert.Equal(t, tt.want, got) diff --git a/pkg/fanal/analyzer/language/java/gradle/lockfile.go b/pkg/fanal/analyzer/language/java/gradle/lockfile.go index 2426722c31ca..ce7fc2c31e59 100644 --- a/pkg/fanal/analyzer/language/java/gradle/lockfile.go +++ b/pkg/fanal/analyzer/language/java/gradle/lockfile.go @@ -13,7 +13,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/gradle/lockfile" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -33,7 +32,7 @@ const ( // gradleLockAnalyzer analyzes '*gradle.lockfile' type gradleLockAnalyzer struct { logger *log.Logger - parser godeptypes.Parser + parser language.Parser } func newGradleLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { @@ -65,16 +64,16 @@ func (a gradleLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAn return nil } - libs := lo.SliceToMap(app.Libraries, func(lib types.Package) (string, struct{}) { + pkgs := lo.SliceToMap(app.Packages, func(lib types.Package) (string, struct{}) { return lib.ID, struct{}{} }) - for i, lib := range app.Libraries { + for i, lib := range app.Packages { pom := poms[lib.ID] // Fill licenses from pom file if len(pom.Licenses.License) > 0 { - app.Libraries[i].Licenses = lo.Map(pom.Licenses.License, func(license License, _ int) string { + app.Packages[i].Licenses = lo.Map(pom.Licenses.License, func(license License, _ int) string { return license.Name }) } @@ -83,15 +82,15 @@ func (a gradleLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAn var deps []string for _, dep := range pom.Dependencies.Dependency { id := packageID(dep.GroupID, dep.ArtifactID, dep.Version) - if _, ok := libs[id]; ok { + if _, ok := pkgs[id]; ok { deps = append(deps, id) } } sort.Strings(deps) - app.Libraries[i].DependsOn = deps + app.Packages[i].DependsOn = deps } - sort.Sort(app.Libraries) + sort.Sort(app.Packages) apps = append(apps, *app) return nil }) diff --git a/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go b/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go index 0f5c67d9049c..e226b542f7d1 100644 --- a/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go +++ b/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go @@ -28,7 +28,7 @@ func Test_gradleLockAnalyzer_Analyze(t *testing.T) { { Type: types.Gradle, FilePath: "gradle.lockfile", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "junit:junit:4.13", Name: "junit:junit", @@ -72,7 +72,7 @@ func Test_gradleLockAnalyzer_Analyze(t *testing.T) { { Type: types.Gradle, FilePath: "gradle.lockfile", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "junit:junit:4.13", Name: "junit:junit", diff --git a/pkg/fanal/analyzer/language/java/jar/jar_test.go b/pkg/fanal/analyzer/language/java/jar/jar_test.go index 3988dc27daf5..d1cf6ab1f491 100644 --- a/pkg/fanal/analyzer/language/java/jar/jar_test.go +++ b/pkg/fanal/analyzer/language/java/jar/jar_test.go @@ -37,7 +37,7 @@ func Test_javaLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Jar, FilePath: "testdata/test.war", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "org.glassfish:javax.el", FilePath: "testdata/test.war/WEB-INF/lib/javax.el-3.0.0.jar", @@ -92,7 +92,7 @@ func Test_javaLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Jar, FilePath: "testdata/test.par", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "com.fasterxml.jackson.core:jackson-core", FilePath: "testdata/test.par/lib/jackson-core-2.9.10.jar", @@ -112,7 +112,7 @@ func Test_javaLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Jar, FilePath: "testdata/test.jar", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "org.apache.tomcat.embed:tomcat-embed-websocket", FilePath: "testdata/test.jar", diff --git a/pkg/fanal/analyzer/language/java/pom/pom.go b/pkg/fanal/analyzer/language/java/pom/pom.go index d192d69dfc07..2980b469107c 100644 --- a/pkg/fanal/analyzer/language/java/pom/pom.go +++ b/pkg/fanal/analyzer/language/java/pom/pom.go @@ -34,8 +34,8 @@ func (a pomAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (* // Mark integration test pom files for `maven-invoker-plugin` as Dev to skip them by default. if isIntegrationTestDir(filePath) { for i := range res.Applications { - for j := range res.Applications[i].Libraries { - res.Applications[i].Libraries[j].Dev = true + for j := range res.Applications[i].Packages { + res.Applications[i].Packages[j].Dev = true } } } diff --git a/pkg/fanal/analyzer/language/java/pom/pom_test.go b/pkg/fanal/analyzer/language/java/pom/pom_test.go index 3ea44231e7a5..865f2cb5471b 100644 --- a/pkg/fanal/analyzer/language/java/pom/pom_test.go +++ b/pkg/fanal/analyzer/language/java/pom/pom_test.go @@ -28,7 +28,7 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { { Type: types.Pom, FilePath: "testdata/happy/pom.xml", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "com.example:example:1.0.0", Name: "com.example:example", @@ -65,7 +65,7 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { { Type: types.Pom, FilePath: "pom.xml", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "com.example:example:1.0.0", Name: "com.example:example", @@ -101,7 +101,7 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { { Type: types.Pom, FilePath: "testdata/mark-as-dev/src/it/example/pom.xml", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "com.example:example:1.0.0", Name: "com.example:example", @@ -139,7 +139,7 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { { Type: types.Pom, FilePath: "testdata/requirements/pom.xml", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "com.example:example:2.0.0", Name: "com.example:example", diff --git a/pkg/fanal/analyzer/language/nodejs/license/license.go b/pkg/fanal/analyzer/language/nodejs/license/license.go index 0a797c558e56..529a8137d359 100644 --- a/pkg/fanal/analyzer/language/nodejs/license/license.go +++ b/pkg/fanal/analyzer/language/nodejs/license/license.go @@ -38,9 +38,9 @@ func (l *License) Traverse(fsys fs.FS, root string) (map[string][]string, error) return xerrors.Errorf("unable to parse %q: %w", pkgJSONPath, err) } - ok, licenseFileName := IsLicenseRefToFile(pkg.License) + ok, licenseFileName := IsLicenseRefToFile(pkg.Licenses) if !ok { - licenses[pkg.ID] = []string{pkg.License} + licenses[pkg.ID] = pkg.Licenses return nil } @@ -68,19 +68,19 @@ func (l *License) Traverse(fsys fs.FS, root string) (map[string][]string, error) // IsLicenseRefToFile The license field can refer to a file // https://docs.npmjs.com/cli/v9/configuring-npm/package-json -func IsLicenseRefToFile(maybeLicense string) (bool, string) { - if maybeLicense == "" { +func IsLicenseRefToFile(maybeLicenses []string) (bool, string) { + if len(maybeLicenses) != 1 { // trying to find at least the LICENSE file return true, "LICENSE" } var licenseFileName string - if strings.HasPrefix(maybeLicense, "LicenseRef-") { + if strings.HasPrefix(maybeLicenses[0], "LicenseRef-") { // LicenseRef- - licenseFileName = strings.Split(maybeLicense, "-")[1] - } else if strings.HasPrefix(maybeLicense, "SEE LICENSE IN ") { + licenseFileName = strings.Split(maybeLicenses[0], "-")[1] + } else if strings.HasPrefix(maybeLicenses[0], "SEE LICENSE IN ") { // SEE LICENSE IN - parts := strings.Split(maybeLicense, " ") + parts := strings.Split(maybeLicenses[0], " ") licenseFileName = parts[len(parts)-1] } diff --git a/pkg/fanal/analyzer/language/nodejs/license/license_test.go b/pkg/fanal/analyzer/language/nodejs/license/license_test.go index a282bc4bf6de..745d24cb4044 100644 --- a/pkg/fanal/analyzer/language/nodejs/license/license_test.go +++ b/pkg/fanal/analyzer/language/nodejs/license/license_test.go @@ -51,13 +51,13 @@ func Test_ParseLicenses(t *testing.T) { func Test_IsLicenseRefToFile(t *testing.T) { tests := []struct { name string - input string + input []string wantOk bool wantFileName string }{ { name: "no ref to file", - input: "MIT", + input: []string{"MIT"}, }, { name: "empty input", @@ -66,24 +66,24 @@ func Test_IsLicenseRefToFile(t *testing.T) { }, { name: "happy `SEE LICENSE IN`", - input: "SEE LICENSE IN LICENSE.md", + input: []string{"SEE LICENSE IN LICENSE.md"}, wantOk: true, wantFileName: "LICENSE.md", }, { name: "sad `SEE LICENSE IN`", - input: "SEE LICENSE IN ", + input: []string{"SEE LICENSE IN "}, wantOk: false, }, { name: "happy `LicenseRef-`", - input: "LicenseRef-LICENSE.txt", + input: []string{"LicenseRef-LICENSE.txt"}, wantOk: true, wantFileName: "LICENSE.txt", }, { name: "sad `LicenseRef-`", - input: "LicenseRef-", + input: []string{"LicenseRef-"}, wantOk: false, }, } @@ -92,7 +92,7 @@ func Test_IsLicenseRefToFile(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ok, licenseFileName := license.IsLicenseRefToFile(tt.input) assert.Equal(t, ok, tt.wantOk) - assert.Equal(t, licenseFileName, tt.wantFileName) + assert.Equal(t, tt.wantFileName, licenseFileName) }) } } diff --git a/pkg/fanal/analyzer/language/nodejs/npm/npm.go b/pkg/fanal/analyzer/language/nodejs/npm/npm.go index 44123eade970..870ba1be88e7 100644 --- a/pkg/fanal/analyzer/language/nodejs/npm/npm.go +++ b/pkg/fanal/analyzer/language/nodejs/npm/npm.go @@ -13,7 +13,6 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/npm" "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -33,7 +32,7 @@ const ( type npmLibraryAnalyzer struct { logger *log.Logger - lockParser godeptypes.Parser + lockParser language.Parser packageParser *packagejson.Parser } @@ -57,7 +56,7 @@ func (a npmLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAn licenses, err := a.findLicenses(input.FS, filePath) if err != nil { a.logger.Error("Unable to collect licenses", log.Err(err)) - licenses = make(map[string]string) + licenses = make(map[string][]string) } app, err := a.parseNpmPkgLock(input.FS, filePath) @@ -68,9 +67,9 @@ func (a npmLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAn } // Fill licenses - for i, lib := range app.Libraries { - if license, ok := licenses[lib.ID]; ok { - app.Libraries[i].Licenses = []string{license} + for i, lib := range app.Packages { + if ll, ok := licenses[lib.ID]; ok { + app.Packages[i].Licenses = ll } } @@ -125,7 +124,7 @@ func (a npmLibraryAnalyzer) parseNpmPkgLock(fsys fs.FS, filePath string) (*types return language.Parse(types.Npm, filePath, file, a.lockParser) } -func (a npmLibraryAnalyzer) findLicenses(fsys fs.FS, lockPath string) (map[string]string, error) { +func (a npmLibraryAnalyzer) findLicenses(fsys fs.FS, lockPath string) (map[string][]string, error) { dir := path.Dir(lockPath) root := path.Join(dir, "node_modules") if _, err := fs.Stat(fsys, root); errors.Is(err, fs.ErrNotExist) { @@ -142,14 +141,14 @@ func (a npmLibraryAnalyzer) findLicenses(fsys fs.FS, lockPath string) (map[strin // Traverse node_modules dir and find licenses // Note that fs.FS is always slashed regardless of the platform, // and path.Join should be used rather than filepath.Join. - licenses := make(map[string]string) + licenses := make(map[string][]string) err := fsutils.WalkDir(fsys, root, required, func(filePath string, d fs.DirEntry, r io.Reader) error { pkg, err := a.packageParser.Parse(r) if err != nil { return xerrors.Errorf("unable to parse %q: %w", filePath, err) } - licenses[pkg.ID] = pkg.License + licenses[pkg.ID] = pkg.Licenses return nil }) if err != nil { diff --git a/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go b/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go index 82130279836d..010a6207502d 100644 --- a/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go +++ b/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go @@ -33,7 +33,7 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Npm, FilePath: "package-lock.json", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "@babel/parser@7.23.6", Name: "@babel/parser", @@ -45,6 +45,12 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { EndLine: 10, }, }, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + }, + }, }, { ID: "ansi-colors@3.2.3", @@ -57,6 +63,12 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { EndLine: 16, }, }, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + }, + }, }, { ID: "array-flatten@1.1.1", @@ -68,6 +80,12 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { EndLine: 21, }, }, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + }, + }, }, { ID: "body-parser@1.18.3", @@ -81,6 +99,12 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { EndLine: 44, }, }, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + }, + }, }, { ID: "debug@2.6.9", @@ -98,6 +122,12 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { EndLine: 60, }, }, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + }, + }, }, { ID: "express@4.16.4", @@ -111,6 +141,12 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { EndLine: 67, }, }, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + }, + }, }, { ID: "ms@2.0.0", @@ -127,6 +163,12 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { EndLine: 65, }, }, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + }, + }, }, { ID: "ms@2.1.1", @@ -139,6 +181,12 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { EndLine: 72, }, }, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + }, + }, }, }, }, @@ -153,7 +201,7 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Npm, FilePath: "package-lock.json", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "ms@2.1.1", Name: "ms", @@ -164,6 +212,12 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { EndLine: 10, }, }, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + }, + }, }, }, }, @@ -187,7 +241,7 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { assert.NoError(t, err) if len(got.Applications) > 0 { - sort.Sort(got.Applications[0].Libraries) + sort.Sort(got.Applications[0].Packages) } assert.Equal(t, tt.want, got) }) diff --git a/pkg/fanal/analyzer/language/nodejs/pkg/pkg.go b/pkg/fanal/analyzer/language/nodejs/pkg/pkg.go index aabc4da6b6a2..5ca5d4eeae80 100644 --- a/pkg/fanal/analyzer/language/nodejs/pkg/pkg.go +++ b/pkg/fanal/analyzer/language/nodejs/pkg/pkg.go @@ -6,7 +6,6 @@ import ( "path/filepath" "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -24,20 +23,20 @@ const ( type parser struct{} -func (*parser) Parse(r xio.ReadSeekerAt) ([]godeptypes.Library, []godeptypes.Dependency, error) { +func (*parser) Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, error) { p := packagejson.NewParser() pkg, err := p.Parse(r) if err != nil { return nil, nil, err } // skip packages without name/version - if pkg.Library.ID == "" { + if pkg.Package.ID == "" { return nil, nil, nil } // package.json may contain version range in `dependencies` fields // e.g. "devDependencies": { "mocha": "^5.2.0", } // so we get only information about project - return []godeptypes.Library{pkg.Library}, nil, nil + return []types.Package{pkg.Package}, nil, nil } type nodePkgLibraryAnalyzer struct{} diff --git a/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go b/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go index 91a6bb6d708d..c27032b45166 100644 --- a/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go +++ b/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go @@ -28,7 +28,7 @@ func Test_nodePkgLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.NodePkg, FilePath: "testdata/package.json", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "lodash@5.0.0", Name: "lodash", @@ -50,7 +50,7 @@ func Test_nodePkgLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.NodePkg, FilePath: "testdata/package.json", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "lodash@5.0.0", Name: "lodash", diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go index 85200cccc2e7..c3a50922a569 100644 --- a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go @@ -27,7 +27,7 @@ func Test_pnpmPkgLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Pnpm, FilePath: "testdata/pnpm-lock.yaml", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "lodash@4.17.21", Name: "lodash", diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go index feabf4d6abc1..086f5fe7f615 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go @@ -20,7 +20,6 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/yarn" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/detector/library/compare/npm" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" @@ -44,7 +43,7 @@ var fragmentRegexp = regexp.MustCompile(`(\S+):(@?.*?)(@(.*?)|)$`) type yarnAnalyzer struct { logger *log.Logger packageJsonParser *packagejson.Parser - lockParser godeptypes.Parser + lockParser language.Parser comparer npm.Comparer license *license.License } @@ -87,9 +86,9 @@ func (a yarnAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis } // Fill licenses - for i, lib := range app.Libraries { + for i, lib := range app.Packages { if l, ok := licenses[lib.ID]; ok { - app.Libraries[i].Licenses = l + app.Packages[i].Licenses = l } } @@ -165,20 +164,20 @@ func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.App return xerrors.Errorf("unable to parse %s: %w", dir, err) } - // yarn.lock file can contain same libraries with different versions + // yarn.lock file can contain same packages with different versions // save versions separately for version comparison by comparator - pkgIDs := lo.SliceToMap(app.Libraries, func(pkg types.Package) (string, types.Package) { + pkgIDs := lo.SliceToMap(app.Packages, func(pkg types.Package) (string, types.Package) { return pkg.ID, pkg }) // Walk prod dependencies - pkgs, err := a.walkDependencies(app.Libraries, pkgIDs, directDeps, false) + pkgs, err := a.walkDependencies(app.Packages, pkgIDs, directDeps, false) if err != nil { return xerrors.Errorf("unable to walk dependencies: %w", err) } // Walk dev dependencies - devPkgs, err := a.walkDependencies(app.Libraries, pkgIDs, directDevDeps, true) + devPkgs, err := a.walkDependencies(app.Packages, pkgIDs, directDevDeps, true) if err != nil { return xerrors.Errorf("unable to walk dependencies: %w", err) } @@ -190,17 +189,17 @@ func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.App pkgSlice := maps.Values(pkgs) sort.Sort(types.Packages(pkgSlice)) - // Save libraries - app.Libraries = pkgSlice + // Save packages + app.Packages = pkgSlice return nil } -func (a yarnAnalyzer) walkDependencies(libs []types.Package, pkgIDs map[string]types.Package, +func (a yarnAnalyzer) walkDependencies(pkgs []types.Package, pkgIDs map[string]types.Package, directDeps map[string]string, dev bool) (map[string]types.Package, error) { // Identify direct dependencies - pkgs := make(map[string]types.Package) - for _, pkg := range libs { + directPkgs := make(map[string]types.Package) + for _, pkg := range pkgs { constraint, ok := directDeps[pkg.Name] if !ok { continue @@ -224,16 +223,16 @@ func (a yarnAnalyzer) walkDependencies(libs []types.Package, pkgIDs map[string]t pkg.Indirect = false pkg.Relationship = types.RelationshipDirect pkg.Dev = dev - pkgs[pkg.ID] = pkg + directPkgs[pkg.ID] = pkg } // Walk indirect dependencies - for _, pkg := range pkgs { - a.walkIndirectDependencies(pkg, pkgIDs, pkgs) + for _, pkg := range directPkgs { + a.walkIndirectDependencies(pkg, pkgIDs, directPkgs) } - return pkgs, nil + return directPkgs, nil } func (a yarnAnalyzer) walkIndirectDependencies(pkg types.Package, pkgIDs, deps map[string]types.Package) { diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go index 6815dd6b4127..7aae5d5181ae 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go @@ -26,7 +26,7 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Yarn, FilePath: "yarn.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "js-tokens@2.0.0", Name: "js-tokens", @@ -39,6 +39,40 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, }, + { + ID: "prop-types@15.7.2", + Name: "prop-types", + Version: "15.7.2", + Dev: true, + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 27, + EndLine: 34, + }, + }, + DependsOn: []string{ + "loose-envify@1.4.0", + "object-assign@4.1.1", + "react-is@16.13.1", + }, + }, + { + ID: "scheduler@0.13.6", + Name: "scheduler", + Version: "0.13.6", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 41, + EndLine: 47, + }, + }, + DependsOn: []string{ + "loose-envify@1.4.0", + "object-assign@4.1.1", + }, + }, { ID: "js-tokens@4.0.0", Name: "js-tokens", @@ -81,24 +115,6 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, }, - { - ID: "prop-types@15.7.2", - Name: "prop-types", - Version: "15.7.2", - Dev: true, - Relationship: types.RelationshipDirect, - Locations: []types.Location{ - { - StartLine: 27, - EndLine: 34, - }, - }, - DependsOn: []string{ - "loose-envify@1.4.0", - "object-assign@4.1.1", - "react-is@16.13.1", - }, - }, { ID: "react-is@16.13.1", Name: "react-is", @@ -113,22 +129,6 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, }, - { - ID: "scheduler@0.13.6", - Name: "scheduler", - Version: "0.13.6", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ - { - StartLine: 41, - EndLine: 47, - }, - }, - DependsOn: []string{ - "loose-envify@1.4.0", - "object-assign@4.1.1", - }, - }, }, }, }, @@ -142,7 +142,7 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Yarn, FilePath: "foo/yarn.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "hoek@6.1.3", Name: "hoek", @@ -168,7 +168,7 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Yarn, FilePath: "yarn.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "js-tokens@2.0.0", Name: "js-tokens", @@ -271,7 +271,7 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Yarn, FilePath: "yarn.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "js-tokens@2.0.0", Name: "js-tokens", @@ -307,7 +307,7 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Yarn, FilePath: "yarn.lock", - Libraries: []types.Package{ + Packages: []types.Package{ { ID: "is-callable@1.2.7", Name: "is-callable", @@ -321,20 +321,6 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, }, - { - ID: "is-number@6.0.0", - Name: "is-number", - Version: "6.0.0", - Licenses: []string{"MIT"}, - Indirect: true, - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 15, - EndLine: 20, - }, - }, - }, { ID: "is-odd@3.0.1", Name: "is-odd", @@ -349,6 +335,20 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, }, + { + ID: "is-number@6.0.0", + Name: "is-number", + Version: "6.0.0", + Licenses: []string{"MIT"}, + Indirect: true, + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 15, + EndLine: 20, + }, + }, + }, }, }, }, @@ -362,7 +362,7 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Yarn, FilePath: "yarn.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "foo-json@0.8.33", Name: "@types/jsonstream", @@ -380,23 +380,6 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { "@types/node@20.10.5", }, }, - { - ID: "@types/node@20.10.5", - Name: "@types/node", - Version: "20.10.5", - Indirect: true, - Relationship: types.RelationshipIndirect, - Dev: true, - Locations: []types.Location{ - { - StartLine: 5, - EndLine: 10, - }, - }, - DependsOn: []string{ - "undici-types@5.26.5", - }, - }, { ID: "foo-uuid@9.0.7", Name: "@types/uuid", @@ -428,28 +411,45 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "ms@2.1.2", + ID: "foo-ms@2.1.3", Name: "ms", - Version: "2.1.2", + Version: "2.1.3", + Indirect: false, + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 26, + EndLine: 29, + }, + }, + }, + { + ID: "@types/node@20.10.5", + Name: "@types/node", + Version: "20.10.5", Indirect: true, Relationship: types.RelationshipIndirect, + Dev: true, Locations: []types.Location{ { - StartLine: 36, - EndLine: 39, + StartLine: 5, + EndLine: 10, }, }, + DependsOn: []string{ + "undici-types@5.26.5", + }, }, { - ID: "foo-ms@2.1.3", + ID: "ms@2.1.2", Name: "ms", - Version: "2.1.3", - Indirect: false, - Relationship: types.RelationshipDirect, + Version: "2.1.2", + Indirect: true, + Relationship: types.RelationshipIndirect, Locations: []types.Location{ { - StartLine: 26, - EndLine: 29, + StartLine: 36, + EndLine: 39, }, }, }, @@ -480,20 +480,7 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Yarn, FilePath: "yarn.lock", - Libraries: types.Packages{ - { - ID: "is-number@6.0.0", - Name: "is-number", - Version: "6.0.0", - Indirect: true, - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 16, - EndLine: 21, - }, - }, - }, + Packages: types.Packages{ { ID: "is-number@7.0.0", Name: "is-number", @@ -519,19 +506,6 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, }, - { - ID: "js-tokens@4.0.0", - Name: "js-tokens", - Version: "4.0.0", - Indirect: true, - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 39, - EndLine: 44, - }, - }, - }, { ID: "js-tokens@8.0.1", Name: "js-tokens", @@ -544,34 +518,6 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, }, - { - ID: "loose-envify@1.4.0", - Name: "loose-envify", - Version: "1.4.0", - Indirect: true, - Relationship: types.RelationshipIndirect, - DependsOn: []string{"js-tokens@4.0.0"}, - Locations: []types.Location{ - { - StartLine: 53, - EndLine: 62, - }, - }, - }, - { - ID: "object-assign@4.1.1", - Name: "object-assign", - Version: "4.1.1", - Indirect: true, - Relationship: types.RelationshipIndirect, - Dev: true, - Locations: []types.Location{ - { - StartLine: 64, - EndLine: 69, - }, - }, - }, { ID: "prettier@2.8.8", Name: "prettier", @@ -604,29 +550,83 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "react-is@16.13.1", - Name: "react-is", - Version: "16.13.1", + ID: "scheduler@0.23.0", + Name: "scheduler", + Version: "0.23.0", + Relationship: types.RelationshipDirect, + DependsOn: []string{"loose-envify@1.4.0"}, + Locations: []types.Location{ + { + StartLine: 114, + EndLine: 121, + }, + }, + }, + { + ID: "is-number@6.0.0", + Name: "is-number", + Version: "6.0.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 16, + EndLine: 21, + }, + }, + }, + { + ID: "js-tokens@4.0.0", + Name: "js-tokens", + Version: "4.0.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 39, + EndLine: 44, + }, + }, + }, + { + ID: "loose-envify@1.4.0", + Name: "loose-envify", + Version: "1.4.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + DependsOn: []string{"js-tokens@4.0.0"}, + Locations: []types.Location{ + { + StartLine: 53, + EndLine: 62, + }, + }, + }, + { + ID: "object-assign@4.1.1", + Name: "object-assign", + Version: "4.1.1", Indirect: true, Relationship: types.RelationshipIndirect, Dev: true, Locations: []types.Location{ { - StartLine: 107, - EndLine: 112, + StartLine: 64, + EndLine: 69, }, }, }, { - ID: "scheduler@0.23.0", - Name: "scheduler", - Version: "0.23.0", - Relationship: types.RelationshipDirect, - DependsOn: []string{"loose-envify@1.4.0"}, + ID: "react-is@16.13.1", + Name: "react-is", + Version: "16.13.1", + Indirect: true, + Relationship: types.RelationshipIndirect, + Dev: true, Locations: []types.Location{ { - StartLine: 114, - EndLine: 121, + StartLine: 107, + EndLine: 112, }, }, }, @@ -647,21 +647,7 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Yarn, FilePath: "yarn.lock", - Libraries: []types.Package{ - { - ID: "@babel/parser@7.22.7", - Name: "@babel/parser", - Version: "7.22.7", - Indirect: true, - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 5, - EndLine: 8, - }, - }, - Licenses: []string{"MIT"}, - }, + Packages: []types.Package{ { ID: "@vue/compiler-sfc@2.7.14", Name: "@vue/compiler-sfc", @@ -681,6 +667,20 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { "source-map@0.6.1", }, }, + { + ID: "@babel/parser@7.22.7", + Name: "@babel/parser", + Version: "7.22.7", + Indirect: true, + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 8, + }, + }, + Licenses: []string{"MIT"}, + }, { ID: "nanoid@3.3.6", Name: "nanoid", diff --git a/pkg/fanal/analyzer/language/php/composer/composer.go b/pkg/fanal/analyzer/language/php/composer/composer.go index 06f8e7487c62..5e726168a5ae 100644 --- a/pkg/fanal/analyzer/language/php/composer/composer.go +++ b/pkg/fanal/analyzer/language/php/composer/composer.go @@ -15,7 +15,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/php/composer" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -35,7 +34,7 @@ var requiredFiles = []string{ } type composerAnalyzer struct { - lockParser godeptypes.Parser + lockParser language.Parser } func newComposerAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { @@ -65,7 +64,7 @@ func (a composerAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnal log.Warn("Unable to parse composer.json to identify direct dependencies", log.String("path", filepath.Join(filepath.Dir(path), types.ComposerJson)), log.Err(err)) } - sort.Sort(app.Libraries) + sort.Sort(app.Packages) apps = append(apps, *app) return nil @@ -116,13 +115,13 @@ func (a composerAnalyzer) mergeComposerJson(fsys fs.FS, dir string, app *types.A return xerrors.Errorf("unable to parse %s: %w", path, err) } - for i, lib := range app.Libraries { + for i, pkg := range app.Packages { // Identify the direct/transitive dependencies - if _, ok := p[lib.Name]; ok { - app.Libraries[i].Relationship = types.RelationshipDirect + if _, ok := p[pkg.Name]; ok { + app.Packages[i].Relationship = types.RelationshipDirect } else { - app.Libraries[i].Indirect = true - app.Libraries[i].Relationship = types.RelationshipIndirect + app.Packages[i].Indirect = true + app.Packages[i].Relationship = types.RelationshipIndirect } } diff --git a/pkg/fanal/analyzer/language/php/composer/composer_test.go b/pkg/fanal/analyzer/language/php/composer/composer_test.go index 61cc3af8bc50..cc5d6908fc84 100644 --- a/pkg/fanal/analyzer/language/php/composer/composer_test.go +++ b/pkg/fanal/analyzer/language/php/composer/composer_test.go @@ -26,7 +26,7 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { { Type: types.Composer, FilePath: "composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "pear/log@1.13.3", Name: "pear/log", @@ -69,7 +69,7 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { { Type: types.Composer, FilePath: "composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "pear/log@1.13.3", Name: "pear/log", @@ -112,7 +112,7 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { { Type: types.Composer, FilePath: "composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "pear/log@1.13.3", Name: "pear/log", diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging.go b/pkg/fanal/analyzer/language/python/packaging/packaging.go index 6f2c508b5404..51fd585d8a6c 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging.go @@ -16,7 +16,6 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/python/packaging" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -56,7 +55,7 @@ var ( type packagingAnalyzer struct { logger *log.Logger - pkgParser godeptypes.Parser + pkgParser language.Parser licenseClassifierConfidenceLevel float64 } @@ -117,9 +116,9 @@ func (a packagingAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAna } func (a packagingAnalyzer) fillAdditionalData(fsys fs.FS, app *types.Application) error { - for i, lib := range app.Libraries { + for i, pkg := range app.Packages { var licenses []string - for _, lic := range lib.Licenses { + for _, lic := range pkg.Licenses { // Parser adds `file://` prefix to filepath from `License-File` field // We need to read this file to find licenses // Otherwise, this is the name of the license @@ -142,7 +141,7 @@ func (a packagingAnalyzer) fillAdditionalData(fsys fs.FS, app *types.Application }) licenses = append(licenses, foundLicenses...) } - app.Libraries[i].Licenses = licenses + app.Packages[i].Licenses = licenses } return nil diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go index 2dbfd603e92b..eac4d29f5e84 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go @@ -28,7 +28,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Type: types.PythonPkg, FilePath: "kitchen-1.2.6-py2.7.egg", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "kitchen", Version: "1.2.6", @@ -51,7 +51,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Type: types.PythonPkg, FilePath: "distlib-0.3.1.egg-info/PKG-INFO", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "distlib", Version: "0.3.1", @@ -72,7 +72,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Type: types.PythonPkg, FilePath: "setuptools-51.3.3.egg-info/PKG-INFO", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "setuptools", Version: "51.3.3", @@ -92,7 +92,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Type: types.PythonPkg, FilePath: "setuptools-51.3.3.dist-info/METADATA", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "setuptools", Version: "51.3.3", @@ -112,7 +112,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Type: types.PythonPkg, FilePath: "distlib-0.3.1.dist-info/METADATA", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "distlib", Version: "0.3.1", @@ -137,11 +137,16 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Type: types.PythonPkg, FilePath: "typing_extensions-4.4.0.dist-info/METADATA", - Libraries: []types.Package{ + Packages: []types.Package{ { - Name: "typing_extensions", - Version: "4.4.0", - Licenses: []string{"BeOpen", "CNRI-Python-GPL-Compatible", "LicenseRef-MIT-Lucent", "Python-2.0"}, + Name: "typing_extensions", + Version: "4.4.0", + Licenses: []string{ + "BeOpen", + "CNRI-Python-GPL-Compatible", + "LicenseRef-MIT-Lucent", + "Python-2.0", + }, FilePath: "typing_extensions-4.4.0.dist-info/METADATA", }, }, diff --git a/pkg/fanal/analyzer/language/python/pip/pip_test.go b/pkg/fanal/analyzer/language/python/pip/pip_test.go index b023648ae5d0..0a930f5cce51 100644 --- a/pkg/fanal/analyzer/language/python/pip/pip_test.go +++ b/pkg/fanal/analyzer/language/python/pip/pip_test.go @@ -27,7 +27,7 @@ func Test_pipAnalyzer_Analyze(t *testing.T) { { Type: types.Pip, FilePath: "testdata/requirements.txt", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "click", Version: "8.0.0", diff --git a/pkg/fanal/analyzer/language/python/poetry/poetry.go b/pkg/fanal/analyzer/language/python/poetry/poetry.go index bff6ae68e129..84af54c587df 100644 --- a/pkg/fanal/analyzer/language/python/poetry/poetry.go +++ b/pkg/fanal/analyzer/language/python/poetry/poetry.go @@ -12,7 +12,6 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency/parser/python/poetry" "github.com/aquasecurity/trivy/pkg/dependency/parser/python/pyproject" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -29,7 +28,7 @@ const version = 1 type poetryAnalyzer struct { logger *log.Logger pyprojectParser *pyproject.Parser - lockParser godeptypes.Parser + lockParser language.Parser } func newPoetryAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { @@ -103,13 +102,13 @@ func (a poetryAnalyzer) mergePyProject(fsys fs.FS, dir string, app *types.Applic return xerrors.Errorf("unable to parse %s: %w", path, err) } - for i, lib := range app.Libraries { + for i, pkg := range app.Packages { // Identify the direct/transitive dependencies - if _, ok := p[lib.Name]; ok { - app.Libraries[i].Relationship = types.RelationshipDirect + if _, ok := p[pkg.Name]; ok { + app.Packages[i].Relationship = types.RelationshipDirect } else { - app.Libraries[i].Indirect = true - app.Libraries[i].Relationship = types.RelationshipIndirect + app.Packages[i].Indirect = true + app.Packages[i].Relationship = types.RelationshipIndirect } } diff --git a/pkg/fanal/analyzer/language/python/poetry/poetry_test.go b/pkg/fanal/analyzer/language/python/poetry/poetry_test.go index 49cc06c420b6..b0e38a51f5e0 100644 --- a/pkg/fanal/analyzer/language/python/poetry/poetry_test.go +++ b/pkg/fanal/analyzer/language/python/poetry/poetry_test.go @@ -26,7 +26,7 @@ func Test_poetryLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Poetry, FilePath: "poetry.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "certifi@2022.12.7", Name: "certifi", @@ -130,7 +130,7 @@ func Test_poetryLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Poetry, FilePath: "poetry.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "click@8.1.3", Name: "click", @@ -157,7 +157,7 @@ func Test_poetryLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.Poetry, FilePath: "poetry.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "click@8.1.3", Name: "click", diff --git a/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go b/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go index 4bd48d379f8b..41e4746b68b6 100644 --- a/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go +++ b/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go @@ -28,7 +28,7 @@ func Test_gemspecLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.GemSpec, FilePath: "testdata/multiple_licenses.gemspec", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "test-unit", Version: "3.3.7", @@ -53,7 +53,7 @@ func Test_gemspecLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.GemSpec, FilePath: "testdata/multiple_licenses.gemspec", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "test-unit", Version: "3.3.7", diff --git a/pkg/fanal/analyzer/language/rust/binary/binary_test.go b/pkg/fanal/analyzer/language/rust/binary/binary_test.go index b2a87e52d63a..27bfce04eea0 100644 --- a/pkg/fanal/analyzer/language/rust/binary/binary_test.go +++ b/pkg/fanal/analyzer/language/rust/binary/binary_test.go @@ -28,7 +28,7 @@ func Test_rustBinaryLibraryAnalyzer_Analyze(t *testing.T) { { Type: types.RustBinary, FilePath: "testdata/executable_rust", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "crate_with_features@0.1.0", Name: "crate_with_features", diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo.go b/pkg/fanal/analyzer/language/rust/cargo/cargo.go index e4a6bcc4736b..5795586ab33a 100644 --- a/pkg/fanal/analyzer/language/rust/cargo/cargo.go +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo.go @@ -20,7 +20,6 @@ import ( "github.com/aquasecurity/go-version/pkg/semver" goversion "github.com/aquasecurity/go-version/pkg/version" "github.com/aquasecurity/trivy/pkg/dependency/parser/rust/cargo" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/detector/library/compare" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" @@ -42,7 +41,7 @@ var requiredFiles = []string{ type cargoAnalyzer struct { logger *log.Logger - lockParser godeptypes.Parser + lockParser language.Parser comparer compare.GenericComparer } @@ -75,7 +74,7 @@ func (a cargoAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysi a.logger.Warn("Unable to parse Cargo.toml q to identify direct dependencies", log.String("path", path.Join(path.Dir(filePath), types.CargoToml)), log.Err(err)) } - sort.Sort(app.Libraries) + sort.Sort(app.Packages) apps = append(apps, *app) return nil @@ -116,16 +115,16 @@ func (a cargoAnalyzer) removeDevDependencies(fsys fs.FS, dir string, app *types. return xerrors.Errorf("unable to parse %s: %w", cargoTOMLPath, err) } - // Cargo.toml file can contain same libraries with different versions. + // Cargo.toml file can contain same packages with different versions. // Save versions separately for version comparison by comparator - pkgIDs := lo.SliceToMap(app.Libraries, func(pkg types.Package) (string, types.Package) { + pkgIDs := lo.SliceToMap(app.Packages, func(pkg types.Package) (string, types.Package) { return pkg.ID, pkg }) // Identify direct dependencies pkgs := make(map[string]types.Package) for name, constraint := range directDeps { - for _, pkg := range app.Libraries { + for _, pkg := range app.Packages { if pkg.Name != name { continue } @@ -152,8 +151,8 @@ func (a cargoAnalyzer) removeDevDependencies(fsys fs.FS, dir string, app *types. pkgSlice := maps.Values(pkgs) sort.Sort(types.Packages(pkgSlice)) - // Save only prod libraries - app.Libraries = pkgSlice + // Save only prod packages + app.Packages = pkgSlice return nil } diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go b/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go index c5fb27278069..52c5820faa04 100644 --- a/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go @@ -27,34 +27,7 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { { Type: types.Cargo, FilePath: "Cargo.lock", - Libraries: types.Packages{ - { - ID: "aho-corasick@0.7.20", - Name: "aho-corasick", - Version: "0.7.20", - Indirect: true, - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 4, - EndLine: 11, - }, - }, - DependsOn: []string{"memchr@2.5.0"}, - }, - { - ID: "libc@0.2.140", - Name: "libc", - Version: "0.2.140", - Indirect: true, - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 22, - EndLine: 26, - }, - }, - }, + Packages: types.Packages{ { ID: "memchr@1.0.2", Name: "memchr", @@ -69,19 +42,6 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, DependsOn: []string{"libc@0.2.140"}, }, - { - ID: "memchr@2.5.0", - Name: "memchr", - Version: "2.5.0", - Indirect: true, - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 37, - EndLine: 41, - }, - }, - }, { ID: "regex@1.7.3", Name: "regex", @@ -114,6 +74,46 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, DependsOn: []string{"ucd-util@0.1.10"}, }, + { + ID: "aho-corasick@0.7.20", + Name: "aho-corasick", + Version: "0.7.20", + Indirect: true, + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 11, + }, + }, + DependsOn: []string{"memchr@2.5.0"}, + }, + { + ID: "libc@0.2.140", + Name: "libc", + Version: "0.2.140", + Indirect: true, + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 22, + EndLine: 26, + }, + }, + }, + { + ID: "memchr@2.5.0", + Name: "memchr", + Version: "2.5.0", + Indirect: true, + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 37, + EndLine: 41, + }, + }, + }, { ID: "regex-syntax@0.6.29", Name: "regex-syntax", @@ -153,7 +153,7 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { { Type: types.Cargo, FilePath: "Cargo.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "memchr@2.5.0", Name: "memchr", @@ -180,7 +180,7 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { { Type: types.Cargo, FilePath: "Cargo.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "aho-corasick@0.7.20", Name: "aho-corasick", @@ -367,7 +367,7 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { { Type: types.Cargo, FilePath: "Cargo.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "app@0.1.0", Name: "app", @@ -413,21 +413,7 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { { Type: types.Cargo, FilePath: "Cargo.lock", - Libraries: types.Packages{ - { - ID: "aho-corasick@1.1.2", - Name: "aho-corasick", - Version: "1.1.2", - Indirect: true, - Relationship: types.RelationshipIndirect, - Locations: []types.Location{ - { - StartLine: 5, - EndLine: 12, - }, - }, - DependsOn: []string{"memchr@2.6.4"}, - }, + Packages: types.Packages{ { ID: "gdb-command@0.7.6", Name: "gdb-command", @@ -445,6 +431,38 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { "wait-timeout@0.2.0", }, }, + { + ID: "regex@1.10.2", + Name: "regex", + Version: "1.10.2", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 50, + EndLine: 60, + }, + }, + DependsOn: []string{ + "aho-corasick@1.1.2", + "memchr@2.6.4", + "regex-automata@0.4.3", + "regex-syntax@0.8.2", + }, + }, + { + ID: "aho-corasick@1.1.2", + Name: "aho-corasick", + Version: "1.1.2", + Indirect: true, + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 12, + }, + }, + DependsOn: []string{"memchr@2.6.4"}, + }, { ID: "libc@0.2.150", Name: "libc", @@ -471,24 +489,6 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { }, }, }, - { - ID: "regex@1.10.2", - Name: "regex", - Version: "1.10.2", - Relationship: types.RelationshipDirect, - Locations: []types.Location{ - { - StartLine: 50, - EndLine: 60, - }, - }, - DependsOn: []string{ - "aho-corasick@1.1.2", - "memchr@2.6.4", - "regex-automata@0.4.3", - "regex-syntax@0.8.2", - }, - }, { ID: "regex-automata@0.4.3", Name: "regex-automata", diff --git a/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go b/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go index fcf2e7254f59..5dfc9be18d56 100644 --- a/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go +++ b/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go @@ -26,7 +26,7 @@ func Test_cocoaPodsLockAnalyzer_Analyze(t *testing.T) { { Type: types.Cocoapods, FilePath: "testdata/happy.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "AppCenter@4.2.0", Name: "AppCenter", @@ -87,7 +87,7 @@ func Test_cocoaPodsLockAnalyzer_Analyze(t *testing.T) { if got != nil { for _, app := range got.Applications { - sort.Sort(app.Libraries) + sort.Sort(app.Packages) } } diff --git a/pkg/fanal/analyzer/language/swift/swift/swift_test.go b/pkg/fanal/analyzer/language/swift/swift/swift_test.go index 9a7fc981c1fc..73066aa69948 100644 --- a/pkg/fanal/analyzer/language/swift/swift/swift_test.go +++ b/pkg/fanal/analyzer/language/swift/swift/swift_test.go @@ -25,7 +25,7 @@ func Test_swiftLockAnalyzer_Analyze(t *testing.T) { { Type: types.Swift, FilePath: "testdata/happy/Package.resolved", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "github.com/Quick/Nimble@9.2.1", diff --git a/pkg/fanal/analyzer/sbom/sbom.go b/pkg/fanal/analyzer/sbom/sbom.go index efb9829a1593..6392f0200da3 100644 --- a/pkg/fanal/analyzer/sbom/sbom.go +++ b/pkg/fanal/analyzer/sbom/sbom.go @@ -77,17 +77,17 @@ func handleBitnamiImages(componentPath string, bom types.SBOM) { if app.Type == ftypes.Bitnami { // Set the component dir path to the application bom.Applications[i].FilePath = componentPath - // Either Application.FilePath or Application.Libraries[].FilePath should be set + // Either Application.FilePath or Application.Packages[].FilePath should be set continue } - for j, pkg := range app.Libraries { + for j, pkg := range app.Packages { // Set the absolute path since SBOM in Bitnami images contain a relative path // e.g. modules/apm/elastic-apm-agent-1.36.0.jar // => opt/bitnami/elasticsearch/modules/apm/elastic-apm-agent-1.36.0.jar // If the file path is empty, the file path will be set to the component dir path. filePath := path.Join(componentPath, pkg.FilePath) - bom.Applications[i].Libraries[j].FilePath = filePath + bom.Applications[i].Packages[j].FilePath = filePath } } } diff --git a/pkg/fanal/analyzer/sbom/sbom_test.go b/pkg/fanal/analyzer/sbom/sbom_test.go index 3bcb619d402b..c1c09b24a5bc 100644 --- a/pkg/fanal/analyzer/sbom/sbom_test.go +++ b/pkg/fanal/analyzer/sbom/sbom_test.go @@ -29,7 +29,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { Applications: []types.Application{ { Type: types.Jar, - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "co.elastic.apm:apm-agent:1.36.0", Name: "co.elastic.apm:apm-agent", @@ -91,7 +91,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { { Type: types.Bitnami, FilePath: "opt/bitnami/elasticsearch", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "Elasticsearch@8.9.1", Name: "Elasticsearch", @@ -126,7 +126,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { Applications: []types.Application{ { Type: types.Jar, - Libraries: types.Packages{ + Packages: types.Packages{ { FilePath: "opt/bitnami/elasticsearch/modules/apm/elastic-apm-agent-1.36.0.jar", ID: "co.elastic.apm:apm-agent:1.36.0", @@ -172,7 +172,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { { Type: types.Bitnami, FilePath: "opt/bitnami/postgresql", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "GDAL@3.7.1", Name: "GDAL", diff --git a/pkg/fanal/applier/applier_test.go b/pkg/fanal/applier/applier_test.go index bea3f30fa0c5..a1844c5bb545 100644 --- a/pkg/fanal/applier/applier_test.go +++ b/pkg/fanal/applier/applier_test.go @@ -111,7 +111,7 @@ func TestApplier_ApplyLayers(t *testing.T) { { Type: "composer", FilePath: "php-app/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "guzzlehttp/guzzle", Version: "6.2.0", @@ -200,7 +200,7 @@ func TestApplier_ApplyLayers(t *testing.T) { { Type: "composer", FilePath: "php-app/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "guzzlehttp/guzzle", Version: "6.2.0", @@ -623,7 +623,7 @@ func TestApplier_ApplyLayers(t *testing.T) { { Type: "composer", FilePath: "php-app/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "guzzlehttp/guzzle", Version: "6.2.0", @@ -672,7 +672,7 @@ func TestApplier_ApplyLayers(t *testing.T) { { Type: "composer", FilePath: "php-app/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "guzzlehttp/guzzle", Version: "6.2.0", @@ -833,7 +833,7 @@ func TestApplier_ApplyLayers(t *testing.T) { { Type: "composer", FilePath: "php-app/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "guzzlehttp/guzzle", Version: "6.2.0", @@ -885,7 +885,7 @@ func TestApplier_ApplyLayers(t *testing.T) { { Type: "composer", FilePath: "php-app/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "guzzlehttp/guzzle", Version: "6.2.0", @@ -980,7 +980,7 @@ func TestApplier_ApplyLayers(t *testing.T) { sort.Sort(got.Packages) for _, app := range got.Applications { - sort.Sort(app.Libraries) + sort.Sort(app.Packages) } sort.Slice(got.CustomResources, func(i, j int) bool { diff --git a/pkg/fanal/applier/docker.go b/pkg/fanal/applier/docker.go index 7640cfadaec6..41487819b4ae 100644 --- a/pkg/fanal/applier/docker.go +++ b/pkg/fanal/applier/docker.go @@ -81,7 +81,7 @@ func lookupOriginLayerForLib(filePath string, lib ftypes.Package, layers []ftype if filePath != layerApp.FilePath { continue } - if containsPackage(lib, layerApp.Libraries) { + if containsPackage(lib, layerApp.Packages) { return layer.Digest, layer.DiffID } } @@ -230,19 +230,19 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { } for _, app := range mergedLayer.Applications { - for i, lib := range app.Libraries { + for i, pkg := range app.Packages { // Skip lookup for SBOM - if lo.IsEmpty(lib.Layer) { - originLayerDigest, originLayerDiffID := lookupOriginLayerForLib(app.FilePath, lib, layers) - app.Libraries[i].Layer = ftypes.Layer{ + if lo.IsEmpty(pkg.Layer) { + originLayerDigest, originLayerDiffID := lookupOriginLayerForLib(app.FilePath, pkg, layers) + app.Packages[i].Layer = ftypes.Layer{ Digest: originLayerDigest, DiffID: originLayerDiffID, } } - if lib.Identifier.PURL == nil { - app.Libraries[i].Identifier.PURL = newPURL(app.Type, types.Metadata{}, lib) + if pkg.Identifier.PURL == nil { + app.Packages[i].Identifier.PURL = newPURL(app.Type, types.Metadata{}, pkg) } - app.Libraries[i].Identifier.UID = calcPkgUID(app.FilePath, lib) + app.Packages[i].Identifier.UID = calcPkgUID(app.FilePath, pkg) } } @@ -292,11 +292,11 @@ func aggregate(detail *ftypes.ArtifactDetail) { apps = append(apps, app) continue } - a.Libraries = append(a.Libraries, app.Libraries...) + a.Packages = append(a.Packages, app.Packages...) } for _, app := range aggregatedApps { - if len(app.Libraries) > 0 { + if len(app.Packages) > 0 { apps = append(apps, *app) } } diff --git a/pkg/fanal/applier/docker_test.go b/pkg/fanal/applier/docker_test.go index 9a5f043c6e3c..f5bccfa86bf6 100644 --- a/pkg/fanal/applier/docker_test.go +++ b/pkg/fanal/applier/docker_test.go @@ -44,7 +44,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.Bundler, FilePath: "app/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "gemlibrary1", Version: "1.2.3", @@ -54,7 +54,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.Composer, FilePath: "app/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "phplibrary1", Version: "6.6.6", @@ -64,7 +64,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.GemSpec, FilePath: "usr/local/bundle/specifications/gon-6.3.2.gemspec", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "gon", Version: "6.3.2", @@ -123,7 +123,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.GemSpec, FilePath: "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "activesupport", Version: "6.0.2.1", @@ -192,7 +192,7 @@ func TestApplyLayers(t *testing.T) { Applications: []types.Application{ { Type: types.GemSpec, - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "activesupport", Version: "6.0.2.1", @@ -232,7 +232,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.Bundler, FilePath: "app/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "gemlibrary1", Version: "1.2.3", @@ -284,7 +284,7 @@ func TestApplyLayers(t *testing.T) { Applications: []types.Application{ { Type: types.PythonPkg, - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "pip", Version: "23.0.1", @@ -339,7 +339,7 @@ func TestApplyLayers(t *testing.T) { Applications: []types.Application{ { Type: types.PythonPkg, - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "pip", Version: "23.0.1", @@ -406,7 +406,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.Bundler, FilePath: "app/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "rails", Version: "5.0.0", @@ -420,7 +420,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.Composer, FilePath: "app/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "phplibrary1", Version: "6.6.6", @@ -430,7 +430,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.GemSpec, FilePath: "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "activesupport", Version: "6.0.2.1", @@ -448,7 +448,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.Bundler, FilePath: "app/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "rails", Version: "6.0.0", @@ -462,7 +462,7 @@ func TestApplyLayers(t *testing.T) { { Type: "composer", FilePath: "app/composer2.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "phplibrary1", Version: "6.6.6", @@ -485,7 +485,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.Bundler, FilePath: "app/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "rack", Version: "4.0.0", @@ -523,7 +523,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.Composer, FilePath: "app/composer2.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "phplibrary1", Version: "6.6.6", @@ -736,7 +736,7 @@ func TestApplyLayers(t *testing.T) { { Type: "composer", FilePath: "app/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "phplibrary1", Version: "6.6.6", @@ -854,7 +854,7 @@ func TestApplyLayers(t *testing.T) { { Type: "composer", FilePath: "app/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "phplibrary1", Version: "6.6.6", @@ -1069,7 +1069,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.Bundler, FilePath: "app1/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "gemlibrary1", Version: "1.2.3", @@ -1079,7 +1079,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.Bundler, FilePath: "app2/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "gemlibrary1", Version: "1.2.3", @@ -1094,7 +1094,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.Bundler, FilePath: "app1/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "gemlibrary1", Version: "1.2.3", @@ -1116,7 +1116,7 @@ func TestApplyLayers(t *testing.T) { { Type: types.Bundler, FilePath: "app2/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "gemlibrary1", Version: "1.2.3", @@ -1148,7 +1148,7 @@ func TestApplyLayers(t *testing.T) { return got.Applications[i].FilePath < got.Applications[j].FilePath }) for _, app := range got.Applications { - sort.Sort(app.Libraries) + sort.Sort(app.Packages) } assert.Equal(t, tt.want, got, tt.name) }) diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go index d741b78e4c9f..fbcddef9ce4a 100644 --- a/pkg/fanal/artifact/image/image_test.go +++ b/pkg/fanal/artifact/image/image_test.go @@ -700,7 +700,7 @@ func TestArtifact_Inspect(t *testing.T) { { Type: "composer", FilePath: "php-app/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "guzzlehttp/guzzle@6.2.0", Name: "guzzlehttp/guzzle", @@ -908,7 +908,114 @@ func TestArtifact_Inspect(t *testing.T) { { Type: "bundler", FilePath: "ruby-app/Gemfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ + { + ID: "dotenv@2.7.2", + Name: "dotenv", + Version: "2.7.2", + Indirect: false, + Relationship: types.RelationshipDirect, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 51, + EndLine: 51, + }, + }, + }, + { + ID: "faker@1.9.3", + Name: "faker", + Version: "1.9.3", + Indirect: false, + Relationship: types.RelationshipDirect, + DependsOn: []string{"i18n@1.6.0"}, + Locations: []types.Location{ + { + StartLine: 53, + EndLine: 53, + }, + }, + }, + { + ID: "json@2.2.0", + Name: "json", + Version: "2.2.0", + Indirect: false, + Relationship: types.RelationshipDirect, + DependsOn: []string(nil), + Locations: []types.Location{ + { + StartLine: 60, + EndLine: 60, + }, + }, + }, + { + ID: "pry@0.12.2", + Name: "pry", + Version: "0.12.2", + Indirect: false, + Relationship: types.RelationshipDirect, + DependsOn: []string{ + "coderay@1.1.2", + "method_source@0.9.2", + }, + Locations: []types.Location{ + { + StartLine: 79, + EndLine: 79, + }, + }, + }, + { + ID: "rails@5.2.0", + Name: "rails", + Version: "5.2.0", + Indirect: false, + Relationship: types.RelationshipDirect, + DependsOn: []string{ + "actioncable@5.2.3", + "actionmailer@5.2.3", + "actionpack@5.2.3", + "actionview@5.2.3", + "activejob@5.2.3", + "activemodel@5.2.3", + "activerecord@5.2.3", + "activestorage@5.2.3", + "activesupport@5.2.3", + "railties@5.2.3", + "sprockets-rails@3.2.1", + }, + Locations: []types.Location{ + { + StartLine: 86, + EndLine: 86, + }, + }, + }, + { + ID: "rubocop@0.67.2", + Name: "rubocop", + Version: "0.67.2", + Indirect: false, + Relationship: types.RelationshipDirect, + DependsOn: []string{ + "jaro_winkler@1.5.2", + "parallel@1.17.0", + "parser@2.6.3.0", + "psych@3.1.0", + "rainbow@3.0.0", + "ruby-progressbar@1.10.0", + "unicode-display_width@1.5.0", + }, + Locations: []types.Location{ + { + StartLine: 112, + EndLine: 112, + }, + }, + }, { ID: "actioncable@5.2.3", Name: "actioncable", @@ -1158,20 +1265,6 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - { - ID: "dotenv@2.7.2", - Name: "dotenv", - Version: "2.7.2", - Indirect: false, - Relationship: types.RelationshipDirect, - DependsOn: []string(nil), - Locations: []types.Location{ - { - StartLine: 51, - EndLine: 51, - }, - }, - }, { ID: "erubi@1.8.0", Name: "erubi", @@ -1186,20 +1279,6 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - { - ID: "faker@1.9.3", - Name: "faker", - Version: "1.9.3", - Indirect: false, - Relationship: types.RelationshipDirect, - DependsOn: []string{"i18n@1.6.0"}, - Locations: []types.Location{ - { - StartLine: 53, - EndLine: 53, - }, - }, - }, { ID: "globalid@0.4.2", Name: "globalid", @@ -1242,20 +1321,6 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - { - ID: "json@2.2.0", - Name: "json", - Version: "2.2.0", - Indirect: false, - Relationship: types.RelationshipDirect, - DependsOn: []string(nil), - Locations: []types.Location{ - { - StartLine: 60, - EndLine: 60, - }, - }, - }, { ID: "loofah@2.2.3", Name: "loofah", @@ -1427,23 +1492,6 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - { - ID: "pry@0.12.2", - Name: "pry", - Version: "0.12.2", - Indirect: false, - Relationship: types.RelationshipDirect, - DependsOn: []string{ - "coderay@1.1.2", - "method_source@0.9.2", - }, - Locations: []types.Location{ - { - StartLine: 79, - EndLine: 79, - }, - }, - }, { ID: "psych@3.1.0", Name: "psych", @@ -1486,32 +1534,6 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - { - ID: "rails@5.2.0", - Name: "rails", - Version: "5.2.0", - Indirect: false, - Relationship: types.RelationshipDirect, - DependsOn: []string{ - "actioncable@5.2.3", - "actionmailer@5.2.3", - "actionpack@5.2.3", - "actionview@5.2.3", - "activejob@5.2.3", - "activemodel@5.2.3", - "activerecord@5.2.3", - "activestorage@5.2.3", - "activesupport@5.2.3", - "railties@5.2.3", - "sprockets-rails@3.2.1", - }, - Locations: []types.Location{ - { - StartLine: 86, - EndLine: 86, - }, - }, - }, { ID: "rails-dom-testing@2.0.3", Name: "rails-dom-testing", @@ -1591,28 +1613,6 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - { - ID: "rubocop@0.67.2", - Name: "rubocop", - Version: "0.67.2", - Indirect: false, - Relationship: types.RelationshipDirect, - DependsOn: []string{ - "jaro_winkler@1.5.2", - "parallel@1.17.0", - "parser@2.6.3.0", - "psych@3.1.0", - "rainbow@3.0.0", - "ruby-progressbar@1.10.0", - "unicode-display_width@1.5.0", - }, - Locations: []types.Location{ - { - StartLine: 112, - EndLine: 112, - }, - }, - }, { ID: "ruby-progressbar@1.10.0", Name: "ruby-progressbar", diff --git a/pkg/fanal/artifact/image/remote_sbom_test.go b/pkg/fanal/artifact/image/remote_sbom_test.go index ef777fe5c641..b38bdc5c6dc5 100644 --- a/pkg/fanal/artifact/image/remote_sbom_test.go +++ b/pkg/fanal/artifact/image/remote_sbom_test.go @@ -223,13 +223,13 @@ func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:7e0b5476a5ff5a10594ad1ed7566220fcc43ecff29b831236cb2e98e574a1d05", + BlobID: "sha256:a06ed679a3289fba254040e1ce8f3467fadcc454ee3d0d4720f6978065f56684", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ { Type: types.GoBinary, - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "github.com/opencontainers/go-digest@v1.0.0", Name: "github.com/opencontainers/go-digest", @@ -268,9 +268,9 @@ func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { want: types.ArtifactReference{ Name: registry + "/test/image:10", Type: types.ArtifactCycloneDX, - ID: "sha256:7e0b5476a5ff5a10594ad1ed7566220fcc43ecff29b831236cb2e98e574a1d05", + ID: "sha256:a06ed679a3289fba254040e1ce8f3467fadcc454ee3d0d4720f6978065f56684", BlobIDs: []string{ - "sha256:7e0b5476a5ff5a10594ad1ed7566220fcc43ecff29b831236cb2e98e574a1d05", + "sha256:a06ed679a3289fba254040e1ce8f3467fadcc454ee3d0d4720f6978065f56684", }, }, }, diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index 890679b7f45d..f230cb1340fc 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -174,14 +174,14 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", + BlobID: "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ { Type: "pip", FilePath: "requirements.txt", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "Flask", Version: "2.0.0", @@ -196,9 +196,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/requirements.txt", Type: types.ArtifactFilesystem, - ID: "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", + ID: "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", BlobIDs: []string{ - "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", + "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", }, }, }, @@ -209,14 +209,14 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", + BlobID: "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ { Type: "pip", FilePath: "requirements.txt", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "Flask", Version: "2.0.0", @@ -231,9 +231,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/requirements.txt", Type: types.ArtifactFilesystem, - ID: "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", + ID: "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", BlobIDs: []string{ - "sha256:f1101f37560adf2f5d9c8fef2ac66beff236be3be94b3ea48c0fb6f86867775f", + "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", }, }, }, diff --git a/pkg/fanal/artifact/sbom/sbom_test.go b/pkg/fanal/artifact/sbom/sbom_test.go index 3b26194cfa55..11ddfdad781b 100644 --- a/pkg/fanal/artifact/sbom/sbom_test.go +++ b/pkg/fanal/artifact/sbom/sbom_test.go @@ -30,7 +30,7 @@ func TestArtifact_Inspect(t *testing.T) { filePath: filepath.Join("testdata", "bom.json"), putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", + BlobID: "sha256:76bc49ae239d24c6a122e730bafb9d5295d0af380492aeb92a3bf34bea3a14ca", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -73,7 +73,7 @@ func TestArtifact_Inspect(t *testing.T) { { Type: "composer", FilePath: "app/composer/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "pear/log@1.13.1", Name: "pear/log", @@ -113,7 +113,7 @@ func TestArtifact_Inspect(t *testing.T) { { Type: "gobinary", FilePath: "app/gobinary/gobinary", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", Name: "github.com/package-url/packageurl-go", @@ -136,7 +136,7 @@ func TestArtifact_Inspect(t *testing.T) { { Type: "jar", FilePath: "", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "org.codehaus.mojo:child-project:1.0", Name: "org.codehaus.mojo:child-project", @@ -161,7 +161,7 @@ func TestArtifact_Inspect(t *testing.T) { { Type: "node-pkg", FilePath: "", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "bootstrap@5.0.2", Name: "bootstrap", @@ -191,9 +191,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: filepath.Join("testdata", "bom.json"), Type: types.ArtifactCycloneDX, - ID: "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", + ID: "sha256:76bc49ae239d24c6a122e730bafb9d5295d0af380492aeb92a3bf34bea3a14ca", BlobIDs: []string{ - "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", + "sha256:76bc49ae239d24c6a122e730bafb9d5295d0af380492aeb92a3bf34bea3a14ca", }, }, }, @@ -202,7 +202,7 @@ func TestArtifact_Inspect(t *testing.T) { filePath: filepath.Join("testdata", "sbom.cdx.intoto.jsonl"), putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", + BlobID: "sha256:76bc49ae239d24c6a122e730bafb9d5295d0af380492aeb92a3bf34bea3a14ca", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -245,7 +245,7 @@ func TestArtifact_Inspect(t *testing.T) { { Type: "composer", FilePath: "app/composer/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "pear/log@1.13.1", Name: "pear/log", @@ -285,7 +285,7 @@ func TestArtifact_Inspect(t *testing.T) { { Type: "gobinary", FilePath: "app/gobinary/gobinary", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", Name: "github.com/package-url/packageurl-go", @@ -308,7 +308,7 @@ func TestArtifact_Inspect(t *testing.T) { { Type: "jar", FilePath: "", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "org.codehaus.mojo:child-project:1.0", Name: "org.codehaus.mojo:child-project", @@ -333,7 +333,7 @@ func TestArtifact_Inspect(t *testing.T) { { Type: "node-pkg", FilePath: "", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "bootstrap@5.0.2", Name: "bootstrap", @@ -363,9 +363,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: filepath.Join("testdata", "sbom.cdx.intoto.jsonl"), Type: types.ArtifactCycloneDX, - ID: "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", + ID: "sha256:76bc49ae239d24c6a122e730bafb9d5295d0af380492aeb92a3bf34bea3a14ca", BlobIDs: []string{ - "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", + "sha256:76bc49ae239d24c6a122e730bafb9d5295d0af380492aeb92a3bf34bea3a14ca", }, }, }, diff --git a/pkg/fanal/cache/fs_test.go b/pkg/fanal/cache/fs_test.go index eaa050096d24..ba2d9fe33d01 100644 --- a/pkg/fanal/cache/fs_test.go +++ b/pkg/fanal/cache/fs_test.go @@ -189,7 +189,7 @@ func TestFSCache_PutBlob(t *testing.T) { { Type: "composer", FilePath: "php-app/composer.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "guzzlehttp/guzzle", Version: "6.2.0", @@ -235,7 +235,7 @@ func TestFSCache_PutBlob(t *testing.T) { { "Type": "composer", "FilePath": "php-app/composer.lock", - "Libraries": [ + "Packages": [ { "Name":"guzzlehttp/guzzle", "Version":"6.2.0", diff --git a/pkg/fanal/handler/sysfile/filter.go b/pkg/fanal/handler/sysfile/filter.go index 09525aebc1e7..5222049c0d16 100644 --- a/pkg/fanal/handler/sysfile/filter.go +++ b/pkg/fanal/handler/sysfile/filter.go @@ -58,7 +58,7 @@ func (h systemFileFilteringPostHandler) Handle(_ context.Context, result *analyz // Trim leading slashes to be the same format as the path in container images. systemFile := strings.TrimPrefix(file, "/") // We should check the root filepath ("/") and ignore it. - // Otherwise libraries with an empty filePath will be removed. + // Otherwise, packages with an empty filePath will be removed. if systemFile != "" { systemFiles = append(systemFiles, systemFile) } @@ -73,7 +73,7 @@ func (h systemFileFilteringPostHandler) Handle(_ context.Context, result *analyz } var pkgs []types.Package - for _, lib := range app.Libraries { + for _, lib := range app.Packages { // If the lang-specific package was installed by OS package manager, it should not be taken. // Otherwise, the package version will be wrong, then it will lead to false positive. if slices.Contains(systemFiles, lib.FilePath) { @@ -82,8 +82,8 @@ func (h systemFileFilteringPostHandler) Handle(_ context.Context, result *analyz pkgs = append(pkgs, lib) } - // Overwrite Libraries - app.Libraries = pkgs + // Overwrite Packages + app.Packages = pkgs apps = append(apps, app) } diff --git a/pkg/fanal/handler/sysfile/filter_test.go b/pkg/fanal/handler/sysfile/filter_test.go index 1b987fd5823f..158f349e9536 100644 --- a/pkg/fanal/handler/sysfile/filter_test.go +++ b/pkg/fanal/handler/sysfile/filter_test.go @@ -63,7 +63,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { { Type: types.Pipenv, FilePath: "app/Pipfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "django", Version: "3.1.2", @@ -72,7 +72,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { }, { Type: types.PythonPkg, - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "python", Version: "2.7.5", @@ -88,7 +88,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { { Type: types.PythonPkg, FilePath: "usr/lib64/python2.7/wsgiref.egg-info", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "wsgiref", Version: "0.1.2", @@ -98,7 +98,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { { Type: types.GoBinary, FilePath: "usr/local/bin/goBinariryFile", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "cloud.google.com/go", Version: "v0.81.0", @@ -140,7 +140,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { { Type: types.Pipenv, FilePath: "app/Pipfile.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "django", Version: "3.1.2", @@ -149,7 +149,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { }, { Type: types.PythonPkg, - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "pycurl", Version: "7.19.0", @@ -160,7 +160,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { { Type: types.GoBinary, FilePath: "usr/local/bin/goBinariryFile", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "cloud.google.com/go", Version: "v0.81.0", @@ -185,7 +185,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { { Type: types.PythonPkg, FilePath: "usr/lib/python2.7/lib-dynload/Python-2.7.egg-info", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "python", Version: "2.7.14", @@ -209,7 +209,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { { Type: types.GoBinary, FilePath: "usr/local/bin/goreleaser", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "github.com/sassoftware/go-rpmutils", Version: "v0.0.0-20190420191620-a8f1baeba37b", @@ -232,7 +232,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { { Type: types.Cargo, FilePath: "app/Cargo.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "ghash", Version: "0.4.4", @@ -246,7 +246,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { { Type: types.Cargo, FilePath: "app/Cargo.lock", - Libraries: types.Packages{ + Packages: types.Packages{ { Name: "ghash", Version: "0.4.4", diff --git a/pkg/fanal/handler/unpackaged/unpackaged_test.go b/pkg/fanal/handler/unpackaged/unpackaged_test.go index 685af042d131..be2ddd116151 100644 --- a/pkg/fanal/handler/unpackaged/unpackaged_test.go +++ b/pkg/fanal/handler/unpackaged/unpackaged_test.go @@ -41,7 +41,7 @@ func Test_unpackagedHook_Handle(t *testing.T) { { Type: types.GoModule, FilePath: "go.mod", - Libraries: types.Packages{ + Packages: types.Packages{ { ID: "github.com/spf13/cobra@v1.5.0", Name: "github.com/spf13/cobra", diff --git a/pkg/fanal/test/integration/library_test.go b/pkg/fanal/test/integration/library_test.go index f6a863fc82bd..9e2073185c60 100644 --- a/pkg/fanal/test/integration/library_test.go +++ b/pkg/fanal/test/integration/library_test.go @@ -326,16 +326,16 @@ func checkLangPkgs(detail types.ArtifactDetail, t *testing.T, tc testCase) { }) for _, app := range detail.Applications { - sort.Sort(app.Libraries) - for i := range app.Libraries { - sort.Strings(app.Libraries[i].DependsOn) + sort.Sort(app.Packages) + for i := range app.Packages { + sort.Strings(app.Packages[i].DependsOn) } } // Do not compare layers for _, app := range detail.Applications { - for i := range app.Libraries { - app.Libraries[i].Layer = types.Layer{} + for i := range app.Packages { + app.Packages[i].Layer = types.Layer{} } } diff --git a/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden b/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden index 58abc9c33585..7c52aef32965 100644 --- a/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden +++ b/pkg/fanal/test/integration/testdata/goldens/vuln-image1.2.3.expectedlibs.golden @@ -2,7 +2,138 @@ { "Type": "bundler", "FilePath": "ruby-app/Gemfile.lock", - "Libraries": [ + "Packages": [ + { + "ID": "dotenv@2.7.2", + "Name": "dotenv", + "Identifier": { + "PURL": "pkg:gem/dotenv@2.7.2", + "UID": "bf323ef200ea177a" + }, + "Version": "2.7.2", + "Relationship": "direct", + "Layer": {}, + "Locations": [ + { + "StartLine": 51, + "EndLine": 51 + } + ] + }, + { + "ID": "faker@1.9.3", + "Name": "faker", + "Identifier": { + "PURL": "pkg:gem/faker@1.9.3", + "UID": "b5ac01b24ab4ed39" + }, + "Version": "1.9.3", + "Relationship": "direct", + "DependsOn": [ + "i18n@1.6.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 53, + "EndLine": 53 + } + ] + }, + { + "ID": "json@2.2.0", + "Name": "json", + "Identifier": { + "PURL": "pkg:gem/json@2.2.0", + "UID": "cb6c1eb54f8c6e9d" + }, + "Version": "2.2.0", + "Relationship": "direct", + "Layer": {}, + "Locations": [ + { + "StartLine": 60, + "EndLine": 60 + } + ] + }, + { + "ID": "pry@0.12.2", + "Name": "pry", + "Identifier": { + "PURL": "pkg:gem/pry@0.12.2", + "UID": "1349a1afdbbc80bc" + }, + "Version": "0.12.2", + "Relationship": "direct", + "DependsOn": [ + "coderay@1.1.2", + "method_source@0.9.2" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 79, + "EndLine": 79 + } + ] + }, + { + "ID": "rails@5.2.0", + "Name": "rails", + "Identifier": { + "PURL": "pkg:gem/rails@5.2.0", + "UID": "edef417a0d4c73e" + }, + "Version": "5.2.0", + "Relationship": "direct", + "DependsOn": [ + "actioncable@5.2.3", + "actionmailer@5.2.3", + "actionpack@5.2.3", + "actionview@5.2.3", + "activejob@5.2.3", + "activemodel@5.2.3", + "activerecord@5.2.3", + "activestorage@5.2.3", + "activesupport@5.2.3", + "railties@5.2.3", + "sprockets-rails@3.2.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 86, + "EndLine": 86 + } + ] + }, + { + "ID": "rubocop@0.67.2", + "Name": "rubocop", + "Identifier": { + "PURL": "pkg:gem/rubocop@0.67.2", + "UID": "68a7b25b67dfb858" + }, + "Version": "0.67.2", + "Relationship": "direct", + "DependsOn": [ + "jaro_winkler@1.5.2", + "parallel@1.17.0", + "parser@2.6.3.0", + "psych@3.1.0", + "rainbow@3.0.0", + "ruby-progressbar@1.10.0", + "unicode-display_width@1.5.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 112, + "EndLine": 112 + } + ] + }, { "ID": "actioncable@5.2.3", "Name": "actioncable", @@ -323,23 +454,6 @@ } ] }, - { - "ID": "dotenv@2.7.2", - "Name": "dotenv", - "Identifier": { - "PURL": "pkg:gem/dotenv@2.7.2", - "UID": "bf323ef200ea177a" - }, - "Version": "2.7.2", - "Relationship": "direct", - "Layer": {}, - "Locations": [ - { - "StartLine": 51, - "EndLine": 51 - } - ] - }, { "ID": "erubi@1.8.0", "Name": "erubi", @@ -358,26 +472,6 @@ } ] }, - { - "ID": "faker@1.9.3", - "Name": "faker", - "Identifier": { - "PURL": "pkg:gem/faker@1.9.3", - "UID": "b5ac01b24ab4ed39" - }, - "Version": "1.9.3", - "Relationship": "direct", - "DependsOn": [ - "i18n@1.6.0" - ], - "Layer": {}, - "Locations": [ - { - "StartLine": 53, - "EndLine": 53 - } - ] - }, { "ID": "globalid@0.4.2", "Name": "globalid", @@ -438,23 +532,6 @@ } ] }, - { - "ID": "json@2.2.0", - "Name": "json", - "Identifier": { - "PURL": "pkg:gem/json@2.2.0", - "UID": "cb6c1eb54f8c6e9d" - }, - "Version": "2.2.0", - "Relationship": "direct", - "Layer": {}, - "Locations": [ - { - "StartLine": 60, - "EndLine": 60 - } - ] - }, { "ID": "loofah@2.2.3", "Name": "loofah", @@ -687,27 +764,6 @@ } ] }, - { - "ID": "pry@0.12.2", - "Name": "pry", - "Identifier": { - "PURL": "pkg:gem/pry@0.12.2", - "UID": "1349a1afdbbc80bc" - }, - "Version": "0.12.2", - "Relationship": "direct", - "DependsOn": [ - "coderay@1.1.2", - "method_source@0.9.2" - ], - "Layer": {}, - "Locations": [ - { - "StartLine": 79, - "EndLine": 79 - } - ] - }, { "ID": "psych@3.1.0", "Name": "psych", @@ -765,36 +821,6 @@ } ] }, - { - "ID": "rails@5.2.0", - "Name": "rails", - "Identifier": { - "PURL": "pkg:gem/rails@5.2.0", - "UID": "edef417a0d4c73e" - }, - "Version": "5.2.0", - "Relationship": "direct", - "DependsOn": [ - "actioncable@5.2.3", - "actionmailer@5.2.3", - "actionpack@5.2.3", - "actionview@5.2.3", - "activejob@5.2.3", - "activemodel@5.2.3", - "activerecord@5.2.3", - "activestorage@5.2.3", - "activesupport@5.2.3", - "railties@5.2.3", - "sprockets-rails@3.2.1" - ], - "Layer": {}, - "Locations": [ - { - "StartLine": 86, - "EndLine": 86 - } - ] - }, { "ID": "rails-dom-testing@2.0.3", "Name": "rails-dom-testing", @@ -899,32 +925,6 @@ } ] }, - { - "ID": "rubocop@0.67.2", - "Name": "rubocop", - "Identifier": { - "PURL": "pkg:gem/rubocop@0.67.2", - "UID": "68a7b25b67dfb858" - }, - "Version": "0.67.2", - "Relationship": "direct", - "DependsOn": [ - "jaro_winkler@1.5.2", - "parallel@1.17.0", - "parser@2.6.3.0", - "psych@3.1.0", - "rainbow@3.0.0", - "ruby-progressbar@1.10.0", - "unicode-display_width@1.5.0" - ], - "Layer": {}, - "Locations": [ - { - "StartLine": 112, - "EndLine": 112 - } - ] - }, { "ID": "ruby-progressbar@1.10.0", "Name": "ruby-progressbar", @@ -1107,7 +1107,7 @@ { "Type": "cargo", "FilePath": "rust-app/Cargo.lock", - "Libraries": [ + "Packages": [ { "ID": "ammonia@1.9.0", "Name": "ammonia", @@ -2519,7 +2519,7 @@ { "Type": "composer", "FilePath": "php-app/composer.lock", - "Libraries": [ + "Packages": [ { "ID": "guzzlehttp/guzzle@6.2.0", "Name": "guzzlehttp/guzzle", @@ -2815,7 +2815,7 @@ { "Type": "npm", "FilePath": "node-app/package-lock.json", - "Libraries": [ + "Packages": [ { "ID": "asap@2.0.6", "Name": "asap", @@ -3054,7 +3054,7 @@ { "Type": "pipenv", "FilePath": "python-app/Pipfile.lock", - "Libraries": [ + "Packages": [ { "Name": "amqp", "Identifier": { diff --git a/pkg/fanal/types/artifact.go b/pkg/fanal/types/artifact.go index 6c213a3fad18..ff8da07b4e2c 100644 --- a/pkg/fanal/types/artifact.go +++ b/pkg/fanal/types/artifact.go @@ -8,8 +8,8 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/package-url/packageurl-go" "github.com/samber/lo" + "golang.org/x/xerrors" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/digest" "github.com/aquasecurity/trivy/pkg/sbom/core" ) @@ -66,31 +66,63 @@ type Layer struct { CreatedBy string `json:",omitempty"` } -// TODO: merge pkg/dependency/types/types.go into this file -type Relationship = godeptypes.Relationship +type Relationship int const ( - RelationshipUnknown = godeptypes.RelationshipUnknown - RelationshipRoot = godeptypes.RelationshipRoot - RelationshipDirect = godeptypes.RelationshipDirect - RelationshipIndirect = godeptypes.RelationshipIndirect + RelationshipUnknown Relationship = iota + RelationshipRoot + RelationshipDirect + RelationshipIndirect ) +var relationshipNames = [...]string{ + "unknown", + "root", + "direct", + "indirect", +} + +func (r Relationship) String() string { + if r <= RelationshipUnknown || int(r) >= len(relationshipNames) { + return "unknown" + } + return relationshipNames[r] +} + +func (r Relationship) MarshalJSON() ([]byte, error) { + return json.Marshal(r.String()) +} + +func (r *Relationship) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + for i, name := range relationshipNames { + if s == name { + *r = Relationship(i) + return nil + } + } + return xerrors.Errorf("invalid relationship (%s)", s) +} + type Package struct { - ID string `json:",omitempty"` - Name string `json:",omitempty"` - Identifier PkgIdentifier `json:",omitempty"` - Version string `json:",omitempty"` - Release string `json:",omitempty"` - Epoch int `json:",omitempty"` - Arch string `json:",omitempty"` - Dev bool `json:",omitempty"` - SrcName string `json:",omitempty"` - SrcVersion string `json:",omitempty"` - SrcRelease string `json:",omitempty"` - SrcEpoch int `json:",omitempty"` - Licenses []string `json:",omitempty"` - Maintainer string `json:",omitempty"` + ID string `json:",omitempty"` + Name string `json:",omitempty"` + Identifier PkgIdentifier `json:",omitempty"` + Version string `json:",omitempty"` + Release string `json:",omitempty"` + Epoch int `json:",omitempty"` + Arch string `json:",omitempty"` + Dev bool `json:",omitempty"` + SrcName string `json:",omitempty"` + SrcVersion string `json:",omitempty"` + SrcRelease string `json:",omitempty"` + SrcEpoch int `json:",omitempty"` + Licenses []string `json:",omitempty"` + Maintainer string `json:",omitempty"` + ExternalReferences []ExternalRef `json:"-"` Modularitylabel string `json:",omitempty"` // only for Red Hat based distributions BuildInfo *BuildInfo `json:",omitempty"` // only for Red Hat @@ -111,7 +143,7 @@ type Package struct { Digest digest.Digest `json:",omitempty"` // lines from the lock file where the dependency is written - Locations []Location `json:",omitempty"` + Locations Locations `json:",omitempty"` // Files installed by the package InstalledFiles []string `json:",omitempty"` @@ -188,11 +220,44 @@ func (id *PkgIdentifier) Match(s string) bool { return false } +type Dependency struct { + ID string + DependsOn []string +} + +type Dependencies []Dependency + +func (deps Dependencies) Len() int { return len(deps) } +func (deps Dependencies) Less(i, j int) bool { + return deps[i].ID < deps[j].ID +} +func (deps Dependencies) Swap(i, j int) { deps[i], deps[j] = deps[j], deps[i] } + type Location struct { StartLine int `json:",omitempty"` EndLine int `json:",omitempty"` } +type Locations []Location + +func (locs Locations) Len() int { return len(locs) } +func (locs Locations) Less(i, j int) bool { + return locs[i].StartLine < locs[j].StartLine +} +func (locs Locations) Swap(i, j int) { locs[i], locs[j] = locs[j], locs[i] } + +type ExternalRef struct { + Type RefType + URL string +} + +type RefType string + +const ( + RefVCS RefType = "vcs" + RefOther RefType = "other" +) + // BuildInfo represents information under /root/buildinfo in RHEL type BuildInfo struct { ContentSets []string `json:",omitempty"` @@ -216,6 +281,13 @@ func (pkgs Packages) Swap(i, j int) { func (pkgs Packages) Less(i, j int) bool { switch { + case pkgs[i].Relationship != pkgs[j].Relationship: + if pkgs[i].Relationship == RelationshipUnknown { + return false + } else if pkgs[j].Relationship == RelationshipUnknown { + return true + } + return pkgs[i].Relationship < pkgs[j].Relationship case pkgs[i].Name != pkgs[j].Name: return pkgs[i].Name < pkgs[j].Name case pkgs[i].Version != pkgs[j].Version: @@ -260,8 +332,8 @@ type Application struct { // Lock files have the file path here, while each package metadata do not have FilePath string `json:",omitempty"` - // Libraries is a list of lang-specific packages - Libraries Packages + // Packages is a list of lang-specific packages + Packages Packages } type File struct { diff --git a/pkg/k8s/scanner/scanner.go b/pkg/k8s/scanner/scanner.go index b0c055e0b886..2ac380dadb58 100644 --- a/pkg/k8s/scanner/scanner.go +++ b/pkg/k8s/scanner/scanner.go @@ -235,7 +235,7 @@ func (s *Scanner) scanK8sVulns(ctx context.Context, artifactsData []*artifacts.A { Type: ftypes.LangType(lang), FilePath: artifact.Name, - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: comp.Name, Version: comp.Version, @@ -271,7 +271,7 @@ func (s *Scanner) scanK8sVulns(ctx context.Context, artifactsData []*artifacts.A { Type: ftypes.LangType(lang), FilePath: artifact.Name, - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: kubelet, Version: kubeletVersion, @@ -281,7 +281,7 @@ func (s *Scanner) scanK8sVulns(ctx context.Context, artifactsData []*artifacts.A { Type: ftypes.GoBinary, FilePath: artifact.Name, - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: runtimeName, Version: runtimeVersion, @@ -316,7 +316,7 @@ func (s *Scanner) scanK8sVulns(ctx context.Context, artifactsData []*artifacts.A { Type: ftypes.LangType(lang), FilePath: artifact.Name, - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: cf.Name, Version: cf.Version, diff --git a/pkg/licensing/normalize.go b/pkg/licensing/normalize.go index 0493747a9430..07105e329357 100644 --- a/pkg/licensing/normalize.go +++ b/pkg/licensing/normalize.go @@ -180,6 +180,7 @@ var pythonLicenseExceptions = map[string]string{ var licenseSplitRegexp = regexp.MustCompile("(,?[_ ]+(?:or|and)[_ ]+)|(,[ ]*)") func Normalize(name string) string { + name = strings.TrimSpace(name) if l, ok := mapping[strings.ToUpper(name)]; ok { return l } @@ -187,6 +188,9 @@ func Normalize(name string) string { } func SplitLicenses(str string) []string { + if str == "" { + return nil + } var licenses []string for _, maybeLic := range licenseSplitRegexp.Split(str, -1) { lower := strings.ToLower(maybeLic) diff --git a/pkg/rpc/convert.go b/pkg/rpc/convert.go index 033b1944274a..dcd81ce85ded 100644 --- a/pkg/rpc/convert.go +++ b/pkg/rpc/convert.go @@ -740,9 +740,9 @@ func ConvertFromRPCApplications(rpcApps []*common.Application) []ftypes.Applicat var apps []ftypes.Application for _, rpcApp := range rpcApps { apps = append(apps, ftypes.Application{ - Type: ftypes.LangType(rpcApp.Type), - FilePath: rpcApp.FilePath, - Libraries: ConvertFromRPCPkgs(rpcApp.Libraries), + Type: ftypes.LangType(rpcApp.Type), + FilePath: rpcApp.FilePath, + Packages: ConvertFromRPCPkgs(rpcApp.Packages), }) } return apps @@ -865,9 +865,9 @@ func ConvertToRPCPutBlobRequest(diffID string, blobInfo ftypes.BlobInfo) *cache. var applications []*common.Application for _, app := range blobInfo.Applications { applications = append(applications, &common.Application{ - Type: string(app.Type), - FilePath: app.FilePath, - Libraries: ConvertToRPCPkgs(app.Libraries), + Type: string(app.Type), + FilePath: app.FilePath, + Packages: ConvertToRPCPkgs(app.Packages), }) } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 3022ff2cdf0a..a472fcbe8443 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -363,7 +363,7 @@ func TestCacheServer_PutBlob(t *testing.T) { { Type: "composer", FilePath: "php-app/composer.lock", - Libraries: []*common.Package{ + Packages: []*common.Package{ { Name: "guzzlehttp/guzzle", Version: "6.2.0", @@ -444,7 +444,7 @@ func TestCacheServer_PutBlob(t *testing.T) { { Type: "composer", FilePath: "php-app/composer.lock", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { Name: "guzzlehttp/guzzle", Version: "6.2.0", diff --git a/pkg/sbom/cyclonedx/unmarshal_test.go b/pkg/sbom/cyclonedx/unmarshal_test.go index 6ffd9ce89ad9..9988d21c75ed 100644 --- a/pkg/sbom/cyclonedx/unmarshal_test.go +++ b/pkg/sbom/cyclonedx/unmarshal_test.go @@ -77,7 +77,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { { Type: ftypes.Composer, FilePath: "app/composer/composer.lock", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "pear/log@1.13.1", Name: "pear/log", @@ -117,7 +117,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { { Type: ftypes.GoBinary, FilePath: "app/gobinary/gobinary", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", Name: "github.com/package-url/packageurl-go", @@ -140,7 +140,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { { Type: ftypes.Gradle, FilePath: "app/gradle/target/gradle.lockfile", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "com.example:example:0.0.1", Name: "com.example:example", @@ -162,7 +162,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, { Type: ftypes.Jar, - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "org.codehaus.mojo:child-project:1.0", Name: "org.codehaus.mojo:child-project", @@ -186,7 +186,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { { Type: ftypes.NodePkg, FilePath: "", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "@example/bootstrap@5.0.2", Name: "@example/bootstrap", @@ -224,7 +224,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Applications: []ftypes.Application{ { Type: ftypes.GoBinary, - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "docker@v24.0.4", Name: "docker", @@ -242,7 +242,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, { Type: ftypes.K8sUpstream, - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "k8s.io/apiserver@1.27.4", Name: "k8s.io/apiserver", @@ -474,7 +474,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { { Type: "composer", FilePath: "", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "pear/log@1.13.1", Name: "pear/log", @@ -517,7 +517,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { { Type: "composer", FilePath: "", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "pear/log@1.13.1", Name: "pear/log", @@ -545,7 +545,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { { Type: "composer", FilePath: "", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "pear/log@1.13.1", Name: "pear/log", @@ -587,7 +587,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { { Type: "composer", FilePath: "", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "pear/core@1.13.1", Name: "pear/core", @@ -646,7 +646,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Applications: []ftypes.Application{ { Type: "jar", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "org.springframework:spring-web:5.3.22", Name: "org.springframework:spring-web", @@ -715,7 +715,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Applications: []ftypes.Application{ { Type: ftypes.Composer, - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "pear/core@1.13.1", Name: "pear/core", diff --git a/pkg/sbom/io/decode.go b/pkg/sbom/io/decode.go index 4a699eb59d76..84039973c213 100644 --- a/pkg/sbom/io/decode.go +++ b/pkg/sbom/io/decode.go @@ -334,7 +334,7 @@ func (m *Decoder) addLangPkgs(sbom *types.SBOM) { if !ok { continue } - app.Libraries = append(app.Libraries, *pkg) + app.Packages = append(app.Packages, *pkg) delete(m.pkgs, rel.Dependency) // Delete the added package } sbom.Applications = append(sbom.Applications, *app) @@ -380,8 +380,8 @@ func (m *Decoder) addOrphanPkgs(sbom *types.SBOM) error { for pkgType, pkgs := range langPkgMap { sort.Sort(pkgs) sbom.Applications = append(sbom.Applications, ftypes.Application{ - Type: pkgType, - Libraries: pkgs, + Type: pkgType, + Packages: pkgs, }) } return nil diff --git a/pkg/sbom/spdx/unmarshal_test.go b/pkg/sbom/spdx/unmarshal_test.go index 73f7d2dc934f..4348618e0f4d 100644 --- a/pkg/sbom/spdx/unmarshal_test.go +++ b/pkg/sbom/spdx/unmarshal_test.go @@ -77,7 +77,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { { Type: "composer", FilePath: "app/composer/composer.lock", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "pear/log@1.13.1", Name: "pear/log", @@ -115,7 +115,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { { Type: "gobinary", FilePath: "app/gobinary/gobinary", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", Name: "github.com/package-url/packageurl-go", @@ -136,7 +136,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, { Type: "jar", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "org.codehaus.mojo:child-project:1.0", Name: "org.codehaus.mojo:child-project", @@ -157,7 +157,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, { Type: "node-pkg", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "bootstrap@5.0.2", Name: "bootstrap", @@ -186,7 +186,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Applications: []ftypes.Application{ { Type: ftypes.NodePkg, - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "yargs-parser@21.1.1", Name: "yargs-parser", @@ -213,7 +213,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Applications: []ftypes.Application{ { Type: "node-pkg", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "yargs-parser@21.1.1", Name: "yargs-parser", @@ -241,7 +241,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { { Type: "composer", FilePath: "app/composer/composer.lock", - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "pear/log@1.13.1", Name: "pear/log", @@ -280,7 +280,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Applications: []ftypes.Application{ { Type: ftypes.Jar, - Libraries: ftypes.Packages{ + Packages: ftypes.Packages{ { ID: "co.elastic.apm:apm-agent:1.36.0", Name: "co.elastic.apm:apm-agent", diff --git a/pkg/scanner/langpkg/scan.go b/pkg/scanner/langpkg/scan.go index c718c37c4848..2606727d56e8 100644 --- a/pkg/scanner/langpkg/scan.go +++ b/pkg/scanner/langpkg/scan.go @@ -37,7 +37,7 @@ func NewScanner() Scanner { func (s *scanner) Packages(target types.ScanTarget, _ types.ScanOptions) types.Results { var results types.Results for _, app := range target.Applications { - if len(app.Libraries) == 0 { + if len(app.Packages) == 0 { continue } @@ -45,7 +45,7 @@ func (s *scanner) Packages(target types.ScanTarget, _ types.ScanOptions) types.R Target: targetName(app.Type, app.FilePath), Class: types.ClassLangPkg, Type: app.Type, - Packages: app.Libraries, + Packages: app.Packages, }) } return results @@ -61,7 +61,7 @@ func (s *scanner) Scan(ctx context.Context, target types.ScanTarget, _ types.Sca var results types.Results printedTypes := make(map[ftypes.LangType]struct{}) for _, app := range apps { - if len(app.Libraries) == 0 { + if len(app.Packages) == 0 { continue } @@ -74,9 +74,9 @@ func (s *scanner) Scan(ctx context.Context, target types.ScanTarget, _ types.Sca } log.DebugContext(ctx, "Scanning packages from the file", log.String("file_path", app.FilePath)) - vulns, err := library.Detect(ctx, app.Type, app.Libraries) + vulns, err := library.Detect(ctx, app.Type, app.Packages) if err != nil { - return nil, xerrors.Errorf("failed vulnerability detection of libraries: %w", err) + return nil, xerrors.Errorf("failed vulnerability detection of packages: %w", err) } else if len(vulns) == 0 { continue } diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 2f64a3b7693b..4b1591e0a52f 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -311,7 +311,7 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions // License - language-specific packages for _, app := range target.Applications { var langLicenses []types.DetectedLicense - for _, lib := range app.Libraries { + for _, lib := range app.Packages { for _, license := range lib.Licenses { category, severity := scanner.Scan(license) langLicenses = append(langLicenses, types.DetectedLicense{ @@ -435,7 +435,7 @@ func excludeDevDeps(apps []ftypes.Application, include bool) { log.Info("Suppressing dependencies for development and testing. To display them, try the '--include-dev-deps' flag.") }) for i := range apps { - apps[i].Libraries = lo.Filter(apps[i].Libraries, func(lib ftypes.Package, index int) bool { + apps[i].Packages = lo.Filter(apps[i].Packages, func(lib ftypes.Package, index int) bool { if lib.Dev { onceInfo() } diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go index 3b173957cc23..f15c90f2964c 100644 --- a/pkg/scanner/local/scan_test.go +++ b/pkg/scanner/local/scan_test.go @@ -75,7 +75,7 @@ func TestScanner_Scan(t *testing.T) { { Type: ftypes.Bundler, FilePath: "/app/Gemfile.lock", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", @@ -184,7 +184,7 @@ func TestScanner_Scan(t *testing.T) { { Type: ftypes.GoModule, FilePath: "/app/go.mod", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "github.com/google/uuid", Version: "1.6.0", @@ -199,7 +199,7 @@ func TestScanner_Scan(t *testing.T) { { Type: ftypes.PythonPkg, FilePath: "", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "urllib3", Version: "3.2.1", @@ -318,7 +318,7 @@ func TestScanner_Scan(t *testing.T) { { Type: "bundler", FilePath: "/app/Gemfile.lock", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", @@ -470,7 +470,7 @@ func TestScanner_Scan(t *testing.T) { { Type: "bundler", FilePath: "/app/Gemfile.lock", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", @@ -556,7 +556,7 @@ func TestScanner_Scan(t *testing.T) { { Type: ftypes.Bundler, FilePath: "/app/Gemfile.lock", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "innocent", // no vulnerability Version: "1.2.3", @@ -569,7 +569,7 @@ func TestScanner_Scan(t *testing.T) { { Type: ftypes.Bundler, FilePath: "/app/Gemfile.lock", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "rails", // one vulnerability Version: "4.0.2", @@ -637,7 +637,7 @@ func TestScanner_Scan(t *testing.T) { { Type: "bundler", FilePath: "", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", @@ -647,7 +647,7 @@ func TestScanner_Scan(t *testing.T) { { Type: "composer", FilePath: "", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "laravel/framework", Version: "6.0.0", @@ -740,7 +740,7 @@ func TestScanner_Scan(t *testing.T) { { Type: "bundler", FilePath: "/app/Gemfile.lock", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", @@ -824,7 +824,7 @@ func TestScanner_Scan(t *testing.T) { { Type: "bundler", FilePath: "/app/Gemfile.lock", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", @@ -930,7 +930,7 @@ func TestScanner_Scan(t *testing.T) { { Type: "bundler", FilePath: "/app/Gemfile.lock", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", @@ -943,7 +943,7 @@ func TestScanner_Scan(t *testing.T) { { Type: "composer", FilePath: "/app/composer-lock.json", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "laravel/framework", Version: "6.0.0", @@ -1216,7 +1216,7 @@ func TestScanner_Scan(t *testing.T) { { Type: "bundler", FilePath: "/app/Gemfile.lock", - Libraries: []ftypes.Package{ + Packages: []ftypes.Package{ { Name: "rails", Version: "6.0", diff --git a/pkg/x/slices/slices.go b/pkg/x/slices/slices.go new file mode 100644 index 000000000000..8e256814bbc7 --- /dev/null +++ b/pkg/x/slices/slices.go @@ -0,0 +1,8 @@ +package slices + +func ZeroToNil[T any](t []T) []T { + if len(t) == 0 { + return nil + } + return t +} diff --git a/rpc/common/service.pb.go b/rpc/common/service.pb.go index 4fcd09927d90..c8d829762956 100644 --- a/rpc/common/service.pb.go +++ b/rpc/common/service.pb.go @@ -379,9 +379,9 @@ type Application struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` - FilePath string `protobuf:"bytes,2,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` - Libraries []*Package `protobuf:"bytes,3,rep,name=libraries,proto3" json:"libraries,omitempty"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + FilePath string `protobuf:"bytes,2,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + Packages []*Package `protobuf:"bytes,3,rep,name=packages,proto3" json:"packages,omitempty"` } func (x *Application) Reset() { @@ -430,9 +430,9 @@ func (x *Application) GetFilePath() string { return "" } -func (x *Application) GetLibraries() []*Package { +func (x *Application) GetPackages() []*Package { if x != nil { - return x.Libraries + return x.Packages } return nil } @@ -2421,367 +2421,367 @@ var file_rpc_common_service_proto_rawDesc = []byte{ 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x31, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x08, - 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x22, 0x73, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x6c, + 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x22, 0x71, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x33, 0x0a, 0x09, 0x6c, 0x69, 0x62, 0x72, - 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, - 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, - 0x67, 0x65, 0x52, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x22, 0xc1, 0x04, - 0x0a, 0x07, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, - 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x3b, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, - 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x6b, 0x67, 0x49, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x63, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x72, 0x63, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x72, 0x63, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x72, 0x63, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x72, 0x63, 0x5f, 0x72, 0x65, 0x6c, 0x65, - 0x61, 0x73, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x72, 0x63, 0x52, 0x65, - 0x6c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x72, 0x63, 0x5f, 0x65, 0x70, 0x6f, - 0x63, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x73, 0x72, 0x63, 0x45, 0x70, 0x6f, - 0x63, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x0f, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x34, - 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0b, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, - 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, - 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x5f, 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x09, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x4f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x64, - 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, - 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x65, 0x76, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x03, 0x64, 0x65, 0x76, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, - 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, - 0x74, 0x22, 0x4e, 0x0a, 0x0d, 0x50, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, - 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x70, 0x75, 0x72, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x62, 0x6f, 0x6d, 0x5f, 0x72, 0x65, - 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x6f, 0x6d, 0x52, 0x65, 0x66, 0x12, - 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, - 0x64, 0x22, 0x44, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, - 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, - 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, - 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x22, 0xb6, 0x02, 0x0a, 0x10, 0x4d, 0x69, 0x73, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, - 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x66, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, - 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, - 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x39, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, - 0x73, 0x12, 0x37, 0x0a, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x52, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x61, - 0x69, 0x6c, 0x75, 0x72, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, - 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, - 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x75, - 0x72, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x0a, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x31, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, + 0x61, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, + 0x65, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x22, 0xc1, 0x04, 0x0a, 0x07, + 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, + 0x65, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x3b, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x61, 0x72, 0x63, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x72, 0x63, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x72, 0x63, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x72, 0x63, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x72, 0x63, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x72, 0x63, 0x52, 0x65, 0x6c, 0x65, + 0x61, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x72, 0x63, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x73, 0x72, 0x63, 0x45, 0x70, 0x6f, 0x63, 0x68, + 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x09, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, + 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x1b, 0x0a, + 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, + 0x70, 0x65, 0x6e, 0x64, 0x73, 0x5f, 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, + 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x4f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, + 0x65, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, + 0x74, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x65, 0x76, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, + 0x64, 0x65, 0x76, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, + 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, + 0x4e, 0x0a, 0x0d, 0x50, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x12, 0x12, 0x0a, 0x04, 0x70, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x70, 0x75, 0x72, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x62, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x66, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x6f, 0x6d, 0x52, 0x65, 0x66, 0x12, 0x10, 0x0a, + 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x22, + 0x44, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, + 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, + 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x22, 0xb6, 0x02, 0x0a, 0x10, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, + 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, + 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, + 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, + 0x50, 0x61, 0x74, 0x68, 0x12, 0x39, 0x0a, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0a, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x22, 0xf3, 0x01, 0x0a, 0x0d, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x45, 0x0a, 0x0f, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x52, 0x0e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x12, 0x42, 0x0a, 0x0e, 0x63, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x07, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x52, 0x02, 0x69, 0x64, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x65, - 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x22, 0xf0, 0x01, 0x0a, 0x0e, 0x50, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x64, 0x76, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x64, 0x76, 0x49, 0x64, - 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, - 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2f, 0x0a, 0x13, 0x72, 0x65, 0x63, 0x6f, - 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, - 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, - 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x22, 0xf7, 0x03, 0x0a, 0x18, 0x44, 0x65, - 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, - 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, - 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, - 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, - 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, - 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, - 0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1f, - 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x55, 0x72, 0x6c, 0x12, - 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x0a, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, - 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, - 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, - 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x63, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, - 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x76, 0x64, 0x5f, 0x69, 0x64, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x76, 0x64, 0x49, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x22, 0xff, 0x09, 0x0a, 0x0d, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, - 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, - 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x49, 0x64, - 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, - 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x78, 0x65, - 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x66, 0x69, 0x78, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, - 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, - 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, - 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, - 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, - 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x70, 0x6b, 0x67, - 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28, + 0x73, 0x75, 0x6c, 0x74, 0x52, 0x09, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, + 0x37, 0x0a, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x50, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0d, - 0x70, 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x29, 0x0a, - 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, - 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, - 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x76, 0x65, - 0x72, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x12, 0x39, 0x0a, 0x04, 0x63, 0x76, 0x73, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x25, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, - 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x43, 0x76, 0x73, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x63, 0x76, 0x73, 0x73, 0x12, 0x17, 0x0a, 0x07, - 0x63, 0x77, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x63, - 0x77, 0x65, 0x49, 0x64, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, - 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, - 0x61, 0x72, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x41, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x73, 0x68, 0x65, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, 0x73, - 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, - 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x44, - 0x61, 0x74, 0x65, 0x12, 0x48, 0x0a, 0x14, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x61, 0x64, - 0x76, 0x69, 0x73, 0x6f, 0x72, 0x79, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x11, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x63, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x41, 0x64, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x12, 0x40, 0x0a, - 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x76, 0x75, 0x6c, 0x6e, 0x5f, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, - 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x75, 0x6c, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, - 0x1d, 0x0a, 0x0a, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x13, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x09, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x49, 0x64, 0x73, 0x12, 0x39, - 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x14, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0a, 0x64, - 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x76, 0x65, 0x6e, - 0x64, 0x6f, 0x72, 0x5f, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x15, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, - 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x0e, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, - 0x69, 0x74, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, - 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x15, - 0x0a, 0x06, 0x70, 0x6b, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x70, 0x6b, 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x18, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x4b, 0x0a, - 0x09, 0x43, 0x76, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, - 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x56, 0x53, 0x53, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x59, 0x0a, 0x13, 0x56, 0x65, - 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x42, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x57, 0x0a, 0x05, 0x4c, 0x61, 0x79, - 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x69, - 0x66, 0x66, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x66, - 0x66, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x42, 0x79, 0x22, 0xc3, 0x01, 0x0a, 0x0d, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, - 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, - 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, - 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, - 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x76, 0x0a, 0x04, 0x43, 0x56, 0x53, 0x53, - 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x32, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x76, 0x32, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x0a, - 0x09, 0x76, 0x33, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x76, 0x33, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x32, - 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x32, - 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x33, 0x5f, 0x73, 0x63, 0x6f, 0x72, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x33, 0x53, 0x63, 0x6f, 0x72, 0x65, - 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, - 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, - 0x50, 0x61, 0x74, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, - 0x2a, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xf3, 0x01, 0x0a, 0x04, - 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x63, 0x61, 0x75, - 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x43, 0x61, 0x75, 0x73, - 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, - 0x20, 0x0a, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, - 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x43, 0x61, 0x75, - 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x43, 0x61, 0x75, 0x73, - 0x65, 0x22, 0x30, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x6c, 0x69, 0x6e, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x05, 0x6c, 0x69, - 0x6e, 0x65, 0x73, 0x22, 0x9f, 0x02, 0x0a, 0x0d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1a, - 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, - 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, - 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, - 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, - 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, - 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, - 0x61, 0x74, 0x63, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4a, - 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0x5d, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, - 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x12, 0x37, 0x0a, 0x08, 0x66, - 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, - 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x73, 0x22, 0x85, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, - 0x64, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, - 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, + 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x08, + 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, + 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x08, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x73, 0x12, 0x3b, 0x0a, 0x0a, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x0a, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf3, + 0x01, 0x0a, 0x0d, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x45, 0x0a, 0x0f, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, + 0x0e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, + 0x42, 0x0a, 0x0e, 0x63, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x07, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x52, + 0x02, 0x69, 0x64, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x22, 0xf0, 0x01, 0x0a, 0x0e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x64, 0x76, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x64, 0x76, 0x49, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, + 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, + 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x2f, 0x0a, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, + 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x12, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x22, 0xf7, 0x03, 0x0a, 0x18, 0x44, 0x65, 0x74, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x73, 0x6f, + 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, + 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, - 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3e, 0x0a, 0x08, - 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, - 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, - 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x6e, - 0x75, 0x6d, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, - 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, - 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, - 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0xed, 0x01, 0x0a, - 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x0c, - 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x45, 0x6e, - 0x75, 0x6d, 0x52, 0x0b, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, - 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, - 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, - 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x67, - 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x22, 0x98, 0x01, 0x0a, - 0x0e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, - 0x3e, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, - 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, - 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0x95, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x63, 0x65, - 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x22, 0x81, 0x01, 0x0a, 0x04, + 0x74, 0x79, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, + 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x16, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, + 0x12, 0x42, 0x0a, 0x0e, 0x63, 0x61, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x63, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x76, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x76, 0x64, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, + 0x79, 0x22, 0xff, 0x09, 0x0a, 0x0d, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, + 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, + 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x78, 0x65, 0x64, 0x5f, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, + 0x69, 0x78, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x73, + 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x42, 0x0a, 0x0e, 0x70, 0x6b, 0x67, 0x5f, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, + 0x6b, 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0d, 0x70, 0x6b, + 0x67, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x05, 0x6c, + 0x61, 0x79, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, + 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, + 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, + 0x74, 0x79, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x39, 0x0a, 0x04, 0x63, 0x76, 0x73, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, + 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x43, 0x76, 0x73, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x63, 0x76, 0x73, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x77, + 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x63, 0x77, 0x65, + 0x49, 0x64, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, + 0x79, 0x55, 0x72, 0x6c, 0x12, 0x41, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, + 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, + 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x10, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x10, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x44, 0x61, 0x74, + 0x65, 0x12, 0x48, 0x0a, 0x14, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x61, 0x64, 0x76, 0x69, + 0x73, 0x6f, 0x72, 0x79, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x41, + 0x64, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x12, 0x40, 0x0a, 0x10, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x76, 0x75, 0x6c, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x56, 0x75, 0x6c, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, + 0x0a, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x09, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x0b, + 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0a, 0x64, 0x61, 0x74, + 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x76, 0x65, 0x6e, 0x64, 0x6f, + 0x72, 0x5f, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2f, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x56, 0x65, + 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0e, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, + 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x16, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x15, 0x0a, 0x06, + 0x70, 0x6b, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x6b, + 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x18, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x4b, 0x0a, 0x09, 0x43, + 0x76, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x56, 0x53, 0x53, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x59, 0x0a, 0x13, 0x56, 0x65, 0x6e, 0x64, + 0x6f, 0x72, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x42, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x57, 0x0a, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, + 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x69, 0x66, 0x66, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x66, 0x66, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, + 0x22, 0xc3, 0x01, 0x0a, 0x0d, 0x43, 0x61, 0x75, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, + 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, + 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x26, + 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x64, 0x65, + 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x76, 0x0a, 0x04, 0x43, 0x56, 0x53, 0x53, 0x12, 0x1b, + 0x0a, 0x09, 0x76, 0x32, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x76, 0x32, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x76, + 0x33, 0x5f, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x76, 0x33, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x32, 0x5f, 0x73, + 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x32, 0x53, 0x63, + 0x6f, 0x72, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x33, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x76, 0x33, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x98, + 0x01, 0x0a, 0x0e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, + 0x74, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x2a, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xf3, 0x01, 0x0a, 0x04, 0x4c, 0x69, + 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x43, 0x61, 0x75, 0x73, 0x65, 0x12, + 0x1e, 0x0a, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x1c, 0x0a, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x20, 0x0a, + 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x68, 0x69, 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x12, + 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x43, 0x61, 0x75, 0x73, 0x65, + 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x43, 0x61, 0x75, 0x73, 0x65, 0x22, + 0x30, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, + 0x73, 0x22, 0x9f, 0x02, 0x0a, 0x0d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, + 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, + 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, + 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x65, 0x6e, 0x64, + 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x74, + 0x63, 0x68, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4a, 0x04, 0x08, + 0x09, 0x10, 0x0a, 0x22, 0x5d, 0x0a, 0x06, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1a, 0x0a, + 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x12, 0x37, 0x0a, 0x08, 0x66, 0x69, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x73, 0x22, 0x85, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, + 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, + 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3e, 0x0a, 0x08, 0x63, 0x61, + 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x6e, 0x75, 0x6d, + 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, + 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, + 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, + 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, + 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0xed, 0x01, 0x0a, 0x0b, 0x4c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x6c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x45, 0x6e, 0x75, 0x6d, + 0x52, 0x0b, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, + 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, + 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x67, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x67, 0x73, 0x12, + 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, + 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x4c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x3e, 0x0a, + 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2e, 0x45, + 0x6e, 0x75, 0x6d, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0x95, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, + 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x22, 0x81, 0x01, 0x0a, 0x04, 0x45, 0x6e, + 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, 0x49, 0x44, 0x44, 0x45, 0x4e, + 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x53, 0x54, 0x52, 0x49, 0x43, 0x54, 0x45, 0x44, + 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x43, 0x49, 0x50, 0x52, 0x4f, 0x43, 0x41, 0x4c, + 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x54, 0x49, 0x43, 0x45, 0x10, 0x04, 0x12, 0x0e, + 0x0a, 0x0a, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x56, 0x45, 0x10, 0x05, 0x12, 0x10, + 0x0a, 0x0c, 0x55, 0x4e, 0x45, 0x4e, 0x43, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x45, 0x44, 0x10, 0x06, + 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x07, 0x22, 0x4e, 0x0a, + 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3f, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, 0x49, 0x44, 0x44, - 0x45, 0x4e, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x53, 0x54, 0x52, 0x49, 0x43, 0x54, - 0x45, 0x44, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x43, 0x49, 0x50, 0x52, 0x4f, 0x43, - 0x41, 0x4c, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x54, 0x49, 0x43, 0x45, 0x10, 0x04, - 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x56, 0x45, 0x10, 0x05, - 0x12, 0x10, 0x0a, 0x0c, 0x55, 0x4e, 0x45, 0x4e, 0x43, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x45, 0x44, - 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x07, 0x22, - 0x4e, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3f, - 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x50, 0x4b, 0x47, 0x10, - 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x10, 0x0a, - 0x0c, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a, - 0x44, 0x0a, 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x4f, 0x57, 0x10, - 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, 0x02, 0x12, 0x08, 0x0a, - 0x04, 0x48, 0x49, 0x47, 0x48, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x49, 0x54, 0x49, - 0x43, 0x41, 0x4c, 0x10, 0x04, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, - 0x2f, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x50, 0x4b, 0x47, 0x10, 0x01, 0x12, + 0x0a, 0x0a, 0x06, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4c, + 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a, 0x44, 0x0a, + 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, + 0x0a, 0x0a, 0x06, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x48, + 0x49, 0x47, 0x48, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x49, 0x54, 0x49, 0x43, 0x41, + 0x4c, 0x10, 0x04, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3b, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2835,7 +2835,7 @@ var file_rpc_common_service_proto_goTypes = []interface{}{ } var file_rpc_common_service_proto_depIdxs = []int32{ 7, // 0: trivy.common.PackageInfo.packages:type_name -> trivy.common.Package - 7, // 1: trivy.common.Application.libraries:type_name -> trivy.common.Package + 7, // 1: trivy.common.Application.packages:type_name -> trivy.common.Package 8, // 2: trivy.common.Package.identifier:type_name -> trivy.common.PkgIdentifier 9, // 3: trivy.common.Package.locations:type_name -> trivy.common.Location 16, // 4: trivy.common.Package.layer:type_name -> trivy.common.Layer diff --git a/rpc/common/service.proto b/rpc/common/service.proto index 8882bbf20e39..91ed89dd534f 100644 --- a/rpc/common/service.proto +++ b/rpc/common/service.proto @@ -27,7 +27,7 @@ message PackageInfo { message Application { string type = 1; string file_path = 2; - repeated Package libraries = 3; + repeated Package packages = 3; } message Package { From b7a0a131a03ed49c08d3b0d481bc9284934fd6e1 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 8 May 2024 01:58:33 +0600 Subject: [PATCH 052/352] feat(misconf): add Terraform 'removed' block to schema (#6640) --- pkg/iac/terraform/schema.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/iac/terraform/schema.go b/pkg/iac/terraform/schema.go index b64b2bad0cea..f408d860d0e3 100644 --- a/pkg/iac/terraform/schema.go +++ b/pkg/iac/terraform/schema.go @@ -48,5 +48,8 @@ var Schema = &hcl.BodySchema{ { Type: "import", }, + { + Type: "removed", + }, }, } From 4eae37c52b035b3576361c12f70d3d9517d0a73c Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 8 May 2024 05:23:55 +0600 Subject: [PATCH 053/352] feat(misconf): support symlinks inside of Helm archives (#6621) --- pkg/iac/scanners/helm/parser/parser.go | 6 +- pkg/iac/scanners/helm/parser/parser_tar.go | 122 +++++++++++++----- pkg/iac/scanners/helm/parser/parser_test.go | 26 ++++ .../archive-with-symlinks/chart.tar.gz | Bin 0 -> 312 bytes pkg/iac/scanners/helm/test/parser_test.go | 16 +-- 5 files changed, 129 insertions(+), 41 deletions(-) create mode 100644 pkg/iac/scanners/helm/parser/testdata/archive-with-symlinks/chart.tar.gz diff --git a/pkg/iac/scanners/helm/parser/parser.go b/pkg/iac/scanners/helm/parser/parser.go index c8bc8a73bedd..ddcfac09682f 100644 --- a/pkg/iac/scanners/helm/parser/parser.go +++ b/pkg/iac/scanners/helm/parser/parser.go @@ -22,7 +22,7 @@ import ( "helm.sh/helm/v3/pkg/releaseutil" "github.com/aquasecurity/trivy/pkg/iac/debug" - detection2 "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) @@ -133,7 +133,7 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) error { return nil } - if detection2.IsArchive(path) { + if detection.IsArchive(path) { tarFS, err := p.addTarToFS(path) if errors.Is(err, errSkipFS) { // an unpacked Chart already exists @@ -320,5 +320,5 @@ func (p *Parser) required(path string, workingFS fs.FS) bool { return false } - return detection2.IsType(path, bytes.NewReader(content), detection2.FileTypeHelm) + return detection.IsType(path, bytes.NewReader(content), detection.FileTypeHelm) } diff --git a/pkg/iac/scanners/helm/parser/parser_tar.go b/pkg/iac/scanners/helm/parser/parser_tar.go index 5455ab780683..f8d692eaa977 100644 --- a/pkg/iac/scanners/helm/parser/parser_tar.go +++ b/pkg/iac/scanners/helm/parser/parser_tar.go @@ -2,13 +2,13 @@ package parser import ( "archive/tar" - "bytes" "compress/gzip" "errors" "fmt" "io" "io/fs" "os" + "path" "path/filepath" "github.com/liamg/memoryfs" @@ -18,10 +18,10 @@ import ( var errSkipFS = errors.New("skip parse FS") -func (p *Parser) addTarToFS(path string) (fs.FS, error) { +func (p *Parser) addTarToFS(archivePath string) (fs.FS, error) { tarFS := memoryfs.CloneFS(p.workingFS) - file, err := tarFS.Open(path) + file, err := tarFS.Open(archivePath) if err != nil { return nil, fmt.Errorf("failed to open tar: %w", err) } @@ -29,7 +29,7 @@ func (p *Parser) addTarToFS(path string) (fs.FS, error) { var tr *tar.Reader - if detection.IsZip(path) { + if detection.IsZip(archivePath) { zipped, err := gzip.NewReader(file) if err != nil { return nil, fmt.Errorf("failed to create gzip reader: %w", err) @@ -41,6 +41,7 @@ func (p *Parser) addTarToFS(path string) (fs.FS, error) { } checkExistedChart := true + symlinks := make(map[string]string) for { header, err := tr.Next() @@ -51,61 +52,124 @@ func (p *Parser) addTarToFS(path string) (fs.FS, error) { return nil, fmt.Errorf("failed to get next entry: %w", err) } + name := filepath.ToSlash(header.Name) + if checkExistedChart { // Do not add archive files to FS if the chart already exists // This can happen when the source chart is located next to an archived chart (with the `helm package` command) // The first level folder in the archive is equal to the Chart name - if _, err := tarFS.Stat(filepath.Dir(path) + "/" + filepath.Dir(header.Name)); err == nil { + if _, err := tarFS.Stat(path.Dir(archivePath) + "/" + path.Dir(name)); err == nil { return nil, errSkipFS } checkExistedChart = false } // get the individual path and extract to the current directory - entryPath := header.Name + targetPath := path.Join(path.Dir(archivePath), path.Clean(name)) + + link := filepath.ToSlash(header.Linkname) switch header.Typeflag { case tar.TypeDir: - if err := tarFS.MkdirAll(entryPath, os.FileMode(header.Mode)); err != nil && !errors.Is(err, fs.ErrExist) { + if err := tarFS.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil && !errors.Is(err, fs.ErrExist) { return nil, err } case tar.TypeReg: - writePath := filepath.Dir(path) + "/" + entryPath - p.debug.Log("Unpacking tar entry %s", writePath) - - _ = tarFS.MkdirAll(filepath.Dir(writePath), fs.ModePerm) - - buf, err := copyChunked(tr, 1024) - if err != nil { + p.debug.Log("Unpacking tar entry %s", targetPath) + if err := copyFile(tarFS, tr, targetPath); err != nil { return nil, err } - - p.debug.Log("writing file contents to %s", writePath) - if err := tarFS.WriteFile(writePath, buf.Bytes(), fs.ModePerm); err != nil { - return nil, fmt.Errorf("write file error: %w", err) + case tar.TypeSymlink: + if path.IsAbs(link) { + p.debug.Log("Symlink %s is absolute, skipping", link) + continue } + + symlinks[targetPath] = path.Join(path.Dir(targetPath), link) // nolint:gosec // virtual file system is used default: return nil, fmt.Errorf("header type %q is not supported", header.Typeflag) } } - if err := tarFS.Remove(path); err != nil { - return nil, fmt.Errorf("failed to remove tar from FS: %w", err) + for target, link := range symlinks { + if err := copySymlink(tarFS, link, target); err != nil { + return nil, fmt.Errorf("copy symlink error: %w", err) + } + } + + if err := tarFS.Remove(archivePath); err != nil { + return nil, fmt.Errorf("remove tar from FS error: %w", err) } return tarFS, nil } -func copyChunked(src io.Reader, chunkSize int64) (*bytes.Buffer, error) { - buf := new(bytes.Buffer) - for { - if _, err := io.CopyN(buf, src, chunkSize); err != nil { - if errors.Is(err, io.EOF) { - break - } - return nil, fmt.Errorf("failed to copy: %w", err) +func copySymlink(fsys *memoryfs.FS, src, dst string) error { + fi, err := fsys.Stat(src) + if err != nil { + return nil + } + if fi.IsDir() { + if err := copyDir(fsys, src, dst); err != nil { + return fmt.Errorf("copy dir error: %w", err) + } + return nil + } + + if err := copyFileLazy(fsys, src, dst); err != nil { + return fmt.Errorf("copy file error: %w", err) + } + + return nil +} + +func copyFile(fsys *memoryfs.FS, src io.Reader, dst string) error { + if err := fsys.MkdirAll(path.Dir(dst), fs.ModePerm); err != nil && !errors.Is(err, fs.ErrExist) { + return fmt.Errorf("mkdir error: %w", err) + } + + b, err := io.ReadAll(src) + if err != nil { + return fmt.Errorf("read error: %w", err) + } + + if err := fsys.WriteFile(dst, b, fs.ModePerm); err != nil { + return fmt.Errorf("write file error: %w", err) + } + + return nil +} + +func copyDir(fsys *memoryfs.FS, src, dst string) error { + walkFn := func(filePath string, entry fs.DirEntry, err error) error { + if err != nil { + return err } + + if entry.IsDir() { + return nil + } + + dst := path.Join(dst, filePath[len(src):]) + + if err := copyFileLazy(fsys, filePath, dst); err != nil { + return fmt.Errorf("copy file error: %w", err) + } + return nil } - return buf, nil + return fs.WalkDir(fsys, src, walkFn) +} + +func copyFileLazy(fsys *memoryfs.FS, src, dst string) error { + if err := fsys.MkdirAll(path.Dir(dst), fs.ModePerm); err != nil && !errors.Is(err, fs.ErrExist) { + return fmt.Errorf("mkdir error: %w", err) + } + return fsys.WriteLazyFile(dst, func() (io.Reader, error) { + f, err := fsys.Open(src) + if err != nil { + return nil, err + } + return f, nil + }, fs.ModePerm) } diff --git a/pkg/iac/scanners/helm/parser/parser_test.go b/pkg/iac/scanners/helm/parser/parser_test.go index 030b0efbb86f..9c8b05ce7696 100644 --- a/pkg/iac/scanners/helm/parser/parser_test.go +++ b/pkg/iac/scanners/helm/parser/parser_test.go @@ -22,4 +22,30 @@ func TestParseFS(t *testing.T) { } assert.Equal(t, expectedFiles, p.filepaths) }) + + t.Run("archive with symlinks", func(t *testing.T) { + // mkdir -p chart && cd $_ + // touch Chart.yaml + // mkdir -p dir && cp -p Chart.yaml dir/Chart.yaml + // mkdir -p sym-to-file && ln -s ../Chart.yaml sym-to-file/Chart.yaml + // ln -s dir sym-to-dir + // mkdir rec-sym && touch rec-sym/Chart.yaml + // ln -s . ./rec-sym/a + // cd .. && tar -czvf chart.tar.gz chart && rm -rf chart + p, err := New(".") + require.NoError(t, err) + + fsys := os.DirFS(filepath.Join("testdata", "archive-with-symlinks")) + require.NoError(t, p.ParseFS(context.TODO(), fsys, "chart.tar.gz")) + + expectedFiles := []string{ + "chart/Chart.yaml", + "chart/dir/Chart.yaml", + "chart/rec-sym/Chart.yaml", + "chart/rec-sym/a/Chart.yaml", + "chart/sym-to-dir/Chart.yaml", + "chart/sym-to-file/Chart.yaml", + } + assert.Equal(t, expectedFiles, p.filepaths) + }) } diff --git a/pkg/iac/scanners/helm/parser/testdata/archive-with-symlinks/chart.tar.gz b/pkg/iac/scanners/helm/parser/testdata/archive-with-symlinks/chart.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..a3183710c17f5dd1d04935cfd2731450a83df2b5 GIT binary patch literal 312 zcmV-80muFyiwFSaw>f421MQbxYQr!LfO8aIAenz;`2ahKNgGyZC^+duZ$Bjt9a+nm zVKR*B3)n9vR{U9t-G9V1zcs9At%LV!?J@V-Lhd(|0W|2G3)(4doCnt^&l^_eI?XXr zDD!2aXN>9kd>joMh9BOueaF53C-kfTbnM&dHZtvlBL5D_zc)^c{~Bcf?@Qa=;&|qm zgVqB9e&-)?^$&=R(j38jNBooj!znTT14{BAgCYOmte5<+LH19zznw2FZ3B)u%KQiN zk0wz5Tc&t+i2pDD!|8lvr~_sGVX6PY1j+vzJo^9pvp}(bYeJySA+fik7o~*_|Nx$YQ6Zc!QOvlnWLosZ})$< Date: Tue, 7 May 2024 21:41:52 -0600 Subject: [PATCH 054/352] chore(misconf): Clean up iac logger (#6642) --- pkg/iac/debug/cgo_disabled.go | 5 ---- pkg/iac/debug/cgo_enabled.go | 5 ---- pkg/iac/debug/debug.go | 53 ----------------------------------- 3 files changed, 63 deletions(-) delete mode 100644 pkg/iac/debug/cgo_disabled.go delete mode 100644 pkg/iac/debug/cgo_enabled.go diff --git a/pkg/iac/debug/cgo_disabled.go b/pkg/iac/debug/cgo_disabled.go deleted file mode 100644 index e994a4dc79fa..000000000000 --- a/pkg/iac/debug/cgo_disabled.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build !cgo - -package debug - -const cgoEnabled = false diff --git a/pkg/iac/debug/cgo_enabled.go b/pkg/iac/debug/cgo_enabled.go deleted file mode 100644 index afa840a615b4..000000000000 --- a/pkg/iac/debug/cgo_enabled.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build cgo - -package debug - -const cgoEnabled = true diff --git a/pkg/iac/debug/debug.go b/pkg/iac/debug/debug.go index bd96be1656b5..2b43e7dd9bde 100644 --- a/pkg/iac/debug/debug.go +++ b/pkg/iac/debug/debug.go @@ -3,9 +3,6 @@ package debug import ( "fmt" "io" - "os" - "path/filepath" - "runtime" "strings" "time" ) @@ -39,53 +36,3 @@ func (l *Logger) Log(format string, args ...interface{}) { line := fmt.Sprintf("%s %-32s %s\n", time.Now().Format(timeFormat), l.prefix, message) _, _ = l.writer.Write([]byte(line)) } - -func LogSystemInfo(w io.Writer, appVersion string) { - if w == nil { - return - } - sys := New(w, "system", "info") - var appName string - if path, err := os.Executable(); err != nil { - if len(os.Args) > 0 { - appName = os.Args[0] - } - } else { - appName = filepath.Base(path) - } - - wd, _ := os.Getwd() - hostname, _ := os.Hostname() - - var inDocker bool - if _, err := os.Stat("/.dockerenv"); err == nil || !os.IsNotExist(err) { - inDocker = true - } - - var kernelInfo string - if data, err := os.ReadFile("/proc/version"); err == nil { - kernelInfo = strings.TrimSpace(string(data)) - } - - sys.Log("APP %s", appName) - sys.Log("VERSION %s", appVersion) - sys.Log("OS %s", runtime.GOOS) - sys.Log("ARCH %s", runtime.GOARCH) - sys.Log("KERNEL %s", kernelInfo) - sys.Log("TERM %s", os.Getenv("TERM")) - sys.Log("SHELL %s", os.Getenv("SHELL")) - sys.Log("GOVERSION %s", runtime.Version()) - sys.Log("GOROOT %s", runtime.GOROOT()) - sys.Log("CGO %t", cgoEnabled) - sys.Log("CPUCOUNT %d", runtime.NumCPU()) - sys.Log("MAXPROCS %d", runtime.GOMAXPROCS(0)) - sys.Log("WORKDIR %s", wd) - sys.Log("UID %d", os.Getuid()) - sys.Log("EUID %d", os.Geteuid()) - sys.Log("DOCKER %t", inDocker) - sys.Log("CI %t", os.Getenv("CI") != "") - sys.Log("HOSTNAME %s", hostname) - sys.Log("TEMP %s", os.TempDir()) - sys.Log("PATHSEP %c", filepath.Separator) - sys.Log("CMD %s", strings.Join(os.Args, " ")) -} From 04a6073eac12ae752e3e0c9d953b1cc299bba0da Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 8 May 2024 11:35:18 +0400 Subject: [PATCH 055/352] refactor: re-define module structs for serialization (#6655) Signed-off-by: knqyf263 --- examples/module/spring4shell/spring4shell.go | 3 +- pkg/module/module.go | 12 +-- pkg/module/serialize/types.go | 98 +++++++++++++++++++- 3 files changed, 103 insertions(+), 10 deletions(-) diff --git a/examples/module/spring4shell/spring4shell.go b/examples/module/spring4shell/spring4shell.go index 8d7d18ab65dd..6c527cc946b2 100644 --- a/examples/module/spring4shell/spring4shell.go +++ b/examples/module/spring4shell/spring4shell.go @@ -15,7 +15,6 @@ import ( "github.com/aquasecurity/trivy/pkg/module/api" "github.com/aquasecurity/trivy/pkg/module/serialize" "github.com/aquasecurity/trivy/pkg/module/wasm" - "github.com/aquasecurity/trivy/pkg/types" ) const ( @@ -226,7 +225,7 @@ func (Spring4Shell) PostScan(results serialize.Results) (serialize.Results, erro var javaMajorVersion int var tomcatVersion string for _, result := range results { - if result.Class != types.ClassCustom { + if result.Class != "custom" { continue } diff --git a/pkg/module/module.go b/pkg/module/module.go index f573c20597c1..a37790941f79 100644 --- a/pkg/module/module.go +++ b/pkg/module/module.go @@ -481,15 +481,15 @@ func (m *wasmModule) Analyze(ctx context.Context, input analyzer.AnalysisInput) // e.g. Remove a vulnerability, change severity, etc. func (m *wasmModule) PostScan(ctx context.Context, results types.Results) (types.Results, error) { // Find custom resources - var custom serialize.Result + var custom types.Result for _, result := range results { if result.Class == types.ClassCustom { - custom = serialize.Result(result) + custom = result break } } - arg := serialize.Results{custom} + arg := types.Results{custom} switch m.postScanSpec.Action { case tapi.ActionUpdate, tapi.ActionDelete: // Pass the relevant results to the module @@ -529,8 +529,8 @@ func (m *wasmModule) PostScan(ctx context.Context, results types.Results) (types return results, nil } -func findIDs(ids []string, results types.Results) serialize.Results { - var filtered serialize.Results +func findIDs(ids []string, results types.Results) types.Results { + var filtered types.Results for _, result := range results { if result.Class == types.ClassCustom { continue @@ -542,7 +542,7 @@ func findIDs(ids []string, results types.Results) serialize.Results { return slices.Contains(ids, m.ID) }) if len(vulns) > 0 || len(misconfs) > 0 { - filtered = append(filtered, serialize.Result{ + filtered = append(filtered, types.Result{ Target: result.Target, Class: result.Class, Type: result.Type, diff --git a/pkg/module/serialize/types.go b/pkg/module/serialize/types.go index df72a953eee3..beddb8175f44 100644 --- a/pkg/module/serialize/types.go +++ b/pkg/module/serialize/types.go @@ -1,7 +1,7 @@ package serialize import ( - "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/types" ) type StringSlice []string @@ -39,4 +39,98 @@ type PostScanSpec struct { type Results []Result -type Result types.Result +// Result re-defines the Result struct from 'pkg/types/' so TinyGo can compile the code. +// See https://github.com/aquasecurity/trivy/issues/6654 for more details. +type Result struct { + Target string `json:"Target"` + Class string `json:"Class,omitempty"` + Type string `json:"Type,omitempty"` + Vulnerabilities []DetectedVulnerability `json:"Vulnerabilities,omitempty"` + CustomResources []CustomResource `json:"CustomResources,omitempty"` +} + +type DetectedVulnerability struct { + VulnerabilityID string `json:",omitempty"` + VendorIDs []string `json:",omitempty"` + PkgID string `json:",omitempty"` + PkgName string `json:",omitempty"` + PkgPath string `json:",omitempty"` + InstalledVersion string `json:",omitempty"` + FixedVersion string `json:",omitempty"` + Status types.Status `json:",omitempty"` + Layer Layer `json:",omitempty"` + SeveritySource types.SourceID `json:",omitempty"` + PrimaryURL string `json:",omitempty"` + + // DataSource holds where the advisory comes from + DataSource *types.DataSource `json:",omitempty"` + + // Custom is for extensibility and not supposed to be used in OSS + Custom interface{} `json:",omitempty"` + + // Embed vulnerability details + types.Vulnerability +} + +type DetectedMisconfiguration struct { + Type string `json:",omitempty"` + ID string `json:",omitempty"` + AVDID string `json:",omitempty"` + Title string `json:",omitempty"` + Description string `json:",omitempty"` + Message string `json:",omitempty"` + Namespace string `json:",omitempty"` + Query string `json:",omitempty"` + Resolution string `json:",omitempty"` + Severity string `json:",omitempty"` + PrimaryURL string `json:",omitempty"` + References []string `json:",omitempty"` + Status string `json:",omitempty"` + Layer Layer `json:",omitempty"` + CauseMetadata CauseMetadata `json:",omitempty"` + + // For debugging + Traces []string `json:",omitempty"` +} + +type CauseMetadata struct { + Resource string `json:",omitempty"` + Provider string `json:",omitempty"` + Service string `json:",omitempty"` + StartLine int `json:",omitempty"` + EndLine int `json:",omitempty"` + Code Code `json:",omitempty"` + Occurrences []Occurrence `json:",omitempty"` +} + +type Occurrence struct { + Resource string `json:",omitempty"` + Filename string `json:",omitempty"` + Location Location +} + +type Location struct { + StartLine int `json:",omitempty"` + EndLine int `json:",omitempty"` +} + +type Code struct { + Lines []Line +} + +type Line struct { + Number int `json:"Number"` + Content string `json:"Content"` + IsCause bool `json:"IsCause"` + Annotation string `json:"Annotation"` + Truncated bool `json:"Truncated"` + Highlighted string `json:"Highlighted,omitempty"` + FirstCause bool `json:"FirstCause"` + LastCause bool `json:"LastCause"` +} + +type Layer struct { + Digest string `json:",omitempty"` + DiffID string `json:",omitempty"` + CreatedBy string `json:",omitempty"` +} From 357c358fb14bbc027bca9b6a4167322f1703036f Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 9 May 2024 09:06:34 +0600 Subject: [PATCH 056/352] refactor(misconf): remove extrafs (#6656) --- pkg/extrafs/extrafs.go | 54 --------------------- pkg/iac/scanners/terraform/parser/parser.go | 24 +-------- pkg/iac/scanners/terraform/scanner.go | 19 +------- 3 files changed, 2 insertions(+), 95 deletions(-) delete mode 100644 pkg/extrafs/extrafs.go diff --git a/pkg/extrafs/extrafs.go b/pkg/extrafs/extrafs.go deleted file mode 100644 index e3956c193bbe..000000000000 --- a/pkg/extrafs/extrafs.go +++ /dev/null @@ -1,54 +0,0 @@ -package extrafs - -import ( - "io/fs" - "os" - "path/filepath" -) - -/* - Go does not currently support symlinks in io/fs. - We work around this by wrapping the fs.FS returned by os.DirFS with our own type which bolts on the ReadLinkFS -*/ - -type OSFS interface { - fs.FS - fs.StatFS -} - -type ReadLinkFS interface { - ResolveSymlink(name, dir string) (string, error) -} - -type FS interface { - OSFS - ReadLinkFS -} - -type filesystem struct { - root string - underlying OSFS -} - -func OSDir(path string) FS { - return &filesystem{ - root: path, - underlying: os.DirFS(path).(OSFS), - } -} - -func (f *filesystem) Open(name string) (fs.File, error) { - return f.underlying.Open(name) -} - -func (f *filesystem) Stat(name string) (fs.FileInfo, error) { - return f.underlying.Stat(name) -} - -func (f *filesystem) ResolveSymlink(name, dir string) (string, error) { - link, err := os.Readlink(filepath.Join(f.root, dir, name)) - if err == nil { - return filepath.Join(dir, link), nil - } - return name, nil -} diff --git a/pkg/iac/scanners/terraform/parser/parser.go b/pkg/iac/scanners/terraform/parser/parser.go index b5b50dc913d7..b7de6dd4ba08 100644 --- a/pkg/iac/scanners/terraform/parser/parser.go +++ b/pkg/iac/scanners/terraform/parser/parser.go @@ -15,7 +15,6 @@ import ( "github.com/hashicorp/hcl/v2/hclparse" "github.com/zclconf/go-cty/cty" - "github.com/aquasecurity/trivy/pkg/extrafs" "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/ignore" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" @@ -185,28 +184,7 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) error { var paths []string for _, info := range fileInfos { realPath := path.Join(dir, info.Name()) - if info.Type()&os.ModeSymlink != 0 { - extra, ok := p.moduleFS.(extrafs.FS) - if !ok { - // we can't handle symlinks in this fs type for now - p.debug.Log("Cannot resolve symlink '%s' in '%s' for this fs type", info.Name(), dir) - continue - } - realPath, err = extra.ResolveSymlink(info.Name(), dir) - if err != nil { - p.debug.Log("Failed to resolve symlink '%s' in '%s': %s", info.Name(), dir, err) - continue - } - info, err := extra.Stat(realPath) - if err != nil { - p.debug.Log("Failed to stat resolved symlink '%s': %s", realPath, err) - continue - } - if info.IsDir() { - continue - } - p.debug.Log("Resolved symlink '%s' in '%s' to '%s'", info.Name(), dir, realPath) - } else if info.IsDir() { + if info.IsDir() { continue } paths = append(paths, realPath) diff --git a/pkg/iac/scanners/terraform/scanner.go b/pkg/iac/scanners/terraform/scanner.go index f5a3554d002d..1f051a166595 100644 --- a/pkg/iac/scanners/terraform/scanner.go +++ b/pkg/iac/scanners/terraform/scanner.go @@ -11,7 +11,6 @@ import ( "strings" "sync" - "github.com/aquasecurity/trivy/pkg/extrafs" "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" @@ -281,24 +280,8 @@ func (s *Scanner) findModules(target fs.FS, scanDir string, dirs ...string) []st continue } for _, file := range files { - realPath := path.Join(dir, file.Name()) - if symFS, ok := target.(extrafs.ReadLinkFS); ok { - realPath, err = symFS.ResolveSymlink(realPath, scanDir) - if err != nil { - s.debug.Log("failed to resolve symlink '%s': %s", file.Name(), err) - continue - } - } if file.IsDir() { - others = append(others, realPath) - } else if statFS, ok := target.(fs.StatFS); ok { - info, err := statFS.Stat(filepath.ToSlash(realPath)) - if err != nil { - continue - } - if info.IsDir() { - others = append(others, realPath) - } + others = append(others, path.Join(dir, file.Name())) } } } From 6a72dd47ae40c52bb27fc20c7f97e3c3c5168437 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 9 May 2024 20:18:37 +0400 Subject: [PATCH 057/352] refactor: move artifact types under artifact package to avoid import cycles (#6652) Signed-off-by: knqyf263 --- integration/repo_test.go | 9 +- integration/sbom_test.go | 10 +- integration/testdata/conda-spdx.json.golden | 16 +- pkg/cloud/report/report.go | 4 +- pkg/fanal/artifact/artifact.go | 40 ++- pkg/fanal/artifact/image/image.go | 22 +- pkg/fanal/artifact/image/image_test.go | 20 +- pkg/fanal/artifact/image/remote_sbom.go | 49 +-- pkg/fanal/artifact/image/remote_sbom_test.go | 12 +- pkg/fanal/artifact/local/fs.go | 20 +- pkg/fanal/artifact/local/fs_test.go | 144 ++++----- pkg/fanal/artifact/mock_artifact.go | 16 +- pkg/fanal/artifact/repo/git.go | 9 +- pkg/fanal/artifact/repo/git_test.go | 12 +- pkg/fanal/artifact/sbom/sbom.go | 22 +- pkg/fanal/artifact/sbom/sbom_test.go | 10 +- pkg/fanal/artifact/vm/ami.go | 6 +- pkg/fanal/artifact/vm/ebs.go | 22 +- pkg/fanal/artifact/vm/file.go | 15 +- pkg/fanal/artifact/vm/vm_test.go | 10 +- pkg/fanal/test/integration/containerd_test.go | 10 +- pkg/fanal/types/artifact.go | 295 ------------------ pkg/fanal/types/package.go | 260 +++++++++++++++ pkg/k8s/scanner/scanner.go | 10 +- pkg/k8s/scanner/scanner_test.go | 17 +- pkg/report/github/github.go | 3 +- pkg/report/predicate/vuln_test.go | 4 +- pkg/report/sarif.go | 3 +- pkg/report/sarif_test.go | 3 +- pkg/report/writer.go | 4 +- pkg/sbom/core/bom.go | 28 +- pkg/sbom/cyclonedx/marshal.go | 4 +- pkg/sbom/cyclonedx/marshal_test.go | 17 +- pkg/sbom/cyclonedx/unmarshal.go | 5 +- pkg/sbom/io/decode.go | 10 +- pkg/sbom/io/encode.go | 15 +- pkg/sbom/io/encode_test.go | 29 +- pkg/sbom/spdx/marshal.go | 10 +- pkg/sbom/spdx/marshal_test.go | 15 +- pkg/sbom/spdx/unmarshal.go | 2 +- pkg/scanner/scan.go | 2 +- pkg/scanner/scan_test.go | 10 +- pkg/types/report.go | 13 +- pkg/vex/openvex.go | 4 +- pkg/vex/vex.go | 4 +- pkg/vex/vex_test.go | 9 +- 46 files changed, 629 insertions(+), 625 deletions(-) create mode 100644 pkg/fanal/types/package.go diff --git a/integration/repo_test.go b/integration/repo_test.go index 3aa2baff521e..72edba8a562f 100644 --- a/integration/repo_test.go +++ b/integration/repo_test.go @@ -4,12 +4,13 @@ package integration import ( "fmt" - "github.com/stretchr/testify/assert" "os" "strings" "testing" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/types" ) @@ -379,7 +380,7 @@ func TestRepository(t *testing.T) { }, golden: "testdata/gomod-skip.json.golden", override: func(_ *testing.T, want, _ *types.Report) { - want.ArtifactType = ftypes.ArtifactFilesystem + want.ArtifactType = artifact.TypeFilesystem }, }, { @@ -393,7 +394,7 @@ func TestRepository(t *testing.T) { }, golden: "testdata/dockerfile-custom-policies.json.golden", override: func(_ *testing.T, want, got *types.Report) { - want.ArtifactType = ftypes.ArtifactFilesystem + want.ArtifactType = artifact.TypeFilesystem }, }, } diff --git a/integration/sbom_test.go b/integration/sbom_test.go index 428efe5cfec3..e3ee5b89cc3d 100644 --- a/integration/sbom_test.go +++ b/integration/sbom_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/types" ) @@ -37,7 +37,7 @@ func TestSBOM(t *testing.T) { golden: "testdata/centos-7.json.golden", override: func(t *testing.T, want, got *types.Report) { want.ArtifactName = "testdata/fixtures/sbom/centos-7-cyclonedx.json" - want.ArtifactType = ftypes.ArtifactCycloneDX + want.ArtifactType = artifact.TypeCycloneDX require.Len(t, got.Results, 1) want.Results[0].Target = "testdata/fixtures/sbom/centos-7-cyclonedx.json (centos 7.6.1810)" @@ -76,7 +76,7 @@ func TestSBOM(t *testing.T) { golden: "testdata/centos-7.json.golden", override: func(t *testing.T, want, got *types.Report) { want.ArtifactName = "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl" - want.ArtifactType = ftypes.ArtifactCycloneDX + want.ArtifactType = artifact.TypeCycloneDX require.Len(t, got.Results, 1) want.Results[0].Target = "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl (centos 7.6.1810)" @@ -97,7 +97,7 @@ func TestSBOM(t *testing.T) { golden: "testdata/centos-7.json.golden", override: func(t *testing.T, want, got *types.Report) { want.ArtifactName = "testdata/fixtures/sbom/centos-7-spdx.txt" - want.ArtifactType = ftypes.ArtifactSPDX + want.ArtifactType = artifact.TypeSPDX require.Len(t, got.Results, 1) want.Results[0].Target = "testdata/fixtures/sbom/centos-7-spdx.txt (centos 7.6.1810)" @@ -113,7 +113,7 @@ func TestSBOM(t *testing.T) { golden: "testdata/centos-7.json.golden", override: func(t *testing.T, want, got *types.Report) { want.ArtifactName = "testdata/fixtures/sbom/centos-7-spdx.json" - want.ArtifactType = ftypes.ArtifactSPDX + want.ArtifactType = artifact.TypeSPDX require.Len(t, got.Results, 1) want.Results[0].Target = "testdata/fixtures/sbom/centos-7-spdx.json (centos 7.6.1810)" diff --git a/integration/testdata/conda-spdx.json.golden b/integration/testdata/conda-spdx.json.golden index db81eb8abd13..f8a8778538c0 100644 --- a/integration/testdata/conda-spdx.json.golden +++ b/integration/testdata/conda-spdx.json.golden @@ -14,7 +14,7 @@ "packages": [ { "name": "openssl", - "SPDXID": "SPDXRef-Package-b8061a5279413d55", + "SPDXID": "SPDXRef-Package-32b6b37a6fa2e57f", "versionInfo": "1.1.1q", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -38,7 +38,7 @@ }, { "name": "pip", - "SPDXID": "SPDXRef-Package-84198b3828050c11", + "SPDXID": "SPDXRef-Package-e260029d0b6fd07b", "versionInfo": "22.2.2", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -103,22 +103,22 @@ }, { "spdxElementId": "SPDXRef-Filesystem-2e2426fd0f2580ef", - "relatedSpdxElement": "SPDXRef-Package-84198b3828050c11", + "relatedSpdxElement": "SPDXRef-Package-32b6b37a6fa2e57f", "relationshipType": "CONTAINS" }, { "spdxElementId": "SPDXRef-Filesystem-2e2426fd0f2580ef", - "relatedSpdxElement": "SPDXRef-Package-b8061a5279413d55", + "relatedSpdxElement": "SPDXRef-Package-e260029d0b6fd07b", "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-84198b3828050c11", - "relatedSpdxElement": "SPDXRef-File-7eb62e2a3edddc0a", + "spdxElementId": "SPDXRef-Package-32b6b37a6fa2e57f", + "relatedSpdxElement": "SPDXRef-File-600e5e0110a84891", "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-b8061a5279413d55", - "relatedSpdxElement": "SPDXRef-File-600e5e0110a84891", + "spdxElementId": "SPDXRef-Package-e260029d0b6fd07b", + "relatedSpdxElement": "SPDXRef-File-7eb62e2a3edddc0a", "relationshipType": "CONTAINS" } ] diff --git a/pkg/cloud/report/report.go b/pkg/cloud/report/report.go index 2b2f8f3f17ea..55d992fc505e 100644 --- a/pkg/cloud/report/report.go +++ b/pkg/cloud/report/report.go @@ -12,7 +12,7 @@ import ( "github.com/aquasecurity/tml" "github.com/aquasecurity/trivy/pkg/clock" cr "github.com/aquasecurity/trivy/pkg/compliance/report" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/iac/scan" pkgReport "github.com/aquasecurity/trivy/pkg/report" @@ -97,7 +97,7 @@ func Write(ctx context.Context, rep *Report, opt flag.Options, fromCache bool) e base := types.Report{ CreatedAt: clock.Now(ctx), ArtifactName: rep.AccountID, - ArtifactType: ftypes.ArtifactAWSAccount, + ArtifactType: artifact.TypeAWSAccount, Results: filtered, } diff --git a/pkg/fanal/artifact/artifact.go b/pkg/fanal/artifact/artifact.go index b8245f585389..e431278f6fc2 100644 --- a/pkg/fanal/artifact/artifact.go +++ b/pkg/fanal/artifact/artifact.go @@ -4,10 +4,13 @@ import ( "context" "sort" + "github.com/google/go-containerregistry/pkg/v1" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/sbom/core" ) type Option struct { @@ -72,6 +75,39 @@ func (o *Option) Sort() { } type Artifact interface { - Inspect(ctx context.Context) (reference types.ArtifactReference, err error) - Clean(reference types.ArtifactReference) error + Inspect(ctx context.Context) (reference Reference, err error) + Clean(reference Reference) error +} + +// Type represents a type of artifact +type Type string + +const ( + TypeContainerImage Type = "container_image" + TypeFilesystem Type = "filesystem" + TypeRepository Type = "repository" + TypeCycloneDX Type = "cyclonedx" + TypeSPDX Type = "spdx" + TypeAWSAccount Type = "aws_account" + TypeVM Type = "vm" +) + +// Reference represents a reference of container image, local filesystem and repository +type Reference struct { + Name string // image name, tar file name, directory or repository name + Type Type + ID string + BlobIDs []string + ImageMetadata ImageMetadata + + // SBOM + BOM *core.BOM +} + +type ImageMetadata struct { + ID string // image ID + DiffIDs []string // uncompressed layer IDs + RepoTags []string + RepoDigests []string + ConfigFile v1.ConfigFile } diff --git a/pkg/fanal/artifact/image/image.go b/pkg/fanal/artifact/image/image.go index b0749ad0d1ed..08b61de1b228 100644 --- a/pkg/fanal/artifact/image/image.go +++ b/pkg/fanal/artifact/image/image.go @@ -73,16 +73,16 @@ func NewArtifact(img types.Image, c cache.ArtifactCache, opt artifact.Option) (a }, nil } -func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) { +func (a Artifact) Inspect(ctx context.Context) (artifact.Reference, error) { imageID, err := a.image.ID() if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("unable to get the image ID: %w", err) + return artifact.Reference{}, xerrors.Errorf("unable to get the image ID: %w", err) } a.logger.Debug("Detected image ID", log.String("image_id", imageID)) configFile, err := a.image.ConfigFile() if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("unable to get the image's config file: %w", err) + return artifact.Reference{}, xerrors.Errorf("unable to get the image's config file: %w", err) } diffIDs := a.diffIDs(configFile) @@ -94,7 +94,7 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) return res, nil } else if !errors.Is(err, errNoSBOMFound) { // Fail on unexpected error, otherwise it falls into the usual scanning. - return types.ArtifactReference{}, xerrors.Errorf("remote SBOM fetching error: %w", err) + return artifact.Reference{}, xerrors.Errorf("remote SBOM fetching error: %w", err) } // Try to detect base layers. @@ -104,7 +104,7 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) // Convert image ID and layer IDs to cache keys imageKey, layerKeys, err := a.calcCacheKeys(imageID, diffIDs) if err != nil { - return types.ArtifactReference{}, err + return artifact.Reference{}, err } // Parse histories and extract a list of "created_by" @@ -112,7 +112,7 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) missingImage, missingLayers, err := a.cache.MissingBlobs(imageKey, layerKeys) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("unable to get missing layers: %w", err) + return artifact.Reference{}, xerrors.Errorf("unable to get missing layers: %w", err) } missingImageKey := imageKey @@ -123,15 +123,15 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) } if err = a.inspect(ctx, missingImageKey, missingLayers, baseDiffIDs, layerKeyMap, configFile); err != nil { - return types.ArtifactReference{}, xerrors.Errorf("analyze error: %w", err) + return artifact.Reference{}, xerrors.Errorf("analyze error: %w", err) } - return types.ArtifactReference{ + return artifact.Reference{ Name: a.image.Name(), - Type: types.ArtifactContainerImage, + Type: artifact.TypeContainerImage, ID: imageKey, BlobIDs: layerKeys, - ImageMetadata: types.ImageMetadata{ + ImageMetadata: artifact.ImageMetadata{ ID: imageID, DiffIDs: diffIDs, RepoTags: a.image.RepoTags(), @@ -141,7 +141,7 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) }, nil } -func (Artifact) Clean(_ types.ArtifactReference) error { +func (Artifact) Clean(_ artifact.Reference) error { return nil } diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go index fbcddef9ce4a..05fc6b229105 100644 --- a/pkg/fanal/artifact/image/image_test.go +++ b/pkg/fanal/artifact/image/image_test.go @@ -340,7 +340,7 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation cache.ArtifactCacheMissingBlobsExpectation putBlobExpectations []cache.ArtifactCachePutBlobExpectation putArtifactExpectations []cache.ArtifactCachePutArtifactExpectation - want types.ArtifactReference + want artifact.Reference wantErr string }{ { @@ -425,12 +425,12 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "../../test/testdata/alpine-311.tar.gz", - Type: types.ArtifactContainerImage, + Type: artifact.TypeContainerImage, ID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, - ImageMetadata: types.ImageMetadata{ + ImageMetadata: artifact.ImageMetadata{ ID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", DiffIDs: []string{ "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", @@ -1756,9 +1756,9 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "../../test/testdata/vuln-image.tar.gz", - Type: types.ArtifactContainerImage, + Type: artifact.TypeContainerImage, ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", @@ -1766,7 +1766,7 @@ func TestArtifact_Inspect(t *testing.T) { "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", }, - ImageMetadata: types.ImageMetadata{ + ImageMetadata: artifact.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", DiffIDs: []string{ "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", @@ -1921,9 +1921,9 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "../../test/testdata/vuln-image.tar.gz", - Type: types.ArtifactContainerImage, + Type: artifact.TypeContainerImage, ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", @@ -1931,7 +1931,7 @@ func TestArtifact_Inspect(t *testing.T) { "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", }, - ImageMetadata: types.ImageMetadata{ + ImageMetadata: artifact.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", DiffIDs: []string{ "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", diff --git a/pkg/fanal/artifact/image/remote_sbom.go b/pkg/fanal/artifact/image/remote_sbom.go index 9bb609e64c3b..8a386546c07a 100644 --- a/pkg/fanal/artifact/image/remote_sbom.go +++ b/pkg/fanal/artifact/image/remote_sbom.go @@ -14,6 +14,7 @@ import ( "golang.org/x/xerrors" sbomatt "github.com/aquasecurity/trivy/pkg/attestation/sbom" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact/sbom" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" @@ -24,9 +25,9 @@ import ( var errNoSBOMFound = xerrors.New("remote SBOM not found") -type inspectRemoteSBOM func(context.Context) (ftypes.ArtifactReference, error) +type inspectRemoteSBOM func(context.Context) (artifact.Reference, error) -func (a Artifact) retrieveRemoteSBOM(ctx context.Context) (ftypes.ArtifactReference, error) { +func (a Artifact) retrieveRemoteSBOM(ctx context.Context) (artifact.Reference, error) { for _, sbomSource := range a.artifactOption.SBOMSources { var inspect inspectRemoteSBOM switch sbomSource { @@ -45,27 +46,27 @@ func (a Artifact) retrieveRemoteSBOM(ctx context.Context) (ftypes.ArtifactRefere a.logger.Debug("No SBOM found in the source", log.String("source", sbomSource)) continue } else if err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("SBOM searching error: %w", err) + return artifact.Reference{}, xerrors.Errorf("SBOM searching error: %w", err) } return ref, nil } - return ftypes.ArtifactReference{}, errNoSBOMFound + return artifact.Reference{}, errNoSBOMFound } -func (a Artifact) inspectOCIReferrerSBOM(ctx context.Context) (ftypes.ArtifactReference, error) { +func (a Artifact) inspectOCIReferrerSBOM(ctx context.Context) (artifact.Reference, error) { digest, err := repoDigest(a.image, a.artifactOption.Insecure) if err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("repo digest error: %w", err) + return artifact.Reference{}, xerrors.Errorf("repo digest error: %w", err) } // Fetch referrers index, err := remote.Referrers(ctx, digest, a.artifactOption.ImageOption.RegistryOptions) if err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("unable to fetch referrers: %w", err) + return artifact.Reference{}, xerrors.Errorf("unable to fetch referrers: %w", err) } manifest, err := index.IndexManifest() if err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("unable to get manifest: %w", err) + return artifact.Reference{}, xerrors.Errorf("unable to get manifest: %w", err) } for _, m := range lo.FromPtr(manifest).Manifests { // Unsupported artifact type @@ -80,20 +81,20 @@ func (a Artifact) inspectOCIReferrerSBOM(ctx context.Context) (ftypes.ArtifactRe } return res, nil } - return ftypes.ArtifactReference{}, errNoSBOMFound + return artifact.Reference{}, errNoSBOMFound } -func (a Artifact) parseReferrer(ctx context.Context, repo string, desc v1.Descriptor) (ftypes.ArtifactReference, error) { +func (a Artifact) parseReferrer(ctx context.Context, repo string, desc v1.Descriptor) (artifact.Reference, error) { const fileName string = "referrer.sbom" repoName := fmt.Sprintf("%s@%s", repo, desc.Digest) referrer, err := oci.NewArtifact(repoName, true, a.artifactOption.ImageOption.RegistryOptions) if err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("OCI error: %w", err) + return artifact.Reference{}, xerrors.Errorf("OCI error: %w", err) } tmpDir, err := os.MkdirTemp("", "trivy-sbom-*") if err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("mkdir temp error: %w", err) + return artifact.Reference{}, xerrors.Errorf("mkdir temp error: %w", err) } defer os.RemoveAll(tmpDir) @@ -102,7 +103,7 @@ func (a Artifact) parseReferrer(ctx context.Context, repo string, desc v1.Descri MediaType: desc.ArtifactType, Filename: fileName, }); err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("SBOM download error: %w", err) + return artifact.Reference{}, xerrors.Errorf("SBOM download error: %w", err) } res, err := a.inspectSBOMFile(ctx, filepath.Join(tmpDir, fileName)) @@ -116,35 +117,35 @@ func (a Artifact) parseReferrer(ctx context.Context, repo string, desc v1.Descri return res, nil } -func (a Artifact) inspectRekorSBOMAttestation(ctx context.Context) (ftypes.ArtifactReference, error) { +func (a Artifact) inspectRekorSBOMAttestation(ctx context.Context) (artifact.Reference, error) { digest, err := repoDigest(a.image, a.artifactOption.Insecure) if err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("repo digest error: %w", err) + return artifact.Reference{}, xerrors.Errorf("repo digest error: %w", err) } client, err := sbomatt.NewRekor(a.artifactOption.RekorURL) if err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("failed to create rekor client: %w", err) + return artifact.Reference{}, xerrors.Errorf("failed to create rekor client: %w", err) } raw, err := client.RetrieveSBOM(ctx, digest.DigestStr()) if errors.Is(err, sbomatt.ErrNoSBOMAttestation) { - return ftypes.ArtifactReference{}, errNoSBOMFound + return artifact.Reference{}, errNoSBOMFound } else if err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("failed to retrieve SBOM attestation: %w", err) + return artifact.Reference{}, xerrors.Errorf("failed to retrieve SBOM attestation: %w", err) } f, err := os.CreateTemp("", "sbom-*") if err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("failed to create a temporary file: %w", err) + return artifact.Reference{}, xerrors.Errorf("failed to create a temporary file: %w", err) } defer os.Remove(f.Name()) if _, err = f.Write(raw); err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("copy error: %w", err) + return artifact.Reference{}, xerrors.Errorf("copy error: %w", err) } if err = f.Close(); err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("failed to close %s: %w", f.Name(), err) + return artifact.Reference{}, xerrors.Errorf("failed to close %s: %w", f.Name(), err) } res, err := a.inspectSBOMFile(ctx, f.Name()) if err != nil { @@ -158,15 +159,15 @@ func (a Artifact) inspectRekorSBOMAttestation(ctx context.Context) (ftypes.Artif return res, nil } -func (a Artifact) inspectSBOMFile(ctx context.Context, filePath string) (ftypes.ArtifactReference, error) { +func (a Artifact) inspectSBOMFile(ctx context.Context, filePath string) (artifact.Reference, error) { ar, err := sbom.NewArtifact(filePath, a.cache, a.artifactOption) if err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("failed to new artifact: %w", err) + return artifact.Reference{}, xerrors.Errorf("failed to new artifact: %w", err) } results, err := ar.Inspect(ctx) if err != nil { - return ftypes.ArtifactReference{}, xerrors.Errorf("failed to inspect: %w", err) + return artifact.Reference{}, xerrors.Errorf("failed to inspect: %w", err) } results.Name = a.image.Name() diff --git a/pkg/fanal/artifact/image/remote_sbom_test.go b/pkg/fanal/artifact/image/remote_sbom_test.go index b38bdc5c6dc5..2b58d2fdb8a3 100644 --- a/pkg/fanal/artifact/image/remote_sbom_test.go +++ b/pkg/fanal/artifact/image/remote_sbom_test.go @@ -56,7 +56,7 @@ func TestArtifact_InspectRekorAttestation(t *testing.T) { fields fields artifactOpt artifact.Option putBlobExpectations []cache.ArtifactCachePutBlobExpectation - want types.ArtifactReference + want artifact.Reference wantErr string }{ { @@ -117,9 +117,9 @@ func TestArtifact_InspectRekorAttestation(t *testing.T) { artifactOpt: artifact.Option{ SBOMSources: []string{"rekor"}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "test/image:10", - Type: types.ArtifactCycloneDX, + Type: artifact.TypeCycloneDX, ID: "sha256:066b9998617ffb7dfe0a3219ac5c3efc1008a6223606fcf474e7d5c965e4e8da", BlobIDs: []string{ "sha256:066b9998617ffb7dfe0a3219ac5c3efc1008a6223606fcf474e7d5c965e4e8da", @@ -206,7 +206,7 @@ func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { fields fields artifactOpt artifact.Option putBlobExpectations []cache.ArtifactCachePutBlobExpectation - want types.ArtifactReference + want artifact.Reference wantErr string }{ { @@ -265,9 +265,9 @@ func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { }, }, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: registry + "/test/image:10", - Type: types.ArtifactCycloneDX, + Type: artifact.TypeCycloneDX, ID: "sha256:a06ed679a3289fba254040e1ce8f3467fadcc454ee3d0d4720f6978065f56684", BlobIDs: []string{ "sha256:a06ed679a3289fba254040e1ce8f3467fadcc454ee3d0d4720f6978065f56684", diff --git a/pkg/fanal/artifact/local/fs.go b/pkg/fanal/artifact/local/fs.go index 5896196c0e48..b807db2c6733 100644 --- a/pkg/fanal/artifact/local/fs.go +++ b/pkg/fanal/artifact/local/fs.go @@ -68,7 +68,7 @@ func NewArtifact(rootPath string, c cache.ArtifactCache, w Walker, opt artifact. }, nil } -func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) { +func (a Artifact) Inspect(ctx context.Context) (artifact.Reference, error) { var wg sync.WaitGroup result := analyzer.NewAnalysisResult() limit := semaphore.New(a.artifactOption.Parallel) @@ -80,7 +80,7 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) // Prepare filesystem for post analysis composite, err := a.analyzer.PostAnalyzerFS() if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("failed to prepare filesystem for post analysis: %w", err) + return artifact.Reference{}, xerrors.Errorf("failed to prepare filesystem for post analysis: %w", err) } err = a.walker.Walk(a.rootPath, a.artifactOption.WalkerOption, func(filePath string, info os.FileInfo, opener analyzer.Opener) error { @@ -110,7 +110,7 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) return nil }) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("walk filesystem: %w", err) + return artifact.Reference{}, xerrors.Errorf("walk filesystem: %w", err) } // Wait for all the goroutine to finish. @@ -118,7 +118,7 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) // Post-analysis if err = a.analyzer.PostAnalyze(ctx, composite, result, opts); err != nil { - return types.ArtifactReference{}, xerrors.Errorf("post analysis error: %w", err) + return artifact.Reference{}, xerrors.Errorf("post analysis error: %w", err) } // Sort the analysis result for consistent results @@ -137,16 +137,16 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) } if err = a.handlerManager.PostHandle(ctx, result, &blobInfo); err != nil { - return types.ArtifactReference{}, xerrors.Errorf("failed to call hooks: %w", err) + return artifact.Reference{}, xerrors.Errorf("failed to call hooks: %w", err) } cacheKey, err := a.calcCacheKey(blobInfo) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("failed to calculate a cache key: %w", err) + return artifact.Reference{}, xerrors.Errorf("failed to calculate a cache key: %w", err) } if err = a.cache.PutBlob(cacheKey, blobInfo); err != nil { - return types.ArtifactReference{}, xerrors.Errorf("failed to store blob (%s) in cache: %w", cacheKey, err) + return artifact.Reference{}, xerrors.Errorf("failed to store blob (%s) in cache: %w", cacheKey, err) } // get hostname @@ -159,15 +159,15 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) hostName = filepath.ToSlash(a.rootPath) } - return types.ArtifactReference{ + return artifact.Reference{ Name: hostName, - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: cacheKey, // use a cache key as pseudo artifact ID BlobIDs: []string{cacheKey}, }, nil } -func (a Artifact) Clean(reference types.ArtifactReference) error { +func (a Artifact) Clean(reference artifact.Reference) error { return a.cache.DeleteBlobs(reference.BlobIDs) } diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index f230cb1340fc..176a53797616 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -36,7 +36,7 @@ func TestArtifact_Inspect(t *testing.T) { disabledAnalyzers []analyzer.Type disabledHandlers []types.HandlerType putBlobExpectation cache.ArtifactCachePutBlobExpectation - want types.ArtifactReference + want artifact.Reference wantErr string }{ { @@ -78,9 +78,9 @@ func TestArtifact_Inspect(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "host", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", BlobIDs: []string{ "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", @@ -108,9 +108,9 @@ func TestArtifact_Inspect(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "host", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", BlobIDs: []string{ "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", @@ -193,9 +193,9 @@ func TestArtifact_Inspect(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/requirements.txt", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", BlobIDs: []string{ "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", @@ -228,9 +228,9 @@ func TestArtifact_Inspect(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/requirements.txt", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", BlobIDs: []string{ "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", @@ -279,7 +279,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields fields putBlobExpectation cache.ArtifactCachePutBlobExpectation artifactOpt artifact.Option - want types.ArtifactReference + want artifact.Reference }{ { name: "single failure", @@ -325,9 +325,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/terraform/single-failure", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:1a6ce0acc3b57eb6c830c96fcd868fec1eb4d3b57ad51e481c76d85f22870a65", BlobIDs: []string{ "sha256:1a6ce0acc3b57eb6c830c96fcd868fec1eb4d3b57ad51e481c76d85f22870a65", @@ -410,9 +410,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/terraform/multiple-failures", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:afc20cf0fc99c62bbc79b00cb9fbc70ba7ee76c946a6d560639ba9279344787d", BlobIDs: []string{ "sha256:afc20cf0fc99c62bbc79b00cb9fbc70ba7ee76c946a6d560639ba9279344787d", @@ -440,9 +440,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/terraform/no-results", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:1827b6a0b0a17e0d623a2045e9d9c331ef613390eda2fed823969ee0dd730257", BlobIDs: []string{ "sha256:1827b6a0b0a17e0d623a2045e9d9c331ef613390eda2fed823969ee0dd730257", @@ -489,9 +489,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/terraform/passed", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", BlobIDs: []string{ "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", @@ -555,9 +555,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/terraform/busted-relative-paths/child/main.tf", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:8db34d644bfb98077180616caeab0b41a26d9029a47a23d4b36e1d6e45584919", BlobIDs: []string{ "sha256:8db34d644bfb98077180616caeab0b41a26d9029a47a23d4b36e1d6e45584919", @@ -605,9 +605,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/terraform/tfvar-outside/tf", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", BlobIDs: []string{ "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", @@ -695,9 +695,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/terraform/relative-paths/child", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:f13b89447db61be1c1e4099ef18aec7272091f8f2d3581643a9d1fabc74eda83", BlobIDs: []string{ "sha256:f13b89447db61be1c1e4099ef18aec7272091f8f2d3581643a9d1fabc74eda83", @@ -776,7 +776,7 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { name string fields fields putBlobExpectation cache.ArtifactCachePutBlobExpectation - want types.ArtifactReference + want artifact.Reference }{ { name: "single failure", @@ -814,9 +814,9 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/terraformplan/snapshots/single-failure", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:732c38451bde877a94d1ff5b6f2019655bed04fd24b1169de195eeee1199045e", BlobIDs: []string{ "sha256:732c38451bde877a94d1ff5b6f2019655bed04fd24b1169de195eeee1199045e", @@ -890,9 +890,9 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/terraformplan/snapshots/multiple-failures", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:752c0b470adfcfe7e21892cf4c6fc3bc28dba873c9c1696f40b71b7a51ad7231", BlobIDs: []string{ "sha256:752c0b470adfcfe7e21892cf4c6fc3bc28dba873c9c1696f40b71b7a51ad7231", @@ -930,9 +930,9 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/terraformplan/snapshots/passed", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:8947704a08f54ab1df32cd905d6bca72edf1785a42702968bafa331172da7176", BlobIDs: []string{ "sha256:8947704a08f54ab1df32cd905d6bca72edf1785a42702968bafa331172da7176", @@ -990,7 +990,7 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { fields fields putBlobExpectation cache.ArtifactCachePutBlobExpectation artifactOpt artifact.Option - want types.ArtifactReference + want artifact.Reference }{ { name: "single failure", @@ -1045,9 +1045,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/single-failure/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:889a94522970c6e55f1f7543914b2f0131f79f9c4526445fb95309f64a9947d7", BlobIDs: []string{ "sha256:889a94522970c6e55f1f7543914b2f0131f79f9c4526445fb95309f64a9947d7", @@ -1129,9 +1129,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/multiple-failures/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:17c9c72a759856445e6d3847b2d5ed90c3bad3e4ee50cea0c812ef53c179f8ca", BlobIDs: []string{ "sha256:17c9c72a759856445e6d3847b2d5ed90c3bad3e4ee50cea0c812ef53c179f8ca", @@ -1161,9 +1161,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/no-results/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", BlobIDs: []string{ "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", @@ -1219,9 +1219,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/params/code/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:267b572211115db6a2a4484a02317fbb6d4f050da0e95b1db4243d49889483de", BlobIDs: []string{ "sha256:267b572211115db6a2a4484a02317fbb6d4f050da0e95b1db4243d49889483de", @@ -1277,9 +1277,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/passed/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:8ca92725ce2f47b7ffb1b0a9e0359d59ac2b3b3f517ba42f66a859436057e54a", BlobIDs: []string{ "sha256:8ca92725ce2f47b7ffb1b0a9e0359d59ac2b3b3f517ba42f66a859436057e54a", @@ -1314,7 +1314,7 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { fields fields putBlobExpectation cache.ArtifactCachePutBlobExpectation artifactOpt artifact.Option - want types.ArtifactReference + want artifact.Reference }{ { name: "single failure", @@ -1365,9 +1365,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/single-failure/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", BlobIDs: []string{ "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", @@ -1423,9 +1423,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/multiple-failures/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", BlobIDs: []string{ "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", @@ -1453,9 +1453,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/no-results/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", BlobIDs: []string{ "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", @@ -1513,9 +1513,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/passed/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:4cc7f6bba417cc65c5391bc9c07fd1e205e21bdec87b271889433af18be1e454", BlobIDs: []string{ "sha256:4cc7f6bba417cc65c5391bc9c07fd1e205e21bdec87b271889433af18be1e454", @@ -1549,7 +1549,7 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { fields fields putBlobExpectation cache.ArtifactCachePutBlobExpectation artifactOpt artifact.Option - want types.ArtifactReference + want artifact.Reference }{ { name: "single failure", @@ -1605,9 +1605,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/single-failure/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:d5ca0b4e96aaaeafa424a2250db6297a5182cb6ca5db49bc1ff11790f6cdbee9", BlobIDs: []string{ "sha256:d5ca0b4e96aaaeafa424a2250db6297a5182cb6ca5db49bc1ff11790f6cdbee9", @@ -1691,9 +1691,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/multiple-failures/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:eef9fff2fe8f5c4a123c018b4f91db25d9676e7d171a3a683c2fbfbbbe82fa54", BlobIDs: []string{ "sha256:eef9fff2fe8f5c4a123c018b4f91db25d9676e7d171a3a683c2fbfbbbe82fa54", @@ -1721,9 +1721,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/no-results/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:2b54cf33feaa1fe1f5bf223f873ca6c3f7c3693b0bb3b0ce9e2e7fd79cd37b5a", BlobIDs: []string{ "sha256:2b54cf33feaa1fe1f5bf223f873ca6c3f7c3693b0bb3b0ce9e2e7fd79cd37b5a", @@ -1781,9 +1781,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/passed/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:dc7a0fd3ea2f13b0ea05f4e517a16e602b0fc17fbd72aa5e34107ef12a91a30b", BlobIDs: []string{ "sha256:dc7a0fd3ea2f13b0ea05f4e517a16e602b0fc17fbd72aa5e34107ef12a91a30b", @@ -1817,7 +1817,7 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { fields fields putBlobExpectation cache.ArtifactCachePutBlobExpectation artifactOpt artifact.Option - want types.ArtifactReference + want artifact.Reference }{ { name: "single failure", @@ -1870,9 +1870,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/azurearm/single-failure/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:c1a8bfd544b9041ad194382cc42b54289f70966d061ef501b267aec8fd07c5df", BlobIDs: []string{ "sha256:c1a8bfd544b9041ad194382cc42b54289f70966d061ef501b267aec8fd07c5df", @@ -1952,9 +1952,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/azurearm/multiple-failures/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:75bf0e88f8d2857be90fb8d10a350c04c1532ba7f510e1eb404a8bae30ce97d8", BlobIDs: []string{ "sha256:75bf0e88f8d2857be90fb8d10a350c04c1532ba7f510e1eb404a8bae30ce97d8", @@ -1982,9 +1982,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/azurearm/no-results/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", BlobIDs: []string{ "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", @@ -2038,9 +2038,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/azurearm/passed/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, ID: "sha256:b9ba7c4eafec405c8b6998dbb98ee1c7f7830caf8487fd1461433ff82d8779e9", BlobIDs: []string{ "sha256:b9ba7c4eafec405c8b6998dbb98ee1c7f7830caf8487fd1461433ff82d8779e9", @@ -2074,7 +2074,7 @@ func TestMixedConfigurationScan(t *testing.T) { fields fields putBlobExpectation cache.ArtifactCachePutBlobExpectation artifactOpt artifact.Option - want types.ArtifactReference + want artifact.Reference }{ { name: "single failure each within terraform and cloudformation", @@ -2157,9 +2157,9 @@ func TestMixedConfigurationScan(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "testdata/misconfig/mixed/src", - Type: types.ArtifactFilesystem, + Type: artifact.TypeFilesystem, }, }, } diff --git a/pkg/fanal/artifact/mock_artifact.go b/pkg/fanal/artifact/mock_artifact.go index d17a15d46089..edfe12514659 100644 --- a/pkg/fanal/artifact/mock_artifact.go +++ b/pkg/fanal/artifact/mock_artifact.go @@ -6,8 +6,6 @@ import ( context "context" mock "github.com/stretchr/testify/mock" - - types "github.com/aquasecurity/trivy/pkg/fanal/types" ) // MockArtifact is an autogenerated mock type for the Artifact type @@ -16,7 +14,7 @@ type MockArtifact struct { } type ArtifactCleanArgs struct { - Reference types.ArtifactReference + Reference Reference ReferenceAnything bool } @@ -46,7 +44,7 @@ func (_m *MockArtifact) ApplyCleanExpectations(expectations []ArtifactCleanExpec } // Clean provides a mock function with given fields: reference -func (_m *MockArtifact) Clean(reference types.ArtifactReference) error { +func (_m *MockArtifact) Clean(reference Reference) error { return nil } @@ -56,7 +54,7 @@ type ArtifactInspectArgs struct { } type ArtifactInspectReturns struct { - Reference types.ArtifactReference + Reference Reference Err error } @@ -82,14 +80,14 @@ func (_m *MockArtifact) ApplyInspectExpectations(expectations []ArtifactInspectE } // Inspect provides a mock function with given fields: ctx -func (_m *MockArtifact) Inspect(ctx context.Context) (types.ArtifactReference, error) { +func (_m *MockArtifact) Inspect(ctx context.Context) (Reference, error) { ret := _m.Called(ctx) - var r0 types.ArtifactReference - if rf, ok := ret.Get(0).(func(context.Context) types.ArtifactReference); ok { + var r0 Reference + if rf, ok := ret.Get(0).(func(context.Context) Reference); ok { r0 = rf(ctx) } else { - r0 = ret.Get(0).(types.ArtifactReference) + r0 = ret.Get(0).(Reference) } var r1 error diff --git a/pkg/fanal/artifact/repo/git.go b/pkg/fanal/artifact/repo/git.go index 977c75d71bb7..4ce1c990c925 100644 --- a/pkg/fanal/artifact/repo/git.go +++ b/pkg/fanal/artifact/repo/git.go @@ -15,7 +15,6 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact/local" "github.com/aquasecurity/trivy/pkg/fanal/cache" - "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" ) @@ -62,21 +61,21 @@ func NewArtifact(target string, c cache.ArtifactCache, w Walker, artifactOpt art return nil, cleanup, errs } -func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) { +func (a Artifact) Inspect(ctx context.Context) (artifact.Reference, error) { ref, err := a.local.Inspect(ctx) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("remote repository error: %w", err) + return artifact.Reference{}, xerrors.Errorf("remote repository error: %w", err) } if a.url != "" { ref.Name = a.url } - ref.Type = types.ArtifactRepository + ref.Type = artifact.TypeRepository return ref, nil } -func (Artifact) Clean(_ types.ArtifactReference) error { +func (Artifact) Clean(_ artifact.Reference) error { return nil } diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go index de484e64d221..0e7aadd411a9 100644 --- a/pkg/fanal/artifact/repo/git_test.go +++ b/pkg/fanal/artifact/repo/git_test.go @@ -12,12 +12,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/fanal/cache" - "github.com/aquasecurity/trivy/pkg/fanal/types" - _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/cache" ) func setupGitServer() (*httptest.Server, error) { @@ -186,15 +184,15 @@ func TestArtifact_Inspect(t *testing.T) { tests := []struct { name string rawurl string - want types.ArtifactReference + want artifact.Reference wantErr bool }{ { name: "happy path", rawurl: ts.URL + "/test.git", - want: types.ArtifactReference{ + want: artifact.Reference{ Name: ts.URL + "/test.git", - Type: types.ArtifactRepository, + Type: artifact.TypeRepository, ID: "sha256:6a89d4fcd50f840a79da64523c255da80171acd3d286df2acc60056c778d9304", BlobIDs: []string{ "sha256:6a89d4fcd50f840a79da64523c255da80171acd3d286df2acc60056c778d9304", diff --git a/pkg/fanal/artifact/sbom/sbom.go b/pkg/fanal/artifact/sbom/sbom.go index 90eed8c89e1b..979c5c5a8517 100644 --- a/pkg/fanal/artifact/sbom/sbom.go +++ b/pkg/fanal/artifact/sbom/sbom.go @@ -37,23 +37,23 @@ func NewArtifact(filePath string, c cache.ArtifactCache, opt artifact.Option) (a }, nil } -func (a Artifact) Inspect(_ context.Context) (types.ArtifactReference, error) { +func (a Artifact) Inspect(_ context.Context) (artifact.Reference, error) { f, err := os.Open(a.filePath) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("failed to open sbom file error: %w", err) + return artifact.Reference{}, xerrors.Errorf("failed to open sbom file error: %w", err) } defer f.Close() // Format auto-detection format, err := sbom.DetectFormat(f) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("failed to detect SBOM format: %w", err) + return artifact.Reference{}, xerrors.Errorf("failed to detect SBOM format: %w", err) } log.Info("Detected SBOM format", log.String("format", string(format))) bom, err := sbom.Decode(f, format) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("SBOM decode error: %w", err) + return artifact.Reference{}, xerrors.Errorf("SBOM decode error: %w", err) } blobInfo := types.BlobInfo{ @@ -65,23 +65,23 @@ func (a Artifact) Inspect(_ context.Context) (types.ArtifactReference, error) { cacheKey, err := a.calcCacheKey(blobInfo) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("failed to calculate a cache key: %w", err) + return artifact.Reference{}, xerrors.Errorf("failed to calculate a cache key: %w", err) } if err = a.cache.PutBlob(cacheKey, blobInfo); err != nil { - return types.ArtifactReference{}, xerrors.Errorf("failed to store blob (%s) in cache: %w", cacheKey, err) + return artifact.Reference{}, xerrors.Errorf("failed to store blob (%s) in cache: %w", cacheKey, err) } - var artifactType types.ArtifactType + var artifactType artifact.Type switch format { case sbom.FormatCycloneDXJSON, sbom.FormatCycloneDXXML, sbom.FormatAttestCycloneDXJSON, sbom.FormatLegacyCosignAttestCycloneDXJSON: - artifactType = types.ArtifactCycloneDX + artifactType = artifact.TypeCycloneDX case sbom.FormatSPDXTV, sbom.FormatSPDXJSON: - artifactType = types.ArtifactSPDX + artifactType = artifact.TypeSPDX } - return types.ArtifactReference{ + return artifact.Reference{ Name: a.filePath, Type: artifactType, ID: cacheKey, // use a cache key as pseudo artifact ID @@ -92,7 +92,7 @@ func (a Artifact) Inspect(_ context.Context) (types.ArtifactReference, error) { }, nil } -func (a Artifact) Clean(reference types.ArtifactReference) error { +func (a Artifact) Clean(reference artifact.Reference) error { return a.cache.DeleteBlobs(reference.BlobIDs) } diff --git a/pkg/fanal/artifact/sbom/sbom_test.go b/pkg/fanal/artifact/sbom/sbom_test.go index 11ddfdad781b..7355c0c75abe 100644 --- a/pkg/fanal/artifact/sbom/sbom_test.go +++ b/pkg/fanal/artifact/sbom/sbom_test.go @@ -22,7 +22,7 @@ func TestArtifact_Inspect(t *testing.T) { name string filePath string putBlobExpectation cache.ArtifactCachePutBlobExpectation - want types.ArtifactReference + want artifact.Reference wantErr []string }{ { @@ -188,9 +188,9 @@ func TestArtifact_Inspect(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: filepath.Join("testdata", "bom.json"), - Type: types.ArtifactCycloneDX, + Type: artifact.TypeCycloneDX, ID: "sha256:76bc49ae239d24c6a122e730bafb9d5295d0af380492aeb92a3bf34bea3a14ca", BlobIDs: []string{ "sha256:76bc49ae239d24c6a122e730bafb9d5295d0af380492aeb92a3bf34bea3a14ca", @@ -360,9 +360,9 @@ func TestArtifact_Inspect(t *testing.T) { }, Returns: cache.ArtifactCachePutBlobReturns{}, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: filepath.Join("testdata", "sbom.cdx.intoto.jsonl"), - Type: types.ArtifactCycloneDX, + Type: artifact.TypeCycloneDX, ID: "sha256:76bc49ae239d24c6a122e730bafb9d5295d0af380492aeb92a3bf34bea3a14ca", BlobIDs: []string{ "sha256:76bc49ae239d24c6a122e730bafb9d5295d0af380492aeb92a3bf34bea3a14ca", diff --git a/pkg/fanal/artifact/vm/ami.go b/pkg/fanal/artifact/vm/ami.go index 791c41d52896..23b72990ae74 100644 --- a/pkg/fanal/artifact/vm/ami.go +++ b/pkg/fanal/artifact/vm/ami.go @@ -8,7 +8,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/cloud/aws/config" - "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/log" ) @@ -55,10 +55,10 @@ func newAMI(imageID string, storage Storage, region, endpoint string) (*AMI, err return nil, xerrors.New("no snapshot found") } -func (a *AMI) Inspect(ctx context.Context) (types.ArtifactReference, error) { +func (a *AMI) Inspect(ctx context.Context) (artifact.Reference, error) { ref, err := a.EBS.Inspect(ctx) if err != nil { - return types.ArtifactReference{}, err + return artifact.Reference{}, err } ref.Name = a.imageID return ref, nil diff --git a/pkg/fanal/artifact/vm/ebs.go b/pkg/fanal/artifact/vm/ebs.go index d9881edf193a..64e1cc6a6b5a 100644 --- a/pkg/fanal/artifact/vm/ebs.go +++ b/pkg/fanal/artifact/vm/ebs.go @@ -9,8 +9,8 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/cloud/aws/config" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/cache" - "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" ) @@ -40,21 +40,21 @@ func newEBS(snapshotID string, vm Storage, region, endpoint string) (*EBS, error }, nil } -func (a *EBS) Inspect(ctx context.Context) (types.ArtifactReference, error) { +func (a *EBS) Inspect(ctx context.Context) (artifact.Reference, error) { sr, err := a.openEBS(ctx) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("EBS open error: %w", err) + return artifact.Reference{}, xerrors.Errorf("EBS open error: %w", err) } cacheKey, err := a.calcCacheKey(a.snapshotID) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("cache key calculation error: %w", err) + return artifact.Reference{}, xerrors.Errorf("cache key calculation error: %w", err) } if a.hasCache(cacheKey) { - return types.ArtifactReference{ + return artifact.Reference{ Name: a.snapshotID, - Type: types.ArtifactVM, + Type: artifact.TypeVM, ID: cacheKey, // use a cache key as pseudo artifact ID BlobIDs: []string{cacheKey}, }, nil @@ -62,16 +62,16 @@ func (a *EBS) Inspect(ctx context.Context) (types.ArtifactReference, error) { blobInfo, err := a.Analyze(ctx, sr) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("inspection error: %w", err) + return artifact.Reference{}, xerrors.Errorf("inspection error: %w", err) } if err = a.cache.PutBlob(cacheKey, blobInfo); err != nil { - return types.ArtifactReference{}, xerrors.Errorf("failed to store blob (%s) in cache: %w", cacheKey, err) + return artifact.Reference{}, xerrors.Errorf("failed to store blob (%s) in cache: %w", cacheKey, err) } - return types.ArtifactReference{ + return artifact.Reference{ Name: a.snapshotID, - Type: types.ArtifactVM, + Type: artifact.TypeVM, ID: cacheKey, // use a cache key as pseudo artifact ID BlobIDs: []string{cacheKey}, }, nil @@ -90,7 +90,7 @@ func (a *EBS) openEBS(ctx context.Context) (*io.SectionReader, error) { return r, nil } -func (a *EBS) Clean(_ types.ArtifactReference) error { +func (a *EBS) Clean(_ artifact.Reference) error { return nil } diff --git a/pkg/fanal/artifact/vm/file.go b/pkg/fanal/artifact/vm/file.go index 58fd4d46c96a..7968cf44681b 100644 --- a/pkg/fanal/artifact/vm/file.go +++ b/pkg/fanal/artifact/vm/file.go @@ -12,6 +12,7 @@ import ( "github.com/opencontainers/go-digest" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/vm" @@ -68,24 +69,24 @@ func newFile(filePath string, storage Storage) (*ImageFile, error) { }, nil } -func (a *ImageFile) Inspect(ctx context.Context) (types.ArtifactReference, error) { +func (a *ImageFile) Inspect(ctx context.Context) (artifact.Reference, error) { blobInfo, err := a.Analyze(ctx, a.reader) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("inspection error: %w", err) + return artifact.Reference{}, xerrors.Errorf("inspection error: %w", err) } cacheKey, err := a.calcCacheKey(blobInfo) if err != nil { - return types.ArtifactReference{}, xerrors.Errorf("cache calculation error: %w", err) + return artifact.Reference{}, xerrors.Errorf("cache calculation error: %w", err) } if err = a.cache.PutBlob(cacheKey, blobInfo); err != nil { - return types.ArtifactReference{}, xerrors.Errorf("failed to store blob (%s) in cache: %w", cacheKey, err) + return artifact.Reference{}, xerrors.Errorf("failed to store blob (%s) in cache: %w", cacheKey, err) } - return types.ArtifactReference{ + return artifact.Reference{ Name: a.filePath, - Type: types.ArtifactVM, + Type: artifact.TypeVM, ID: cacheKey, // use a cache key as pseudo artifact ID BlobIDs: []string{cacheKey}, }, nil @@ -107,7 +108,7 @@ func (a *ImageFile) calcCacheKey(blobInfo types.BlobInfo) (string, error) { return cacheKey, nil } -func (a *ImageFile) Clean(reference types.ArtifactReference) error { +func (a *ImageFile) Clean(reference artifact.Reference) error { _ = a.file.Close() return a.cache.DeleteBlobs(reference.BlobIDs) } diff --git a/pkg/fanal/artifact/vm/vm_test.go b/pkg/fanal/artifact/vm/vm_test.go index b510662a2dbf..bbe04d6eba84 100644 --- a/pkg/fanal/artifact/vm/vm_test.go +++ b/pkg/fanal/artifact/vm/vm_test.go @@ -112,7 +112,7 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation cache.ArtifactCacheMissingBlobsExpectation putBlobExpectation cache.ArtifactCachePutBlobExpectation putArtifactExpectations []cache.ArtifactCachePutArtifactExpectation - want types.ArtifactReference + want artifact.Reference wantErr string }{ { @@ -136,9 +136,9 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "rawdata.img", - Type: types.ArtifactVM, + Type: artifact.TypeVM, ID: "sha256:84a726d23c36d0e1857101969b257c1199de5432489d44581750d54ea8eff8cd", BlobIDs: []string{ "sha256:84a726d23c36d0e1857101969b257c1199de5432489d44581750d54ea8eff8cd", @@ -172,9 +172,9 @@ func TestArtifact_Inspect(t *testing.T) { }, }, }, - want: types.ArtifactReference{ + want: artifact.Reference{ Name: "ebs-012345", - Type: types.ArtifactVM, + Type: artifact.TypeVM, ID: "sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a", BlobIDs: []string{ "sha256:c28da2df41e019b5d18459440178341ec05e9082b12b6f11afe73f0600bfe96a", diff --git a/pkg/fanal/test/integration/containerd_test.go b/pkg/fanal/test/integration/containerd_test.go index 9ca993ec0627..c335159fb851 100644 --- a/pkg/fanal/test/integration/containerd_test.go +++ b/pkg/fanal/test/integration/containerd_test.go @@ -295,13 +295,13 @@ func localImageTestWithNamespace(t *testing.T, namespace string) { name string imageName string tarArchive string - wantMetadata types.ImageMetadata + wantMetadata artifact.ImageMetadata }{ { name: "alpine 3.10", imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", tarArchive: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", - wantMetadata: types.ImageMetadata{ + wantMetadata: artifact.ImageMetadata{ ID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", DiffIDs: []string{ "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", @@ -349,7 +349,7 @@ func localImageTestWithNamespace(t *testing.T, namespace string) { name: "vulnimage", imageName: "ghcr.io/aquasecurity/trivy-test-images:vulnimage", tarArchive: "../../../../integration/testdata/fixtures/images/vulnimage.tar.gz", - wantMetadata: types.ImageMetadata{ + wantMetadata: artifact.ImageMetadata{ ID: "sha256:c17083664da903e13e9092fa3a3a1aeee2431aa2728298e3dbcec72f26369c41", DiffIDs: []string{ "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", @@ -746,12 +746,12 @@ func TestContainerd_PullImage(t *testing.T) { tests := []struct { name string imageName string - wantMetadata types.ImageMetadata + wantMetadata artifact.ImageMetadata }{ { name: "remote alpine 3.10", imageName: "ghcr.io/aquasecurity/trivy-test-images:alpine-310", - wantMetadata: types.ImageMetadata{ + wantMetadata: artifact.ImageMetadata{ ID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", DiffIDs: []string{ "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", diff --git a/pkg/fanal/types/artifact.go b/pkg/fanal/types/artifact.go index ff8da07b4e2c..3b3d9b2472a0 100644 --- a/pkg/fanal/types/artifact.go +++ b/pkg/fanal/types/artifact.go @@ -1,17 +1,9 @@ package types import ( - "encoding/json" - "strings" "time" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/package-url/packageurl-go" "github.com/samber/lo" - "golang.org/x/xerrors" - - "github.com/aquasecurity/trivy/pkg/digest" - "github.com/aquasecurity/trivy/pkg/sbom/core" ) type OS struct { @@ -66,260 +58,6 @@ type Layer struct { CreatedBy string `json:",omitempty"` } -type Relationship int - -const ( - RelationshipUnknown Relationship = iota - RelationshipRoot - RelationshipDirect - RelationshipIndirect -) - -var relationshipNames = [...]string{ - "unknown", - "root", - "direct", - "indirect", -} - -func (r Relationship) String() string { - if r <= RelationshipUnknown || int(r) >= len(relationshipNames) { - return "unknown" - } - return relationshipNames[r] -} - -func (r Relationship) MarshalJSON() ([]byte, error) { - return json.Marshal(r.String()) -} - -func (r *Relationship) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - for i, name := range relationshipNames { - if s == name { - *r = Relationship(i) - return nil - } - } - return xerrors.Errorf("invalid relationship (%s)", s) -} - -type Package struct { - ID string `json:",omitempty"` - Name string `json:",omitempty"` - Identifier PkgIdentifier `json:",omitempty"` - Version string `json:",omitempty"` - Release string `json:",omitempty"` - Epoch int `json:",omitempty"` - Arch string `json:",omitempty"` - Dev bool `json:",omitempty"` - SrcName string `json:",omitempty"` - SrcVersion string `json:",omitempty"` - SrcRelease string `json:",omitempty"` - SrcEpoch int `json:",omitempty"` - Licenses []string `json:",omitempty"` - Maintainer string `json:",omitempty"` - ExternalReferences []ExternalRef `json:"-"` - - Modularitylabel string `json:",omitempty"` // only for Red Hat based distributions - BuildInfo *BuildInfo `json:",omitempty"` // only for Red Hat - - Indirect bool `json:",omitempty"` // Deprecated: Use relationship. Kept for backward compatibility. - Relationship Relationship `json:",omitempty"` - - // Dependencies of this package - // Note: it may have interdependencies, which may lead to infinite loops. - DependsOn []string `json:",omitempty"` - - Layer Layer `json:",omitempty"` - - // Each package metadata have the file path, while the package from lock files does not have. - FilePath string `json:",omitempty"` - - // This is required when using SPDX formats. Otherwise, it will be empty. - Digest digest.Digest `json:",omitempty"` - - // lines from the lock file where the dependency is written - Locations Locations `json:",omitempty"` - - // Files installed by the package - InstalledFiles []string `json:",omitempty"` -} - -// PkgIdentifier represents a software identifiers in one of more of the supported formats. -type PkgIdentifier struct { - UID string `json:",omitempty"` // Calculated by the package struct - PURL *packageurl.PackageURL `json:"-"` - BOMRef string `json:",omitempty"` // For CycloneDX -} - -// MarshalJSON customizes the JSON encoding of PkgIdentifier. -func (id *PkgIdentifier) MarshalJSON() ([]byte, error) { - var p string - if id.PURL != nil { - p = id.PURL.String() - } - - type Alias PkgIdentifier - return json.Marshal(&struct { - PURL string `json:",omitempty"` - *Alias - }{ - PURL: p, - Alias: (*Alias)(id), - }) -} - -// UnmarshalJSON customizes the JSON decoding of PkgIdentifier. -func (id *PkgIdentifier) UnmarshalJSON(data []byte) error { - type Alias PkgIdentifier - aux := &struct { - PURL string `json:",omitempty"` - *Alias - }{ - Alias: (*Alias)(id), - } - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - - if aux.PURL != "" { - p, err := packageurl.FromString(aux.PURL) - if err != nil { - return err - } else if len(p.Qualifiers) == 0 { - p.Qualifiers = nil - } - id.PURL = &p - } - - return nil -} - -func (id *PkgIdentifier) Empty() bool { - return id.UID == "" && id.PURL == nil && id.BOMRef == "" -} - -func (id *PkgIdentifier) Match(s string) bool { - // Encode string as PURL - if strings.HasPrefix(s, "pkg:") { - if p, err := packageurl.FromString(s); err == nil { - s = p.String() - } - } - - switch { - case id.BOMRef == s: - return true - case id.PURL != nil && id.PURL.String() == s: - return true - } - return false -} - -type Dependency struct { - ID string - DependsOn []string -} - -type Dependencies []Dependency - -func (deps Dependencies) Len() int { return len(deps) } -func (deps Dependencies) Less(i, j int) bool { - return deps[i].ID < deps[j].ID -} -func (deps Dependencies) Swap(i, j int) { deps[i], deps[j] = deps[j], deps[i] } - -type Location struct { - StartLine int `json:",omitempty"` - EndLine int `json:",omitempty"` -} - -type Locations []Location - -func (locs Locations) Len() int { return len(locs) } -func (locs Locations) Less(i, j int) bool { - return locs[i].StartLine < locs[j].StartLine -} -func (locs Locations) Swap(i, j int) { locs[i], locs[j] = locs[j], locs[i] } - -type ExternalRef struct { - Type RefType - URL string -} - -type RefType string - -const ( - RefVCS RefType = "vcs" - RefOther RefType = "other" -) - -// BuildInfo represents information under /root/buildinfo in RHEL -type BuildInfo struct { - ContentSets []string `json:",omitempty"` - Nvr string `json:",omitempty"` - Arch string `json:",omitempty"` -} - -func (pkg *Package) Empty() bool { - return pkg.Name == "" || pkg.Version == "" -} - -type Packages []Package - -func (pkgs Packages) Len() int { - return len(pkgs) -} - -func (pkgs Packages) Swap(i, j int) { - pkgs[i], pkgs[j] = pkgs[j], pkgs[i] -} - -func (pkgs Packages) Less(i, j int) bool { - switch { - case pkgs[i].Relationship != pkgs[j].Relationship: - if pkgs[i].Relationship == RelationshipUnknown { - return false - } else if pkgs[j].Relationship == RelationshipUnknown { - return true - } - return pkgs[i].Relationship < pkgs[j].Relationship - case pkgs[i].Name != pkgs[j].Name: - return pkgs[i].Name < pkgs[j].Name - case pkgs[i].Version != pkgs[j].Version: - return pkgs[i].Version < pkgs[j].Version - } - return pkgs[i].FilePath < pkgs[j].FilePath -} - -// ParentDeps returns a map where the keys are package IDs and the values are the packages -// that depend on the respective package ID (parent dependencies). -func (pkgs Packages) ParentDeps() map[string]Packages { - parents := make(map[string]Packages) - for _, pkg := range pkgs { - for _, dependOn := range pkg.DependsOn { - parents[dependOn] = append(parents[dependOn], pkg) - } - } - - for k, v := range parents { - parents[k] = lo.UniqBy(v, func(pkg Package) string { - return pkg.ID - }) - } - return parents -} - -type SrcPackage struct { - Name string `json:"name"` - Version string `json:"version"` - BinaryNames []string `json:"binaryNames"` -} - type PackageInfo struct { FilePath string Packages Packages @@ -342,39 +80,6 @@ type File struct { Content []byte } -// ArtifactType represents a type of artifact -type ArtifactType string - -const ( - ArtifactContainerImage ArtifactType = "container_image" - ArtifactFilesystem ArtifactType = "filesystem" - ArtifactRepository ArtifactType = "repository" - ArtifactCycloneDX ArtifactType = "cyclonedx" - ArtifactSPDX ArtifactType = "spdx" - ArtifactAWSAccount ArtifactType = "aws_account" - ArtifactVM ArtifactType = "vm" -) - -// ArtifactReference represents a reference of container image, local filesystem and repository -type ArtifactReference struct { - Name string // image name, tar file name, directory or repository name - Type ArtifactType - ID string - BlobIDs []string - ImageMetadata ImageMetadata - - // SBOM - BOM *core.BOM -} - -type ImageMetadata struct { - ID string // image ID - DiffIDs []string // uncompressed layer IDs - RepoTags []string - RepoDigests []string - ConfigFile v1.ConfigFile -} - // ArtifactInfo is stored in cache type ArtifactInfo struct { SchemaVersion int diff --git a/pkg/fanal/types/package.go b/pkg/fanal/types/package.go new file mode 100644 index 000000000000..0a281326b35d --- /dev/null +++ b/pkg/fanal/types/package.go @@ -0,0 +1,260 @@ +package types + +import ( + "encoding/json" + "strings" + + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/digest" +) + +type Relationship int + +const ( + RelationshipUnknown Relationship = iota + RelationshipRoot + RelationshipDirect + RelationshipIndirect +) + +var relationshipNames = [...]string{ + "unknown", + "root", + "direct", + "indirect", +} + +func (r Relationship) String() string { + if r <= RelationshipUnknown || int(r) >= len(relationshipNames) { + return "unknown" + } + return relationshipNames[r] +} + +func (r Relationship) MarshalJSON() ([]byte, error) { + return json.Marshal(r.String()) +} + +func (r *Relationship) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + for i, name := range relationshipNames { + if s == name { + *r = Relationship(i) + return nil + } + } + return xerrors.Errorf("invalid relationship (%s)", s) +} + +// PkgIdentifier represents a software identifiers in one of more of the supported formats. +type PkgIdentifier struct { + UID string `json:",omitempty"` // Calculated by the package struct + PURL *packageurl.PackageURL `json:"-"` + BOMRef string `json:",omitempty"` // For CycloneDX +} + +// MarshalJSON customizes the JSON encoding of PkgIdentifier. +func (id *PkgIdentifier) MarshalJSON() ([]byte, error) { + var p string + if id.PURL != nil { + p = id.PURL.String() + } + + type Alias PkgIdentifier + return json.Marshal(&struct { + PURL string `json:",omitempty"` + *Alias + }{ + PURL: p, + Alias: (*Alias)(id), + }) +} + +// UnmarshalJSON customizes the JSON decoding of PkgIdentifier. +func (id *PkgIdentifier) UnmarshalJSON(data []byte) error { + type Alias PkgIdentifier + aux := &struct { + PURL string `json:",omitempty"` + *Alias + }{ + Alias: (*Alias)(id), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + if aux.PURL != "" { + p, err := packageurl.FromString(aux.PURL) + if err != nil { + return err + } else if len(p.Qualifiers) == 0 { + p.Qualifiers = nil + } + id.PURL = &p + } + + return nil +} + +func (id *PkgIdentifier) Empty() bool { + return id.UID == "" && id.PURL == nil && id.BOMRef == "" +} + +func (id *PkgIdentifier) Match(s string) bool { + // Encode string as PURL + if strings.HasPrefix(s, "pkg:") { + if p, err := packageurl.FromString(s); err == nil { + s = p.String() + } + } + + switch { + case id.BOMRef == s: + return true + case id.PURL != nil && id.PURL.String() == s: + return true + } + return false +} + +type Location struct { + StartLine int `json:",omitempty"` + EndLine int `json:",omitempty"` +} + +type Locations []Location + +func (locs Locations) Len() int { return len(locs) } +func (locs Locations) Less(i, j int) bool { + return locs[i].StartLine < locs[j].StartLine +} +func (locs Locations) Swap(i, j int) { locs[i], locs[j] = locs[j], locs[i] } + +type ExternalRef struct { + Type RefType + URL string +} + +type RefType string + +const ( + RefVCS RefType = "vcs" + RefOther RefType = "other" +) + +// BuildInfo represents information under /root/buildinfo in RHEL +type BuildInfo struct { + ContentSets []string `json:",omitempty"` + Nvr string `json:",omitempty"` + Arch string `json:",omitempty"` +} + +type Package struct { + ID string `json:",omitempty"` + Name string `json:",omitempty"` + Identifier PkgIdentifier `json:",omitempty"` + Version string `json:",omitempty"` + Release string `json:",omitempty"` + Epoch int `json:",omitempty"` + Arch string `json:",omitempty"` + Dev bool `json:",omitempty"` + SrcName string `json:",omitempty"` + SrcVersion string `json:",omitempty"` + SrcRelease string `json:",omitempty"` + SrcEpoch int `json:",omitempty"` + Licenses []string `json:",omitempty"` + Maintainer string `json:",omitempty"` + ExternalReferences []ExternalRef `json:"-"` + + Modularitylabel string `json:",omitempty"` // only for Red Hat based distributions + BuildInfo *BuildInfo `json:",omitempty"` // only for Red Hat + + Indirect bool `json:",omitempty"` // Deprecated: Use relationship. Kept for backward compatibility. + Relationship Relationship `json:",omitempty"` + + // Dependencies of this package + // Note: it may have interdependencies, which may lead to infinite loops. + DependsOn []string `json:",omitempty"` + + Layer Layer `json:",omitempty"` + + // Each package metadata have the file path, while the package from lock files does not have. + FilePath string `json:",omitempty"` + + // This is required when using SPDX formats. Otherwise, it will be empty. + Digest digest.Digest `json:",omitempty"` + + // lines from the lock file where the dependency is written + Locations Locations `json:",omitempty"` + + // Files installed by the package + InstalledFiles []string `json:",omitempty"` +} + +func (pkg *Package) Empty() bool { + return pkg.Name == "" || pkg.Version == "" +} + +type Packages []Package + +func (pkgs Packages) Len() int { + return len(pkgs) +} + +func (pkgs Packages) Swap(i, j int) { + pkgs[i], pkgs[j] = pkgs[j], pkgs[i] +} + +func (pkgs Packages) Less(i, j int) bool { + switch { + case pkgs[i].Relationship != pkgs[j].Relationship: + if pkgs[i].Relationship == RelationshipUnknown { + return false + } else if pkgs[j].Relationship == RelationshipUnknown { + return true + } + return pkgs[i].Relationship < pkgs[j].Relationship + case pkgs[i].Name != pkgs[j].Name: + return pkgs[i].Name < pkgs[j].Name + case pkgs[i].Version != pkgs[j].Version: + return pkgs[i].Version < pkgs[j].Version + } + return pkgs[i].FilePath < pkgs[j].FilePath +} + +// ParentDeps returns a map where the keys are package IDs and the values are the packages +// that depend on the respective package ID (parent dependencies). +func (pkgs Packages) ParentDeps() map[string]Packages { + parents := make(map[string]Packages) + for _, pkg := range pkgs { + for _, dependOn := range pkg.DependsOn { + parents[dependOn] = append(parents[dependOn], pkg) + } + } + + for k, v := range parents { + parents[k] = lo.UniqBy(v, func(pkg Package) string { + return pkg.ID + }) + } + return parents +} + +type Dependency struct { + ID string + DependsOn []string +} + +type Dependencies []Dependency + +func (deps Dependencies) Len() int { return len(deps) } +func (deps Dependencies) Less(i, j int) bool { + return deps[i].ID < deps[j].ID +} +func (deps Dependencies) Swap(i, j int) { deps[i], deps[j] = deps[j], deps[i] } diff --git a/pkg/k8s/scanner/scanner.go b/pkg/k8s/scanner/scanner.go index 2ac380dadb58..95761ad445a8 100644 --- a/pkg/k8s/scanner/scanner.go +++ b/pkg/k8s/scanner/scanner.go @@ -379,7 +379,7 @@ func (s *Scanner) clusterInfoToReportResources(allArtifact []*artifacts.Artifact Version: comp.Version, Type: core.TypeApplication, Properties: toProperties(comp.Properties, k8sCoreComponentNamespace), - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: generatePURL(comp.Name, comp.Version, nodeName), }, } @@ -406,7 +406,7 @@ func (s *Scanner) clusterInfoToReportResources(allArtifact []*artifacts.Artifact Type: core.TypeContainerImage, Name: name, Version: cDigest, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: imagePURL.Unwrap(), }, Properties: []core.Property{ @@ -439,7 +439,7 @@ func (s *Scanner) clusterInfoToReportResources(allArtifact []*artifacts.Artifact Name: cf.Name, Version: cf.Version, Properties: toProperties(cf.Properties, k8sCoreComponentNamespace), - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: generatePURL(cf.Name, cf.Version, nodeName), }, Root: true, @@ -512,7 +512,7 @@ func (s *Scanner) nodeComponent(b *core.BOM, nf bom.NodeInfo) *core.Component { Namespace: k8sCoreComponentNamespace, }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: generatePURL(kubelet, kubeletVersion, nf.NodeName), }, } @@ -534,7 +534,7 @@ func (s *Scanner) nodeComponent(b *core.BOM, nf bom.NodeInfo) *core.Component { Namespace: k8sCoreComponentNamespace, }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: packageurl.NewPackageURL(packageurl.TypeGolang, "", runtimeName, runtimeVersion, packageurl.Qualifiers{}, ""), }, } diff --git a/pkg/k8s/scanner/scanner_test.go b/pkg/k8s/scanner/scanner_test.go index 9269f78cf11b..69429a6f4d48 100644 --- a/pkg/k8s/scanner/scanner_test.go +++ b/pkg/k8s/scanner/scanner_test.go @@ -2,6 +2,7 @@ package scanner import ( "context" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/uuid" "github.com/stretchr/testify/require" @@ -99,7 +100,7 @@ func TestScanner_Scan(t *testing.T) { Namespace: k8sCoreComponentNamespace, }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: "golang", Name: "github.com/containerd/containerd", @@ -113,7 +114,7 @@ func TestScanner_Scan(t *testing.T) { Type: core.TypeApplication, Name: "k8s.io/apiserver", Version: "1.21.1", - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: purl.TypeK8s, Name: "k8s.io/apiserver", @@ -138,7 +139,7 @@ func TestScanner_Scan(t *testing.T) { Namespace: k8sCoreComponentNamespace, }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: "k8s", Name: "k8s.io/kubelet", @@ -150,7 +151,7 @@ func TestScanner_Scan(t *testing.T) { { Type: core.TypeApplication, Name: "node-core-components", - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ BOMRef: "3ff14136-e09f-4df9-80ea-000000000006", }, }, @@ -158,7 +159,7 @@ func TestScanner_Scan(t *testing.T) { Type: core.TypeContainerImage, Name: "k8s.gcr.io/kube-apiserver", Version: "sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: "oci", Name: "kube-apiserver", @@ -199,7 +200,7 @@ func TestScanner_Scan(t *testing.T) { Namespace: "", }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ BOMRef: "3ff14136-e09f-4df9-80ea-000000000005", }, }, @@ -220,7 +221,7 @@ func TestScanner_Scan(t *testing.T) { Namespace: k8sCoreComponentNamespace, }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: purl.TypeK8s, Name: "k8s.io/kubernetes", @@ -264,7 +265,7 @@ func TestScanner_Scan(t *testing.T) { Namespace: k8sCoreComponentNamespace, }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ BOMRef: "3ff14136-e09f-4df9-80ea-000000000004", }, }, diff --git a/pkg/report/github/github.go b/pkg/report/github/github.go index 8a1ef95c74e1..6441d96630e4 100644 --- a/pkg/report/github/github.go +++ b/pkg/report/github/github.go @@ -12,6 +12,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/purl" "github.com/aquasecurity/trivy/pkg/types" @@ -105,7 +106,7 @@ func (w Writer) Write(ctx context.Context, report types.Report) error { manifest.Name = string(result.Type) // show path for language-specific packages only if result.Class == types.ClassLangPkg { - if report.ArtifactType == ftypes.ArtifactContainerImage { + if report.ArtifactType == artifact.TypeContainerImage { // `RepoDigests` ~= /@sha256: // `RepoTag` ~= /: // By concatenating the hash from `RepoDigests` at the end of `RepoTag` we get all the information diff --git a/pkg/report/predicate/vuln_test.go b/pkg/report/predicate/vuln_test.go index 8477a3971bd4..0473441e30c4 100644 --- a/pkg/report/predicate/vuln_test.go +++ b/pkg/report/predicate/vuln_test.go @@ -12,7 +12,7 @@ import ( dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" "github.com/aquasecurity/trivy/pkg/clock" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/report/predicate" "github.com/aquasecurity/trivy/pkg/types" ) @@ -49,7 +49,7 @@ func TestWriter_Write(t *testing.T) { Result: types.Report{ SchemaVersion: 2, ArtifactName: "alpine:3.14", - ArtifactType: ftypes.ArtifactType(""), + ArtifactType: artifact.Type(""), Metadata: types.Metadata{}, Results: types.Results{ { diff --git a/pkg/report/sarif.go b/pkg/report/sarif.go index 2f9dd5891516..ae84b8ff987f 100644 --- a/pkg/report/sarif.go +++ b/pkg/report/sarif.go @@ -13,6 +13,7 @@ import ( "github.com/owenrumney/go-sarif/v2/sarif" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" ) @@ -131,7 +132,7 @@ func (sw *SarifWriter) Write(ctx context.Context, report types.Report) error { sw.run.Tool.Driver.WithVersion(sw.Version) sw.run.Tool.Driver.WithFullName("Trivy Vulnerability Scanner") sw.locationCache = make(map[string][]location) - if report.ArtifactType == ftypes.ArtifactContainerImage { + if report.ArtifactType == artifact.TypeContainerImage { sw.run.Properties = sarif.Properties{ "imageName": report.ArtifactName, "repoTags": report.Metadata.RepoTags, diff --git a/pkg/report/sarif_test.go b/pkg/report/sarif_test.go index fe46514002b6..31a5f0d8a620 100644 --- a/pkg/report/sarif_test.go +++ b/pkg/report/sarif_test.go @@ -12,6 +12,7 @@ import ( dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/report" "github.com/aquasecurity/trivy/pkg/types" @@ -27,7 +28,7 @@ func TestReportWriter_Sarif(t *testing.T) { name: "report with vulnerabilities", input: types.Report{ ArtifactName: "debian:9", - ArtifactType: ftypes.ArtifactContainerImage, + ArtifactType: artifact.TypeContainerImage, Metadata: types.Metadata{ RepoTags: []string{ "debian:9", diff --git a/pkg/report/writer.go b/pkg/report/writer.go index d732ec397a1f..b4966d88a700 100644 --- a/pkg/report/writer.go +++ b/pkg/report/writer.go @@ -9,7 +9,7 @@ import ( "golang.org/x/xerrors" cr "github.com/aquasecurity/trivy/pkg/compliance/report" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/report/cyclonedx" @@ -82,7 +82,7 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e } case types.FormatSarif: target := "" - if report.ArtifactType == ftypes.ArtifactFilesystem { + if report.ArtifactType == artifact.TypeFilesystem { target = option.Target } writer = &SarifWriter{ diff --git a/pkg/sbom/core/bom.go b/pkg/sbom/core/bom.go index 893796a0fe0d..68ba8dab76d5 100644 --- a/pkg/sbom/core/bom.go +++ b/pkg/sbom/core/bom.go @@ -3,10 +3,9 @@ package core import ( "sort" - "github.com/package-url/packageurl-go" - dtypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/digest" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/uuid" ) @@ -124,14 +123,14 @@ type Component struct { // SPDX: package.licenseConcluded, package.licenseDeclared Licenses []string - // PkgID has PURL and BOMRef for the component + // PkgIdentifier has PURL and BOMRef for the component // PURL: // CycloneDX: component.purl // SPDX: package.externalRefs.referenceLocator // BOMRef: // CycloneDX: component.bom-ref // SPDX: N/A - PkgID PkgID + PkgIdentifier ftypes.PkgIdentifier // Supplier is the name of the supplier of the component // CycloneDX: component.supplier @@ -188,11 +187,6 @@ type Relationship struct { Type RelationshipType } -type PkgID struct { - PURL *packageurl.PackageURL - BOMRef string -} - type Vulnerability struct { dtypes.Vulnerability ID string @@ -222,8 +216,8 @@ func (b *BOM) setupComponent(c *Component) { if c.id == uuid.Nil { c.id = uuid.New() } - if c.PkgID.PURL != nil { - p := c.PkgID.PURL.String() + if c.PkgIdentifier.PURL != nil { + p := c.PkgIdentifier.PURL.String() b.purls[p] = append(b.purls[p], c.id) } sort.Sort(c.Properties) @@ -281,7 +275,7 @@ func (b *BOM) Root() *Component { return nil } if b.opts.GenerateBOMRef { - root.PkgID.BOMRef = b.bomRef(root) + root.PkgIdentifier.BOMRef = b.bomRef(root) } return root } @@ -290,7 +284,7 @@ func (b *BOM) Components() map[uuid.UUID]*Component { // Fill in BOMRefs for components if b.opts.GenerateBOMRef { for id, c := range b.components { - b.components[id].PkgID.BOMRef = b.bomRef(c) + b.components[id].PkgIdentifier.BOMRef = b.bomRef(c) } } return b.components @@ -312,14 +306,14 @@ func (b *BOM) NumComponents() int { // When multiple lock files have the same dependency with the same name and version, PURL in the BOM can conflict. // In that case, PURL cannot be used as a unique identifier, and UUIDv4 be used for BOMRef. func (b *BOM) bomRef(c *Component) string { - if c.PkgID.BOMRef != "" { - return c.PkgID.BOMRef + if c.PkgIdentifier.BOMRef != "" { + return c.PkgIdentifier.BOMRef } // Return the UUID of the component if the PURL is not present. - if c.PkgID.PURL == nil { + if c.PkgIdentifier.PURL == nil { return c.id.String() } - p := c.PkgID.PURL.String() + p := c.PkgIdentifier.PURL.String() // Return the UUID of the component if the PURL is not unique in the BOM. if len(b.purls[p]) > 1 { diff --git a/pkg/sbom/cyclonedx/marshal.go b/pkg/sbom/cyclonedx/marshal.go index a8d96e4b12e3..5b7241254d25 100644 --- a/pkg/sbom/cyclonedx/marshal.go +++ b/pkg/sbom/cyclonedx/marshal.go @@ -108,12 +108,12 @@ func (m *Marshaler) MarshalComponent(component *core.Component) (*cdx.Component, } cdxComponent := &cdx.Component{ - BOMRef: component.PkgID.BOMRef, + BOMRef: component.PkgIdentifier.BOMRef, Type: componentType, Name: component.Name, Group: component.Group, Version: component.Version, - PackageURL: m.PackageURL(component.PkgID.PURL), + PackageURL: m.PackageURL(component.PkgIdentifier.PURL), Supplier: m.Supplier(component.Supplier), Hashes: m.Hashes(component.Files), Licenses: m.Licenses(component.Licenses), diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go index d71c3c9ed074..d3d104b28347 100644 --- a/pkg/sbom/cyclonedx/marshal_test.go +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -2,6 +2,7 @@ package cyclonedx_test import ( "context" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/package-url/packageurl-go" "testing" @@ -29,7 +30,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Root: true, Type: core.TypeApplication, Name: "jackson-databind-2.13.4.1.jar", - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ BOMRef: "aff65b54-6009-4c32-968d-748949ef46e8", }, Properties: []core.Property{ @@ -50,7 +51,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "rails:latest", - ArtifactType: ftypes.ArtifactContainerImage, + ArtifactType: artifact.TypeContainerImage, Metadata: types.Metadata{ Size: 1024, OS: &ftypes.OS{ @@ -668,7 +669,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "centos:latest", - ArtifactType: ftypes.ArtifactContainerImage, + ArtifactType: artifact.TypeContainerImage, Metadata: types.Metadata{ Size: 1024, OS: &ftypes.OS{ @@ -1229,7 +1230,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "masahiro331/CVE-2021-41098", - ArtifactType: ftypes.ArtifactFilesystem, + ArtifactType: artifact.TypeFilesystem, Results: types.Results{ { Target: "Gemfile.lock", @@ -1445,7 +1446,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "./report.cdx.json", - ArtifactType: ftypes.ArtifactCycloneDX, + ArtifactType: artifact.TypeCycloneDX, Results: types.Results{ { Target: "Java", @@ -1629,7 +1630,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "CVE-2023-34468", - ArtifactType: ftypes.ArtifactFilesystem, + ArtifactType: artifact.TypeFilesystem, Results: types.Results{ { Target: "Java", @@ -1926,7 +1927,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "test-aggregate", - ArtifactType: ftypes.ArtifactRepository, + ArtifactType: artifact.TypeRepository, Results: types.Results{ { Target: "Node.js", @@ -2039,7 +2040,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "empty/path", - ArtifactType: ftypes.ArtifactFilesystem, + ArtifactType: artifact.TypeFilesystem, Results: types.Results{}, }, want: &cdx.BOM{ diff --git a/pkg/sbom/cyclonedx/unmarshal.go b/pkg/sbom/cyclonedx/unmarshal.go index 9450a78a455c..71a0ee27b640 100644 --- a/pkg/sbom/cyclonedx/unmarshal.go +++ b/pkg/sbom/cyclonedx/unmarshal.go @@ -12,6 +12,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/digest" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/sbom/core" ) @@ -69,7 +70,7 @@ func (b *BOM) parseBOM(bom *cdx.BOM) error { if err != nil { return xerrors.Errorf("failed to parse root component: %w", err) } else if mComponent != nil { - components[mComponent.PkgID.BOMRef] = mComponent + components[mComponent.PkgIdentifier.BOMRef] = mComponent } // Parse dependencies and build relationships @@ -147,7 +148,7 @@ func (b *BOM) parseComponent(c cdx.Component) (*core.Component, error) { Digests: b.unmarshalHashes(c.Hashes), }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &purl, BOMRef: c.BOMRef, }, diff --git a/pkg/sbom/io/decode.go b/pkg/sbom/io/decode.go index 84039973c213..e4df3bee8489 100644 --- a/pkg/sbom/io/decode.go +++ b/pkg/sbom/io/decode.go @@ -135,7 +135,7 @@ func (m *Decoder) decodeComponents(sbom *types.SBOM) error { } // Third-party SBOMs may contain packages in types other than "Library" - if c.Type == core.TypeLibrary || c.PkgID.PURL != nil { + if c.Type == core.TypeLibrary || c.PkgIdentifier.PURL != nil { pkg, err := m.decodeLibrary(c) if errors.Is(err, ErrUnsupportedType) || errors.Is(err, ErrPURLEmpty) { continue @@ -184,7 +184,7 @@ func (m *Decoder) decodeApplication(c *core.Component) *ftypes.Application { } func (m *Decoder) decodeLibrary(c *core.Component) (*ftypes.Package, error) { - p := (*purl.PackageURL)(c.PkgID.PURL) + p := (*purl.PackageURL)(c.PkgIdentifier.PURL) if p == nil { log.Debug("Skipping a component without PURL", log.String("name", c.Name), log.String("version", c.Version)) @@ -226,7 +226,7 @@ func (m *Decoder) decodeLibrary(c *core.Component) (*ftypes.Package, error) { } } - pkg.Identifier.BOMRef = c.PkgID.BOMRef + pkg.Identifier.BOMRef = c.PkgIdentifier.BOMRef pkg.Licenses = c.Licenses for _, f := range c.Files { @@ -249,10 +249,10 @@ func (m *Decoder) decodeLibrary(c *core.Component) (*ftypes.Package, error) { // pkgName returns the package name. // PURL loses case-sensitivity (e.g. Go, Npm, PyPI), so we have to use an original package name. func (m *Decoder) pkgName(pkg *ftypes.Package, c *core.Component) string { - p := c.PkgID.PURL + p := c.PkgIdentifier.PURL // A name from PURL takes precedence for CocoaPods since it has subpath. - if c.PkgID.PURL.Type == packageurl.TypeCocoapods { + if c.PkgIdentifier.PURL.Type == packageurl.TypeCocoapods { return pkg.Name } diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index b6d1301d6f6e..4b715b023a08 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -10,6 +10,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/purl" "github.com/aquasecurity/trivy/pkg/sbom/core" @@ -61,7 +62,7 @@ func (e *Encoder) rootComponent(r types.Report) (*core.Component, error) { } switch r.ArtifactType { - case ftypes.ArtifactContainerImage: + case artifact.TypeContainerImage: root.Type = core.TypeContainerImage props = append(props, core.Property{ Name: core.PropertyImageID, @@ -73,16 +74,16 @@ func (e *Encoder) rootComponent(r types.Report) (*core.Component, error) { return nil, xerrors.Errorf("failed to new package url for oci: %w", err) } if p != nil { - root.PkgID.PURL = p.Unwrap() + root.PkgIdentifier.PURL = p.Unwrap() } - case ftypes.ArtifactVM: + case artifact.TypeVM: root.Type = core.TypeVM - case ftypes.ArtifactFilesystem: + case artifact.TypeFilesystem: root.Type = core.TypeFilesystem - case ftypes.ArtifactRepository: + case artifact.TypeRepository: root.Type = core.TypeRepository - case ftypes.ArtifactCycloneDX: + case artifact.TypeCycloneDX: return r.BOM.Root(), nil } @@ -346,7 +347,7 @@ func (*Encoder) component(result types.Result, pkg ftypes.Package) *core.Compone SrcName: pkg.SrcName, SrcVersion: utils.FormatSrcVersion(pkg), SrcFile: srcFile, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: pkg.Identifier.PURL, }, Supplier: pkg.Maintainer, diff --git a/pkg/sbom/io/encode_test.go b/pkg/sbom/io/encode_test.go index d45f70112592..e26dd83848e8 100644 --- a/pkg/sbom/io/encode_test.go +++ b/pkg/sbom/io/encode_test.go @@ -2,6 +2,7 @@ package io_test import ( dtypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/sbom/core" sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" @@ -27,7 +28,7 @@ func TestEncoder_Encode(t *testing.T) { report: types.Report{ SchemaVersion: 2, ArtifactName: "debian:12", - ArtifactType: ftypes.ArtifactContainerImage, + ArtifactType: artifact.TypeContainerImage, Metadata: types.Metadata{ OS: &ftypes.OS{ Family: ftypes.Debian, @@ -116,7 +117,7 @@ func TestEncoder_Encode(t *testing.T) { Type: core.TypeContainerImage, Name: "debian:12", Root: true, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: packageurl.TypeOCI, Name: "debian", @@ -163,7 +164,7 @@ func TestEncoder_Encode(t *testing.T) { Value: "debian", }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", }, }, @@ -181,7 +182,7 @@ func TestEncoder_Encode(t *testing.T) { Value: "debian", }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Name: "libc6", @@ -204,7 +205,7 @@ func TestEncoder_Encode(t *testing.T) { Value: "debian", }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Name: "curl", @@ -237,7 +238,7 @@ func TestEncoder_Encode(t *testing.T) { Value: "jar", }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "org.apache.xmlgraphics", @@ -298,7 +299,7 @@ func TestEncoder_Encode(t *testing.T) { report: types.Report{ SchemaVersion: 2, ArtifactName: "gobinary", - ArtifactType: ftypes.ArtifactFilesystem, + ArtifactType: artifact.TypeFilesystem, Results: []types.Result{ { Target: "test", @@ -379,7 +380,7 @@ func TestEncoder_Encode(t *testing.T) { Value: "2", }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", }, }, @@ -396,7 +397,7 @@ func TestEncoder_Encode(t *testing.T) { Value: "gobinary", }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", }, }, @@ -414,7 +415,7 @@ func TestEncoder_Encode(t *testing.T) { Value: "gobinary", }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "github.com/org", @@ -438,7 +439,7 @@ func TestEncoder_Encode(t *testing.T) { Value: "gobinary", }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "github.com/org", @@ -463,7 +464,7 @@ func TestEncoder_Encode(t *testing.T) { Value: "gobinary", }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "github.com/org", @@ -488,7 +489,7 @@ func TestEncoder_Encode(t *testing.T) { Value: "gobinary", }, }, - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Name: "stdlib", @@ -537,7 +538,7 @@ func TestEncoder_Encode(t *testing.T) { report: types.Report{ SchemaVersion: 2, ArtifactName: "debian:12", - ArtifactType: ftypes.ArtifactContainerImage, + ArtifactType: artifact.TypeContainerImage, Metadata: types.Metadata{ OS: &ftypes.OS{ Family: ftypes.Debian, diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index 3f72c6d69a20..2b29c7483024 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -229,8 +229,8 @@ func (m *Marshaler) packageDownloadLocation(root *core.Component) string { func (m *Marshaler) rootSPDXPackage(root *core.Component, pkgDownloadLocation string) (*spdx.Package, error) { var externalReferences []*spdx.PackageExternalReference // When the target is a container image, add PURL to the external references of the root package. - if root.PkgID.PURL != nil { - externalReferences = append(externalReferences, m.purlExternalReference(root.PkgID.PURL.String())) + if root.PkgIdentifier.PURL != nil { + externalReferences = append(externalReferences, m.purlExternalReference(root.PkgIdentifier.PURL.String())) } pkgID, err := calcPkgID(m.hasher, fmt.Sprintf("%s-%s", root.Name, root.Type)) @@ -304,8 +304,8 @@ func (m *Marshaler) spdxPackage(c *core.Component, pkgDownloadLocation string) ( } var pkgExtRefs []*spdx.PackageExternalReference - if c.PkgID.PURL != nil { - pkgExtRefs = []*spdx.PackageExternalReference{m.purlExternalReference(c.PkgID.PURL.String())} + if c.PkgIdentifier.PURL != nil { + pkgExtRefs = []*spdx.PackageExternalReference{m.purlExternalReference(c.PkgIdentifier.PURL.String())} } var digests []digest.Digest @@ -338,7 +338,7 @@ func (m *Marshaler) spdxPackage(c *core.Component, pkgDownloadLocation string) ( } func spdxPkgName(component *core.Component) string { - if p := component.PkgID.PURL; p != nil && component.Group != "" { + if p := component.PkgIdentifier.PURL; p != nil && component.Group != "" { if p.Type == packageurl.TypeMaven || p.Type == packageurl.TypeGradle { return component.Group + ":" + component.Name } diff --git a/pkg/sbom/spdx/marshal_test.go b/pkg/sbom/spdx/marshal_test.go index c4f2f7694d62..ee4c10949148 100644 --- a/pkg/sbom/spdx/marshal_test.go +++ b/pkg/sbom/spdx/marshal_test.go @@ -2,6 +2,7 @@ package spdx_test import ( "context" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/package-url/packageurl-go" "hash/fnv" @@ -34,7 +35,7 @@ func TestMarshaler_Marshal(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "rails:latest", - ArtifactType: ftypes.ArtifactContainerImage, + ArtifactType: artifact.TypeContainerImage, Metadata: types.Metadata{ Size: 1024, OS: &ftypes.OS{ @@ -358,7 +359,7 @@ func TestMarshaler_Marshal(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "centos:latest", - ArtifactType: ftypes.ArtifactContainerImage, + ArtifactType: artifact.TypeContainerImage, Metadata: types.Metadata{ Size: 1024, OS: &ftypes.OS{ @@ -649,7 +650,7 @@ func TestMarshaler_Marshal(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "masahiro331/CVE-2021-41098", - ArtifactType: ftypes.ArtifactFilesystem, + ArtifactType: artifact.TypeFilesystem, Results: types.Results{ { Target: "Gemfile.lock", @@ -818,7 +819,7 @@ func TestMarshaler_Marshal(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "http://test-aggregate", - ArtifactType: ftypes.ArtifactRepository, + ArtifactType: artifact.TypeRepository, Results: types.Results{ { Target: "Node.js", @@ -937,7 +938,7 @@ func TestMarshaler_Marshal(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "empty/path", - ArtifactType: ftypes.ArtifactFilesystem, + ArtifactType: artifact.TypeFilesystem, Results: types.Results{}, }, wantSBOM: &spdx.Document{ @@ -985,7 +986,7 @@ func TestMarshaler_Marshal(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "secret", - ArtifactType: ftypes.ArtifactFilesystem, + ArtifactType: artifact.TypeFilesystem, Results: types.Results{ { Target: "key.pem", @@ -1047,7 +1048,7 @@ func TestMarshaler_Marshal(t *testing.T) { inputReport: types.Report{ SchemaVersion: report.SchemaVersion, ArtifactName: "go-artifact", - ArtifactType: ftypes.ArtifactFilesystem, + ArtifactType: artifact.TypeFilesystem, Results: types.Results{ { Target: "/usr/local/bin/test", diff --git a/pkg/sbom/spdx/unmarshal.go b/pkg/sbom/spdx/unmarshal.go index bda18c16980a..719cdad15c3d 100644 --- a/pkg/sbom/spdx/unmarshal.go +++ b/pkg/sbom/spdx/unmarshal.go @@ -167,7 +167,7 @@ func (s *SPDX) parsePackage(spdxPkg spdx.Package) (*core.Component, error) { } // PURL - if component.PkgID.PURL, err = s.parseExternalReferences(spdxPkg.PackageExternalReferences); err != nil { + if component.PkgIdentifier.PURL, err = s.parseExternalReferences(spdxPkg.PackageExternalReferences); err != nil { return nil, xerrors.Errorf("external references error: %w", err) } diff --git a/pkg/scanner/scan.go b/pkg/scanner/scan.go index c5451167c709..7094e38c71fe 100644 --- a/pkg/scanner/scan.go +++ b/pkg/scanner/scan.go @@ -169,7 +169,7 @@ func (s Scanner) ScanArtifact(ctx context.Context, options types.ScanOptions) (t } // Layer makes sense only when scanning container images - if artifactInfo.Type != ftypes.ArtifactContainerImage { + if artifactInfo.Type != artifact.TypeContainerImage { removeLayer(results) } diff --git a/pkg/scanner/scan_test.go b/pkg/scanner/scan_test.go index 78606299303b..bc6a0d6696e2 100644 --- a/pkg/scanner/scan_test.go +++ b/pkg/scanner/scan_test.go @@ -37,12 +37,12 @@ func TestScanner_ScanArtifact(t *testing.T) { CtxAnything: true, }, Returns: artifact.ArtifactInspectReturns{ - Reference: ftypes.ArtifactReference{ + Reference: artifact.Reference{ Name: "alpine:3.11", - Type: ftypes.ArtifactContainerImage, + Type: artifact.TypeContainerImage, ID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - ImageMetadata: ftypes.ImageMetadata{ + ImageMetadata: artifact.ImageMetadata{ ID: "sha256:e389ae58922402a7ded319e79f06ac428d05698d8e61ecbe88d2cf850e42651d", DiffIDs: []string{"sha256:9a5d14f9f5503e55088666beef7e85a8d9625d4fa7418e2fe269e9c54bcb853c"}, RepoTags: []string{"alpine:3.11"}, @@ -100,7 +100,7 @@ func TestScanner_ScanArtifact(t *testing.T) { SchemaVersion: 2, CreatedAt: time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC), ArtifactName: "alpine:3.11", - ArtifactType: ftypes.ArtifactContainerImage, + ArtifactType: artifact.TypeContainerImage, Metadata: types.Metadata{ OS: &ftypes.OS{ Family: "alpine", @@ -168,7 +168,7 @@ func TestScanner_ScanArtifact(t *testing.T) { CtxAnything: true, }, Returns: artifact.ArtifactInspectReturns{ - Reference: ftypes.ArtifactReference{ + Reference: artifact.Reference{ Name: "alpine:3.11", ID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, diff --git a/pkg/types/report.go b/pkg/types/report.go index e6c96d9564eb..baaeaab0a0c3 100644 --- a/pkg/types/report.go +++ b/pkg/types/report.go @@ -5,18 +5,19 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" // nolint: goimports + "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/sbom/core" ) // Report represents a scan result type Report struct { - SchemaVersion int `json:",omitempty"` - CreatedAt time.Time `json:",omitempty"` - ArtifactName string `json:",omitempty"` - ArtifactType ftypes.ArtifactType `json:",omitempty"` - Metadata Metadata `json:",omitempty"` - Results Results `json:",omitempty"` + SchemaVersion int `json:",omitempty"` + CreatedAt time.Time `json:",omitempty"` + ArtifactName string `json:",omitempty"` + ArtifactType artifact.Type `json:",omitempty"` + Metadata Metadata `json:",omitempty"` + Results Results `json:",omitempty"` // parsed SBOM BOM *core.BOM `json:"-"` // Just for internal usage, not exported in JSON diff --git a/pkg/vex/openvex.go b/pkg/vex/openvex.go index a6cae6de7ac8..ce049777e8cc 100644 --- a/pkg/vex/openvex.go +++ b/pkg/vex/openvex.go @@ -44,8 +44,8 @@ func (v *OpenVEX) Filter(result *types.Result, bom *core.BOM) { func (v *OpenVEX) Matches(vuln types.DetectedVulnerability, bom *core.BOM) []openvex.Statement { root := bom.Root() - if root != nil && root.PkgID.PURL != nil { - stmts := v.vex.Matches(vuln.VulnerabilityID, root.PkgID.PURL.String(), []string{vuln.PkgIdentifier.PURL.String()}) + if root != nil && root.PkgIdentifier.PURL != nil { + stmts := v.vex.Matches(vuln.VulnerabilityID, root.PkgIdentifier.PURL.String(), []string{vuln.PkgIdentifier.PURL.String()}) if len(stmts) != 0 { return stmts } diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index 0e47bf03bf52..64aa651d640c 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -11,7 +11,7 @@ import ( "github.com/sirupsen/logrus" "golang.org/x/xerrors" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/sbom" "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" @@ -68,7 +68,7 @@ func decodeCycloneDXJSON(r io.ReadSeeker, report types.Report) (VEX, error) { if err != nil { return nil, xerrors.Errorf("json decode error: %w", err) } - if report.ArtifactType != ftypes.ArtifactCycloneDX { + if report.ArtifactType != artifact.TypeCycloneDX { return nil, xerrors.New("CycloneDX VEX can be used with CycloneDX SBOM") } return newCycloneDX(report.BOM, vex), nil diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go index 77d2aff3c63e..ad385b63596e 100644 --- a/pkg/vex/vex_test.go +++ b/pkg/vex/vex_test.go @@ -1,6 +1,7 @@ package vex_test import ( + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/sbom/core" "os" "testing" @@ -135,7 +136,7 @@ func TestVEX_Filter(t *testing.T) { fields: fields{ filePath: "testdata/cyclonedx.json", report: types.Report{ - ArtifactType: ftypes.ArtifactCycloneDX, + ArtifactType: artifact.TypeCycloneDX, BOM: &core.BOM{ SerialNumber: "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", Version: 1, @@ -218,7 +219,7 @@ func TestVEX_Filter(t *testing.T) { fields: fields{ filePath: "testdata/cyclonedx.json", report: types.Report{ - ArtifactType: ftypes.ArtifactCycloneDX, + ArtifactType: artifact.TypeCycloneDX, BOM: &core.BOM{ SerialNumber: "urn:uuid:wrong", Version: 1, @@ -378,7 +379,7 @@ func newTestBOM() *core.BOM { Root: true, Type: core.TypeContainerImage, Name: "debian:12", - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: packageurl.TypeOCI, Name: "debian", @@ -405,7 +406,7 @@ func newTestBOM2() *core.BOM { Root: true, Type: core.TypeContainerImage, Name: "ubuntu:24.04", - PkgID: core.PkgID{ + PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ Type: packageurl.TypeOCI, Name: "ubuntu", From a126e1075a44ef0e40c0dc1e214d1c5955f80242 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Sat, 11 May 2024 06:01:40 +0600 Subject: [PATCH 058/352] fix(misconf): skip Rego errors with a nil location (#6666) --- pkg/iac/rego/load.go | 5 ++++- pkg/iac/rego/load_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/pkg/iac/rego/load.go b/pkg/iac/rego/load.go index 284fd2f653a2..2fd3955ce38f 100644 --- a/pkg/iac/rego/load.go +++ b/pkg/iac/rego/load.go @@ -184,7 +184,7 @@ func (s *Scanner) fallbackChecks(compiler *ast.Compiler) { } compiler.Errors = lo.Filter(compiler.Errors, func(e *ast.Error, _ int) bool { - return !lo.Contains(excludedFiles, e.Location.File) + return e.Location == nil || !lo.Contains(excludedFiles, e.Location.File) }) } @@ -219,6 +219,9 @@ func (s *Scanner) prunePoliciesWithError(compiler *ast.Compiler) error { } for _, e := range compiler.Errors { + if e.Location == nil { + continue + } s.debug.Log("Error occurred while parsing: %s, %s", e.Location.File, e.Error()) delete(s.policies, e.Location.File) } diff --git a/pkg/iac/rego/load_test.go b/pkg/iac/rego/load_test.go index 984fec9c4caf..1658fdefc467 100644 --- a/pkg/iac/rego/load_test.go +++ b/pkg/iac/rego/load_test.go @@ -3,12 +3,14 @@ package rego_test import ( "bytes" "embed" + "fmt" "io" "strings" "testing" "testing/fstest" checks "github.com/aquasecurity/trivy-checks" + "github.com/open-policy-agent/opa/ast" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -208,3 +210,40 @@ deny { }) } } + +func Test_FallbackErrorWithoutLocation(t *testing.T) { + fsys := fstest.MapFS{ + "schemas/fooschema.json": { + Data: []byte(`{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + }`), + }, + } + + for i := 0; i < ast.CompileErrorLimitDefault+1; i++ { + src := `# METADATA +# schemas: +# - input: schema["fooschema"] +package builtin.test%d + +deny { + input.evil == "foo bar" +}` + fsys[fmt.Sprintf("policies/my-check%d.rego", i)] = &fstest.MapFile{ + Data: []byte(fmt.Sprintf(src, i)), + } + } + + scanner := rego.NewScanner( + types.SourceDockerfile, + options.ScannerWithEmbeddedPolicies(false), + ) + err := scanner.LoadPolicies(false, false, fsys, []string{"."}, nil) + assert.Error(t, err) +} From 5caf4377f3a7fcb1f6e1a84c67136ae62d100be3 Mon Sep 17 00:00:00 2001 From: guangwu Date: Mon, 13 May 2024 10:45:19 +0800 Subject: [PATCH 059/352] fix: close APKINDEX archive file (#6672) Signed-off-by: guoguangwu --- pkg/fanal/analyzer/imgconf/apk/apk.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/fanal/analyzer/imgconf/apk/apk.go b/pkg/fanal/analyzer/imgconf/apk/apk.go index 43f6cc278023..430dc766d161 100644 --- a/pkg/fanal/analyzer/imgconf/apk/apk.go +++ b/pkg/fanal/analyzer/imgconf/apk/apk.go @@ -103,6 +103,7 @@ func (a alpineCmdAnalyzer) fetchApkIndexArchive(targetOS types.OS) (*apkIndex, e if err != nil { return nil, xerrors.Errorf("failed to read APKINDEX archive file: %w", err) } + defer reader.(*builtinos.File).Close() } else { // nolint resp, err := http.Get(url) From 787b466e069e2d04e73b3eddbda621e5eec8543b Mon Sep 17 00:00:00 2001 From: Anais Urlichs <33576047+AnaisUrlichs@users.noreply.github.com> Date: Mon, 13 May 2024 03:53:00 +0100 Subject: [PATCH 060/352] docs: add support table for client server mode (#6498) Signed-off-by: AnaisUrlichs --- docs/docs/references/modes/client-server.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/docs/references/modes/client-server.md b/docs/docs/references/modes/client-server.md index 4e812bd48ab3..518fe45efc8a 100644 --- a/docs/docs/references/modes/client-server.md +++ b/docs/docs/references/modes/client-server.md @@ -2,6 +2,10 @@ Trivy has client/server mode. Trivy server has vulnerability database and Trivy client doesn't have to download vulnerability database. It is useful if you want to scan images or files at multiple locations and do not want to download the database at every location. +| Client/Server Mode | Image | Rootfs | Filesystem | Repository | Config | AWS | K8s | +|:---------------------:|:-----:|:------:|:----------:|:----------:|:------:|:---:|:---:| +| Supported | ✅ | ✅ | ✅ | ✅ | ✅ | X | X | + ## Server At first, you need to launch Trivy server. It downloads vulnerability database automatically and continue to fetch the latest DB in the background. ``` From 150a77313e980cd63797a89a03afcbc97b285f38 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 13 May 2024 18:10:42 +0600 Subject: [PATCH 061/352] fix(conda): add support `pip` deps for `environment.yml` files (#6675) --- .../parser/conda/environment/parse.go | 58 +++++++++++++++---- .../parser/conda/environment/parse_test.go | 39 ++++++++++++- .../conda/environment/testdata/happy.yaml | 3 + .../environment/testdata/wrong-deps-type.yaml | 7 +++ .../testdata/wrong-nested-dep-type.yaml | 9 +++ .../testdata/wrong-nested-type.yaml | 7 +++ 6 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 pkg/dependency/parser/conda/environment/testdata/wrong-deps-type.yaml create mode 100644 pkg/dependency/parser/conda/environment/testdata/wrong-nested-dep-type.yaml create mode 100644 pkg/dependency/parser/conda/environment/testdata/wrong-nested-type.yaml diff --git a/pkg/dependency/parser/conda/environment/parse.go b/pkg/dependency/parser/conda/environment/parse.go index f8bdcfb49a92..be04d828b40a 100644 --- a/pkg/dependency/parser/conda/environment/parse.go +++ b/pkg/dependency/parser/conda/environment/parse.go @@ -15,7 +15,11 @@ import ( ) type environment struct { - Dependencies []Dependency `yaml:"dependencies"` + Entries []Entry `yaml:"dependencies"` +} + +type Entry struct { + Dependencies []Dependency } type Dependency struct { @@ -42,13 +46,15 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc } var pkgs ftypes.Packages - for _, dep := range env.Dependencies { - pkg := p.toPackage(dep) - // Skip empty pkgs - if pkg.Name == "" { - continue + for _, entry := range env.Entries { + for _, dep := range entry.Dependencies { + pkg := p.toPackage(dep) + // Skip empty pkgs + if pkg.Name == "" { + continue + } + pkgs = append(pkgs, pkg) } - pkgs = append(pkgs, pkg) } sort.Sort(pkgs) @@ -96,8 +102,40 @@ func (*Parser) parseDependency(line string) (string, string) { return name, parts[1] } -func (d *Dependency) UnmarshalYAML(node *yaml.Node) error { - d.Value = node.Value - d.Line = node.Line +func (e *Entry) UnmarshalYAML(node *yaml.Node) error { + var dependencies []Dependency + // cf. https://github.com/go-yaml/yaml/blob/f6f7691b1fdeb513f56608cd2c32c51f8194bf51/resolve.go#L70-L81 + switch node.Tag { + case "!!str": + dependencies = append(dependencies, Dependency{ + Value: node.Value, + Line: node.Line, + }) + case "!!map": + if node.Content != nil { + // Map key is package manager (e.g. pip). So we need to store only map values (dependencies). + // e.g. dependencies: + // - pip: + // - pandas==2.1.4 + if node.Content[1].Tag != "!!seq" { // Conda supports only map[string][]string format. + return xerrors.Errorf("unsupported dependency type %q on line %d", node.Content[1].Tag, node.Content[1].Line) + } + + for _, depContent := range node.Content[1].Content { + if depContent.Tag != "!!str" { + return xerrors.Errorf("unsupported dependency type %q on line %d", depContent.Tag, depContent.Line) + } + + dependencies = append(dependencies, Dependency{ + Value: depContent.Value, + Line: depContent.Line, + }) + } + } + default: + return xerrors.Errorf("unsupported dependency type %q on line %d", node.Tag, node.Line) + } + + e.Dependencies = dependencies return nil } diff --git a/pkg/dependency/parser/conda/environment/parse_test.go b/pkg/dependency/parser/conda/environment/parse_test.go index 109f53a405bd..6127054b2552 100644 --- a/pkg/dependency/parser/conda/environment/parse_test.go +++ b/pkg/dependency/parser/conda/environment/parse_test.go @@ -31,6 +31,16 @@ func TestParse(t *testing.T) { }, }, }, + { + Name: "asgiref", + Version: "3.8.1", + Locations: ftypes.Locations{ + { + StartLine: 21, + EndLine: 21, + }, + }, + }, { Name: "blas", Version: "1.0", @@ -61,6 +71,16 @@ func TestParse(t *testing.T) { }, }, }, + { + Name: "django", + Version: "5.0.6", + Locations: ftypes.Locations{ + { + StartLine: 22, + EndLine: 22, + }, + }, + }, { Name: "ld_impl_linux-aarch64", Locations: ftypes.Locations{ @@ -167,9 +187,24 @@ func TestParse(t *testing.T) { }, }, { - name: "invalid_json", + name: "invalid yaml file", input: "testdata/invalid.yaml", - wantErr: "unable to decode conda environment.yml file", + wantErr: "cannot unmarshal !!str `invalid` into environment.environment", + }, + { + name: "`dependency` field uses unsupported type", + input: "testdata/wrong-deps-type.yaml", + wantErr: `unsupported dependency type "!!int" on line 5`, + }, + { + name: "nested field uses unsupported type", + input: "testdata/wrong-nested-type.yaml", + wantErr: `unsupported dependency type "!!str" on line 5`, + }, + { + name: "nested dependency uses unsupported type", + input: "testdata/wrong-nested-dep-type.yaml", + wantErr: `unsupported dependency type "!!map" on line 6`, }, } for _, tt := range tests { diff --git a/pkg/dependency/parser/conda/environment/testdata/happy.yaml b/pkg/dependency/parser/conda/environment/testdata/happy.yaml index f36e8bf990eb..29ba800e5bb6 100644 --- a/pkg/dependency/parser/conda/environment/testdata/happy.yaml +++ b/pkg/dependency/parser/conda/environment/testdata/happy.yaml @@ -17,5 +17,8 @@ dependencies: - liblapack=3.9.*=22_linuxaarch64_openblas - libnsl=2.0.1=h31becfc_0 - bzip2=1.0.8=h998d150_5 + - pip: + - asgiref==3.8.1 + - django==5.0.6 prefix: /opt/conda/envs/test-env diff --git a/pkg/dependency/parser/conda/environment/testdata/wrong-deps-type.yaml b/pkg/dependency/parser/conda/environment/testdata/wrong-deps-type.yaml new file mode 100644 index 000000000000..e7257be4abe5 --- /dev/null +++ b/pkg/dependency/parser/conda/environment/testdata/wrong-deps-type.yaml @@ -0,0 +1,7 @@ +name: test-env +channels: + - defaults +dependencies: + - 1 + +prefix: /opt/conda/envs/test-env diff --git a/pkg/dependency/parser/conda/environment/testdata/wrong-nested-dep-type.yaml b/pkg/dependency/parser/conda/environment/testdata/wrong-nested-dep-type.yaml new file mode 100644 index 000000000000..0c032b8a6d40 --- /dev/null +++ b/pkg/dependency/parser/conda/environment/testdata/wrong-nested-dep-type.yaml @@ -0,0 +1,9 @@ +name: test-env +channels: + - defaults +dependencies: + - pip: + - wrongType: + - asgiref==3.8.1 + +prefix: /opt/conda/envs/test-env diff --git a/pkg/dependency/parser/conda/environment/testdata/wrong-nested-type.yaml b/pkg/dependency/parser/conda/environment/testdata/wrong-nested-type.yaml new file mode 100644 index 000000000000..2e8c01cad787 --- /dev/null +++ b/pkg/dependency/parser/conda/environment/testdata/wrong-nested-type.yaml @@ -0,0 +1,7 @@ +name: test-env +channels: + - defaults +dependencies: + - pip: asgiref==3.8.1 + +prefix: /opt/conda/envs/test-env From 26faf8f3f04b1c5f9f81c03ffc6b2008732207e2 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Tue, 14 May 2024 12:29:20 +0400 Subject: [PATCH 062/352] feat: add support for plugin index (#6674) Signed-off-by: knqyf263 Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> --- .github/workflows/semantic-pr.yaml | 1 + cmd/trivy/main.go | 5 +- docs/community/contribute/pr.md | 1 + docs/docs/advanced/plugins.md | 236 ------------ docs/docs/configuration/reporting.md | 2 +- docs/docs/plugin/developer-guide.md | 203 ++++++++++ docs/docs/plugin/index.md | 70 ++++ docs/docs/plugin/user-guide.md | 207 ++++++++++ .../configuration/cli/trivy_plugin.md | 4 +- .../configuration/cli/trivy_plugin_install.md | 2 +- .../configuration/cli/trivy_plugin_run.md | 2 +- .../configuration/cli/trivy_plugin_search.md | 31 ++ .../configuration/cli/trivy_plugin_update.md | 4 +- .../configuration/cli/trivy_plugin_upgrade.md | 31 ++ mkdocs.yml | 29 +- pkg/clock/clock.go | 7 +- pkg/commands/app.go | 91 +++-- pkg/flag/options.go | 2 +- pkg/log/handler.go | 7 + pkg/plugin/index.go | 117 ++++++ pkg/plugin/index_test.go | 87 +++++ pkg/plugin/manager.go | 355 ++++++++++++++++++ .../{plugin_test.go => manager_test.go} | 315 +++++++++------- pkg/plugin/plugin.go | 306 +++------------ pkg/plugin/testdata/plugin/index.yaml | 15 + pkg/plugin/testdata/test_plugin/plugin.yaml | 2 +- pkg/utils/fsutils/fs.go | 9 + 27 files changed, 1444 insertions(+), 697 deletions(-) delete mode 100644 docs/docs/advanced/plugins.md create mode 100644 docs/docs/plugin/developer-guide.md create mode 100644 docs/docs/plugin/index.md create mode 100644 docs/docs/plugin/user-guide.md create mode 100644 docs/docs/references/configuration/cli/trivy_plugin_search.md create mode 100644 docs/docs/references/configuration/cli/trivy_plugin_upgrade.md create mode 100644 pkg/plugin/index.go create mode 100644 pkg/plugin/index_test.go create mode 100644 pkg/plugin/manager.go rename pkg/plugin/{plugin_test.go => manager_test.go} (56%) create mode 100644 pkg/plugin/testdata/plugin/index.yaml diff --git a/.github/workflows/semantic-pr.yaml b/.github/workflows/semantic-pr.yaml index f02ef758ae91..ead9c4cccdd8 100644 --- a/.github/workflows/semantic-pr.yaml +++ b/.github/workflows/semantic-pr.yaml @@ -44,6 +44,7 @@ jobs: k8s aws vm + plugin alpine wolfi diff --git a/cmd/trivy/main.go b/cmd/trivy/main.go index dbff5fa54ab1..9e4fe4f5d639 100644 --- a/cmd/trivy/main.go +++ b/cmd/trivy/main.go @@ -28,10 +28,7 @@ func main() { func run() error { // Trivy behaves as the specified plugin. if runAsPlugin := os.Getenv("TRIVY_RUN_AS_PLUGIN"); runAsPlugin != "" { - if !plugin.IsPredefined(runAsPlugin) { - return xerrors.Errorf("unknown plugin: %s", runAsPlugin) - } - if err := plugin.RunWithURL(context.Background(), runAsPlugin, plugin.RunOptions{Args: os.Args[1:]}); err != nil { + if err := plugin.RunWithURL(context.Background(), runAsPlugin, plugin.Options{Args: os.Args[1:]}); err != nil { return xerrors.Errorf("plugin error: %w", err) } return nil diff --git a/docs/community/contribute/pr.md b/docs/community/contribute/pr.md index 2538cce3327f..072d7358b8c9 100644 --- a/docs/community/contribute/pr.md +++ b/docs/community/contribute/pr.md @@ -114,6 +114,7 @@ mode: - server - aws - vm +- plugin os: diff --git a/docs/docs/advanced/plugins.md b/docs/docs/advanced/plugins.md deleted file mode 100644 index dfdfb31d8c0d..000000000000 --- a/docs/docs/advanced/plugins.md +++ /dev/null @@ -1,236 +0,0 @@ -# Plugins -Trivy provides a plugin feature to allow others to extend the Trivy CLI without the need to change the Trivycode base. -This plugin system was inspired by the plugin system used in [kubectl][kubectl], [Helm][helm], and [Conftest][conftest]. - -## Overview -Trivy plugins are add-on tools that integrate seamlessly with Trivy. -They provide a way to extend the core feature set of Trivy, but without requiring every new feature to be written in Go and added to the core tool. - -- They can be added and removed from a Trivy installation without impacting the core Trivy tool. -- They can be written in any programming language. -- They integrate with Trivy, and will show up in Trivy help and subcommands. - -!!! warning - Trivy plugins available in public are not audited for security. - You should install and run third-party plugins at your own risk, since they are arbitrary programs running on your machine. - - -## Installing a Plugin -A plugin can be installed using the `trivy plugin install` command. -This command takes a url and will download the plugin and install it in the plugin cache. - -Trivy adheres to the XDG specification, so the location depends on whether XDG_DATA_HOME is set. -Trivy will now search XDG_DATA_HOME for the location of the Trivy plugins cache. -The preference order is as follows: - -- XDG_DATA_HOME if set and .trivy/plugins exists within the XDG_DATA_HOME dir -- ~/.trivy/plugins - -Under the hood Trivy leverages [go-getter][go-getter] to download plugins. -This means the following protocols are supported for downloading plugins: - -- OCI Registries -- Local Files -- Git -- HTTP/HTTPS -- Mercurial -- Amazon S3 -- Google Cloud Storage - -For example, to download the Kubernetes Trivy plugin you can execute the following command: - -```bash -$ trivy plugin install github.com/aquasecurity/trivy-plugin-kubectl -``` -Also, Trivy plugin can be installed from a local archive: -```bash -$ trivy plugin install myplugin.tar.gz -``` - -## Using Plugins -Once the plugin is installed, Trivy will load all available plugins in the cache on the start of the next Trivy execution. -A plugin will be made in the Trivy CLI based on the plugin name. -To display all plugins, you can list them by `trivy --help` - -```bash -$ trivy --help -NAME: - trivy - A simple and comprehensive vulnerability scanner for containers - -USAGE: - trivy [global options] command [command options] target - -VERSION: - dev - -COMMANDS: - image, i scan an image - filesystem, fs scan local filesystem - repository, repo scan remote repository - client, c client mode - server, s server mode - plugin, p manage plugins - kubectl scan kubectl resources - help, h Shows a list of commands or help for one command -``` - -As shown above, `kubectl` subcommand exists in the `COMMANDS` section. -To call the kubectl plugin and scan existing Kubernetes deployments, you can execute the following command: - -``` -$ trivy kubectl deployment -- --ignore-unfixed --severity CRITICAL -``` - -Internally the kubectl plugin calls the kubectl binary to fetch information about that deployment and passes the using images to Trivy. -You can see the detail [here][trivy-plugin-kubectl]. - -If you want to omit even the subcommand, you can use `TRIVY_RUN_AS_PLUGIN` environment variable. - -```bash -$ TRIVY_RUN_AS_PLUGIN=kubectl trivy job your-job -- --format json -``` - -## Installing and Running Plugins on the fly -`trivy plugin run` installs a plugin and runs it on the fly. -If the plugin is already present in the cache, the installation is skipped. - -```bash -trivy plugin run github.com/aquasecurity/trivy-plugin-kubectl pod your-pod -- --exit-code 1 -``` - -## Uninstalling Plugins -Specify a plugin name with `trivy plugin uninstall` command. - -```bash -$ trivy plugin uninstall kubectl -``` - -## Building Plugins -Each plugin has a top-level directory, and then a plugin.yaml file. - -```bash -your-plugin/ - | - |- plugin.yaml - |- your-plugin.sh -``` - -In the example above, the plugin is contained inside of a directory named `your-plugin`. -It has two files: plugin.yaml (required) and an executable script, your-plugin.sh (optional). - -The core of a plugin is a simple YAML file named plugin.yaml. -Here is an example YAML of trivy-plugin-kubectl plugin that adds support for Kubernetes scanning. - -```yaml -name: "kubectl" -repository: github.com/aquasecurity/trivy-plugin-kubectl -version: "0.1.0" -usage: scan kubectl resources -description: |- - A Trivy plugin that scans the images of a kubernetes resource. - Usage: trivy kubectl TYPE[.VERSION][.GROUP] NAME -platforms: - - selector: # optional - os: darwin - arch: amd64 - uri: ./trivy-kubectl # where the execution file is (local file, http, git, etc.) - bin: ./trivy-kubectl # path to the execution file - - selector: # optional - os: linux - arch: amd64 - uri: https://github.com/aquasecurity/trivy-plugin-kubectl/releases/download/v0.1.0/trivy-kubectl.tar.gz - bin: ./trivy-kubectl -``` - -The `plugin.yaml` field should contain the following information: - -- name: The name of the plugin. This also determines how the plugin will be made available in the Trivy CLI. For example, if the plugin is named kubectl, you can call the plugin with `trivy kubectl`. (required) -- version: The version of the plugin. (required) -- usage: A short usage description. (required) -- description: A long description of the plugin. This is where you could provide a helpful documentation of your plugin. (required) -- platforms: (required) - - selector: The OS/Architecture specific variations of a execution file. (optional) - - os: OS information based on GOOS (linux, darwin, etc.) (optional) - - arch: The architecture information based on GOARCH (amd64, arm64, etc.) (optional) - - uri: Where the executable file is. Relative path from the root directory of the plugin or remote URL such as HTTP and S3. (required) - - bin: Which file to call when the plugin is executed. Relative path from the root directory of the plugin. (required) - -The following rules will apply in deciding which platform to select: - -- If both `os` and `arch` under `selector` match the current platform, search will stop and the platform will be used. -- If `selector` is not present, the platform will be used. -- If `os` matches and there is no more specific `arch` match, the platform will be used. -- If no `platform` match is found, Trivy will exit with an error. - -After determining platform, Trivy will download the execution file from `uri` and store it in the plugin cache. -When the plugin is called via Trivy CLI, `bin` command will be executed. - -The plugin is responsible for handling flags and arguments. Any arguments are passed to the plugin from the `trivy` command. - -A plugin should be archived `*.tar.gz`. - -```bash -$ tar -czvf myplugin.tar.gz plugin.yaml script.py -plugin.yaml -script.py - -$ trivy plugin install myplugin.tar.gz -2023-03-03T19:04:42.026+0600 INFO Installing the plugin from myplugin.tar.gz... -2023-03-03T19:04:42.026+0600 INFO Loading the plugin metadata... - -$ trivy myplugin -Hello from Trivy demo plugin! -``` - -## Plugin Types -Plugins are typically intended to be used as subcommands of Trivy, -but some plugins can be invoked as part of Trivy's built-in commands. -Currently, the following type of plugin is experimentally supported: - -- Output plugins - -### Output Plugins - -!!! warning "EXPERIMENTAL" - This feature might change without preserving backwards compatibility. - -Trivy supports "output plugins" which process Trivy's output, -such as by transforming the output format or sending it elsewhere. -For instance, in the case of image scanning, the output plugin can be called as follows: - -```shell -$ trivy image --format json --output plugin= [--output-plugin-arg ] -``` - -Since scan results are passed to the plugin via standard input, plugins must be capable of handling standard input. - -!!! warning - To avoid Trivy hanging, you need to read all data from `Stdin` before the plugin exits successfully or stops with an error. - -While the example passes JSON to the plugin, other formats like SBOM can also be passed (e.g., `--format cyclonedx`). - -If a plugin requires flags or other arguments, they can be passed using `--output-plugin-arg`. -This is directly forwarded as arguments to the plugin. -For example, `--output plugin=myplugin --output-plugin-arg "--foo --bar=baz"` translates to `myplugin --foo --bar=baz` in execution. - -An example of the output plugin is available [here](https://github.com/aquasecurity/trivy-output-plugin-count). -It can be used as below: - -```shell -# Install the plugin first -$ trivy plugin install github.com/aquasecurity/trivy-output-plugin-count - -# Call the output plugin in image scanning -$ trivy image --format json --output plugin=count --output-plugin-arg "--published-after 2023-10-01" debian:12 -``` - -## Example -- https://github.com/aquasecurity/trivy-plugin-kubectl -- https://github.com/aquasecurity/trivy-output-plugin-count - -[kubectl]: https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/ -[helm]: https://helm.sh/docs/topics/plugins/ -[conftest]: https://www.conftest.dev/plugins/ -[go-getter]: https://github.com/hashicorp/go-getter -[trivy-plugin-kubectl]: https://github.com/aquasecurity/trivy-plugin-kubectl - diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index 117db88de866..8671501ad885 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -399,7 +399,7 @@ $ trivy [--format ] --output plugin= [--output-plu ``` This is useful for cases where you want to convert the output into a custom format, or when you want to send the output somewhere. -For more details, please check [here](../advanced/plugins.md#output-plugins). +For more details, please check [here](../plugin/plugins.md#output-plugins). ## Converting To generate multiple reports, you can generate the JSON report first and convert it to other formats with the `convert` subcommand. diff --git a/docs/docs/plugin/developer-guide.md b/docs/docs/plugin/developer-guide.md new file mode 100644 index 000000000000..5080bab9ede9 --- /dev/null +++ b/docs/docs/plugin/developer-guide.md @@ -0,0 +1,203 @@ +# Developer Guide + +## Developing Trivy plugins +This section will guide you through the process of developing Trivy plugins. +To help you get started quickly, we have published a [plugin template repository][plugin-template]. +You can use this template as a starting point for your plugin development. + +### Introduction +If you are looking to start developing plugins for Trivy, read [the user guide](./user-guide.md) first. + +The development process involves the following steps: + +- Create a repository for your plugin, named `trivy-plugin-`. +- Create an executable binary that can be invoked as `trivy `. +- Place the executable binary in a repository. +- Create a `plugin.yaml` file that describes the plugin. +- (Submit your plugin to the [Trivy plugin index][trivy-plugin-index].) + +After you develop a plugin with a good name following the best practices and publish it, you can submit your plugin to the [Trivy plugin index][trivy-plugin-index]. + +### Naming +This section describes guidelines for naming your plugins. + +#### Use `trivy-plugin-` prefix +The name of the plugin repository should be prefixed with `trivy-plugin-`. + +#### Use lowercase and hyphens +Plugin names must be all lowercase and separate words with hyphens. +Don’t use camelCase, PascalCase, or snake_case; use kebab-case. + +- NO: `trivy OpenSvc` +- YES: `trivy open-svc` + +#### Be specific +Plugin names should not be verbs or nouns that are generic, already overloaded, or likely to be used for broader purposes by another plugin. + +- NO: trivy sast (Too broad) +- YES: trivy govulncheck + + +#### Be unique +Find a unique name for your plugin that differentiates it from other plugins that perform a similar function. + +- NO: `trivy images` (Unclear how it is different from the builtin “image" command) +- YES: `trivy registry-images` (Unique name). + +#### Prefix Vendor Identifiers +Use vendor-specific strings as prefix, separated with a dash. +This makes it easier to search/group plugins that are about a specific vendor. + +- NO: `trivy security-hub-aws (Makes it harder to search or locate in a plugin list) +- YES: `trivy aws-security-hub (Will show up together with other aws-* plugins) + +### Choosing a language +Since Trivy plugins are standalone executables, you can write them in any programming language. + +If you are planning to write a plugin with Go, check out [the Report struct](https://github.com/aquasecurity/trivy/blob/787b466e069e2d04e73b3eddbda621e5eec8543b/pkg/types/report.go#L13-L24), +which is the output of Trivy scan. + + +### Writing your plugin +Each plugin has a top-level directory, and then a `plugin.yaml` file. + +```bash +your-plugin/ + | + |- plugin.yaml + |- your-plugin.sh +``` + +In the example above, the plugin is contained inside a directory named `your-plugin`. +It has two files: `plugin.yaml` (required) and an executable script, `your-plugin.sh` (optional). + +#### Writing a plugin manifest +The plugin manifest is a simple YAML file named `plugin.yaml`. +Here is an example YAML of [trivy-plugin-kubectl][trivy-plugin-kubectl] plugin that adds support for Kubernetes scanning. + +```yaml +name: "kubectl" +version: "0.1.0" +repository: github.com/aquasecurity/trivy-plugin-kubectl +maintainer: aquasecurity +output: false +summary: Scan kubectl resources +description: |- + A Trivy plugin that scans the images of a kubernetes resource. + Usage: trivy kubectl TYPE[.VERSION][.GROUP] NAME +platforms: + - selector: # optional + os: darwin + arch: amd64 + uri: ./trivy-kubectl # where the execution file is (local file, http, git, etc.) + bin: ./trivy-kubectl # path to the execution file + - selector: # optional + os: linux + arch: amd64 + uri: https://github.com/aquasecurity/trivy-plugin-kubectl/releases/download/v0.1.0/trivy-kubectl.tar.gz + bin: ./trivy-kubectl +``` + +We encourage you to copy and adapt plugin manifests of existing plugins. + +- [count][trivy-plugin-count] +- [referrer][trivy-plugin-referrer] + +The `plugin.yaml` field should contain the following information: + +- name: The name of the plugin. This also determines how the plugin will be made available in the Trivy CLI. For example, if the plugin is named kubectl, you can call the plugin with `trivy kubectl`. (required) +- version: The version of the plugin. [Semantic Versioning][semver] should be used. (required) +- repository: The repository name where the plugin is hosted. (required) +- maintainer: The name of the maintainer of the plugin. (required) +- output: Whether the plugin supports [the output mode](./user-guide.md#output-mode-support). (optional) +- usage: Deprecated: use summary instead. (optional) +- summary: A short usage description. (required) +- description: A long description of the plugin. This is where you could provide a helpful documentation of your plugin. (required) +- platforms: (required) + - selector: The OS/Architecture specific variations of a execution file. (optional) + - os: OS information based on GOOS (linux, darwin, etc.) (optional) + - arch: The architecture information based on GOARCH (amd64, arm64, etc.) (optional) + - uri: Where the executable file is. Relative path from the root directory of the plugin or remote URL such as HTTP and S3. (required) + - bin: Which file to call when the plugin is executed. Relative path from the root directory of the plugin. (required) + +The following rules will apply in deciding which platform to select: + +- If both `os` and `arch` under `selector` match the current platform, search will stop and the platform will be used. +- If `selector` is not present, the platform will be used. +- If `os` matches and there is no more specific `arch` match, the platform will be used. +- If no `platform` match is found, Trivy will exit with an error. + +After determining platform, Trivy will download the execution file from `uri` and store it in the plugin cache. +When the plugin is called via Trivy CLI, `bin` command will be executed. + +#### Plugin arguments/flags +The plugin is responsible for handling flags and arguments. +Any arguments are passed to the plugin from the `trivy` command. + +#### Testing plugin installation locally +A plugin should be archived `*.tar.gz`. +After you have archived your plugin into a `.tar.gz` file, you can verify that your plugin installs correctly with Trivy. + +```bash +$ tar -czvf myplugin.tar.gz plugin.yaml script.py +plugin.yaml +script.py + +$ trivy plugin install myplugin.tar.gz +2023-03-03T19:04:42.026+0600 INFO Installing the plugin from myplugin.tar.gz... +2023-03-03T19:04:42.026+0600 INFO Loading the plugin metadata... + +$ trivy myplugin +Hello from Trivy demo plugin! +``` + +## Publishing plugins +The [plugin.yaml](#writing-a-plugin-manifest) file is the core of your plugin, so as long as it is published somewhere, your plugin can be installed. +If you choose to publish your plugin on GitHub, you can make it installable by placing the plugin.yaml file in the root directory of your repository. +Users can then install your plugin with the command, `trivy plugin install github.com/org/repo`. + +While the `uri` specified in the plugin.yaml file doesn't necessarily need to point to the same repository, it's a good practice to host the executable file within the same repository when using GitHub. +You can utilize GitHub Releases to distribute the executable file. +For an example of how to structure your plugin repository, refer to [the plugin template repository][plugin-template]. + +## Distributing plugins via the Trivy plugin index +Trivy can install plugins directly by specifying a repository, like `trivy plugin install github.com/aquasecurity/trivy-plugin-referrer`, +so you don't necessarily need to register your plugin in the Trivy plugin index. +However, we would recommend distributing your plugin via the Trivy plugin index +since it makes it easier for other users to find (`trivy plugin search`) and install your plugin (e.g. `trivy plugin install kubectl`). + +### Pre-submit checklist +- Review [the plugin naming guide](#naming). +- Ensure the `plugin.yaml` file has all the required fields. +- Tag a git release with a semantic version (e.g. v1.0.0). +- [Test your plugin installation locally](#testing-plugin-installation-locally). + +### Submitting plugins +Submitting your plugin to the plugin index is a straightforward process. +All you need to do is create a YAML file for your plugin and place it in the [plugins/](https://github.com/aquasecurity/trivy-plugin-index/tree/main/plugins) directory of [the index repository][trivy-plugin-index]. + +Once you've done that, create a pull request (PR) and have it reviewed by the maintainers. +Once your PR is merged, the index will be updated, and your plugin will be available for installation. +[The plugin index page][plugin-list] will also be automatically updated to list your newly added plugin. + +The content of the YAML file is very simple. +You only need to specify the name of your plugin and the repository where it is distributed. + +```yaml +name: referrer +repository: github.com/aquasecurity/trivy-plugin-referrer +``` + +After your PR is merged, the CI system will automatically retrieve the `plugin.yaml` file from your repository and update [the index.yaml file][index]. +If any required fields are missing from your `plugin.yaml`, the CI will fail, so make sure your `plugin.yaml` has all the required fields before creating a PR. +Once [the index.yaml][index] has been updated, running `trivy plugin update` will download the updated index to your local machine. + + +[plugin-template]: https://github.com/aquasecurity/trivy-plugin-template +[plugin-list]: https://aquasecurity.github.io/trivy-plugin-index/ +[index]: https://aquasecurity.github.io/trivy-plugin-index/v1/index.yaml +[semver]: https://semver.org/ +[trivy-plugin-index]: https://github.com/aquasecurity/trivy-plugin-index +[trivy-plugin-kubectl]: https://github.com/aquasecurity/trivy-plugin-kubectl +[trivy-plugin-count]: https://github.com/aquasecurity/trivy-plugin-count/blob/main/plugin.yaml +[trivy-plugin-referrer]: https://github.com/aquasecurity/trivy-plugin-referrer/blob/main/plugin.yaml diff --git a/docs/docs/plugin/index.md b/docs/docs/plugin/index.md new file mode 100644 index 000000000000..b640dce47b90 --- /dev/null +++ b/docs/docs/plugin/index.md @@ -0,0 +1,70 @@ +# Plugins +Trivy provides a plugin feature to allow others to extend the Trivy CLI without the need to change the Trivy code base. +This plugin system was inspired by the plugin system used in [kubectl][kubectl], [Helm][helm], and [Conftest][conftest]. + +## Overview +Trivy plugins are add-on tools that integrate seamlessly with Trivy. +They provide a way to extend the core feature set of Trivy, but without requiring every new feature to be written in Go and added to the core tool. + +- They can be added and removed from a Trivy installation without impacting the core Trivy tool. +- They can be written in any programming language. +- They integrate with Trivy, and will show up in Trivy help and subcommands. + +!!! warning + Trivy plugins available in public are not audited for security. + You should install and run third-party plugins at your own risk, since they are arbitrary programs running on your machine. + +## Quickstart +Trivy helps you discover and install plugins on your machine. + +You can install and use a wide variety of Trivy plugins to enhance your experience. + +Let’s get started: + +1. Download the plugin list: + + ```bash + $ trivy plugin update + ``` + +2. Discover Trivy plugins available on the plugin index: + + ```bash + $ trivy plugin search + NAME DESCRIPTION MAINTAINER OUTPUT + aqua A plugin for integration with Aqua Security SaaS platform aquasecurity + kubectl A plugin scanning the images of a kubernetes resource aquasecurity + referrer A plugin for OCI referrers aquasecurity ✓ + [...] + ``` + +3. Choose a plugin from the list and install it: + + ```bash + $ trivy plugin install referrer + ``` + +4. Use the installed plugin: + + ```bash + $ trivy referrer --help + ``` + +5. Keep your plugins up-to-date: + + ```bash + $ trivy plugin upgrade + ``` + +6. Uninstall a plugin you no longer use: + + ```bash + trivy plugin uninstall referrer + ``` + +This is practically all you need to know to start using Trivy plugins. + + +[kubectl]: https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/ +[helm]: https://helm.sh/docs/topics/plugins/ +[conftest]: https://www.conftest.dev/plugins/ diff --git a/docs/docs/plugin/user-guide.md b/docs/docs/plugin/user-guide.md new file mode 100644 index 000000000000..b216f3d63c85 --- /dev/null +++ b/docs/docs/plugin/user-guide.md @@ -0,0 +1,207 @@ +# User Guide + +## Discovering Plugins +You can find a list of Trivy plugins distributed via trivy-plugin-index [here][trivy-plugin-index]. +However, you can find plugins using the command line as well. + +First, refresh your local copy of the plugin index: + +```bash +$ trivy plugin update +``` + +To list all plugins available, run: + +```bash +$ trivy plugin search +NAME DESCRIPTION MAINTAINER OUTPUT +aqua A plugin for integration with Aqua Security SaaS platform aquasecurity +kubectl A plugin scanning the images of a kubernetes resource aquasecurity +referrer A plugin for OCI referrers aquasecurity ✓ +``` + +You can specify search keywords as arguments: + +```bash +$ trivy plugin search referrer + +NAME DESCRIPTION MAINTAINER OUTPUT +referrer A plugin for OCI referrers aquasecurity ✓ +``` + +It lists plugins with the keyword in the name or description. + +## Installing Plugins +Plugins can be installed with the `trivy plugin install` command: + +```bash +$ trivy plugin install referrer +``` + +This command will download the plugin and install it in the plugin cache. + +Trivy adheres to the XDG specification, so the location depends on whether XDG_DATA_HOME is set. +Trivy will now search XDG_DATA_HOME for the location of the Trivy plugins cache. +The preference order is as follows: + +- XDG_DATA_HOME if set and .trivy/plugins exists within the XDG_DATA_HOME dir +- ~/.trivy/plugins + +Furthermore, it is possible to download plugins that are not registered in the index by specifying the URL directly or by specifying the file path. + +```bash +$ trivy plugin install github.com/aquasecurity/trivy-plugin-kubectl +``` +```bash +$ trivy plugin install myplugin.tar.gz +``` + +Under the hood Trivy leverages [go-getter][go-getter] to download plugins. +This means the following protocols are supported for downloading plugins: + +- OCI Registries +- Local Files +- Git +- HTTP/HTTPS +- Mercurial +- Amazon S3 +- Google Cloud Storage + +## Listing Installed Plugins +To list all plugins installed, run: + +```bash +$ trivy plugin list +``` + +## Using Plugins +Once the plugin is installed, Trivy will load all available plugins in the cache on the start of the next Trivy execution. +A plugin will be made in the Trivy CLI based on the plugin name. +To display all plugins, you can list them by `trivy --help` + +```bash +$ trivy --help +NAME: + trivy - A simple and comprehensive vulnerability scanner for containers + +USAGE: + trivy [global options] command [command options] target + +VERSION: + dev + +Scanning Commands + aws [EXPERIMENTAL] Scan AWS account + config Scan config files for misconfigurations + filesystem Scan local filesystem + image Scan a container image + +... + +Plugin Commands + kubectl scan kubectl resources + referrer Put referrers to OCI registry +``` + +As shown above, `kubectl` subcommand exists in the `Plugin Commands` section. +To call the kubectl plugin and scan existing Kubernetes deployments, you can execute the following command: + +``` +$ trivy kubectl deployment -- --ignore-unfixed --severity CRITICAL +``` + +Internally the kubectl plugin calls the kubectl binary to fetch information about that deployment and passes the using images to Trivy. +You can see the detail [here][trivy-plugin-kubectl]. + +If you want to omit even the subcommand, you can use `TRIVY_RUN_AS_PLUGIN` environment variable. + +```bash +$ TRIVY_RUN_AS_PLUGIN=kubectl trivy job your-job -- --format json +``` + +## Installing and Running Plugins on the fly +`trivy plugin run` installs a plugin and runs it on the fly. +If the plugin is already present in the cache, the installation is skipped. + +```bash +trivy plugin run kubectl pod your-pod -- --exit-code 1 +``` + +## Upgrading Plugins +To upgrade all plugins that you have installed to their latest versions, run: + +```bash +$ trivy plugin upgrade +``` + +To upgrade only certain plugins, you can explicitly specify their names: + +```bash +$ trivy plugin upgrade +``` + +## Uninstalling Plugins +Specify a plugin name with `trivy plugin uninstall` command. + +```bash +$ trivy plugin uninstall kubectl +``` + +Here's the revised English documentation based on your requested changes: + +## Output Mode Support +While plugins are typically intended to be used as subcommands of Trivy, plugins supporting the output mode can be invoked as part of Trivy's built-in commands. + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy supports plugins that are compatible with the output mode, which process Trivy's output, such as by transforming the output format or sending it elsewhere. +You can determine whether a plugin supports the output mode by checking the `OUTPUT` column in the output of `trivy plugin search` or `trivy plugin list`. + +```bash +$ trivy plugin search +NAME DESCRIPTION MAINTAINER OUTPUT +aqua A plugin for integration with Aqua Security SaaS platform aquasecurity +kubectl A plugin scanning the images of a kubernetes resource aquasecurity +referrer A plugin for OCI referrers aquasecurity ✓ +``` + +In this case, the `referrer` plugin supports the output mode. + +For instance, in the case of image scanning, a plugin supporting the output mode can be called as follows: + +```bash +$ trivy image --format json --output plugin= [--output-plugin-arg ] +``` + +Since scan results are passed to the plugin via standard input, plugins must be capable of handling standard input. + +!!! warning + To avoid Trivy hanging, you need to read all data from `Stdin` before the plugin exits successfully or stops with an error. + +While the example passes JSON to the plugin, other formats like SBOM can also be passed (e.g., `--format cyclonedx`). + +If a plugin requires flags or other arguments, they can be passed using `--output-plugin-arg`. +This is directly forwarded as arguments to the plugin. +For example, `--output plugin=myplugin --output-plugin-arg "--foo --bar=baz"` translates to `myplugin --foo --bar=baz` in execution. + +An example of a plugin supporting the output mode is available [here][trivy-plugin-count]. +It can be used as below: + +```bash +# Install the plugin first +$ trivy plugin install count + +# Call the plugin supporting the output mode in image scanning +$ trivy image --format json --output plugin=count --output-plugin-arg "--published-after 2023-10-01" debian:12 +``` + +## Example + +- [kubectl][trivy-plugin-kubectl] +- [count][trivy-plugin-count] + +[trivy-plugin-index]: https://aquasecurity.github.io/trivy-plugin-index/ +[go-getter]: https://github.com/hashicorp/go-getter +[trivy-plugin-kubectl]: https://github.com/aquasecurity/trivy-plugin-kubectl +[trivy-plugin-count]: https://github.com/aquasecurity/trivy-plugin-count diff --git a/docs/docs/references/configuration/cli/trivy_plugin.md b/docs/docs/references/configuration/cli/trivy_plugin.md index 9f47212d17f5..a3d105d2cd3f 100644 --- a/docs/docs/references/configuration/cli/trivy_plugin.md +++ b/docs/docs/references/configuration/cli/trivy_plugin.md @@ -28,6 +28,8 @@ Manage plugins * [trivy plugin install](trivy_plugin_install.md) - Install a plugin * [trivy plugin list](trivy_plugin_list.md) - List installed plugin * [trivy plugin run](trivy_plugin_run.md) - Run a plugin on the fly +* [trivy plugin search](trivy_plugin_search.md) - List Trivy plugins available on the plugin index and search among them * [trivy plugin uninstall](trivy_plugin_uninstall.md) - Uninstall a plugin -* [trivy plugin update](trivy_plugin_update.md) - Update an existing plugin +* [trivy plugin update](trivy_plugin_update.md) - Update the local copy of the plugin index +* [trivy plugin upgrade](trivy_plugin_upgrade.md) - Upgrade installed plugins to newer versions diff --git a/docs/docs/references/configuration/cli/trivy_plugin_install.md b/docs/docs/references/configuration/cli/trivy_plugin_install.md index f92da9598326..dbd5f21797b8 100644 --- a/docs/docs/references/configuration/cli/trivy_plugin_install.md +++ b/docs/docs/references/configuration/cli/trivy_plugin_install.md @@ -3,7 +3,7 @@ Install a plugin ``` -trivy plugin install URL | FILE_PATH +trivy plugin install NAME | URL | FILE_PATH ``` ### Options diff --git a/docs/docs/references/configuration/cli/trivy_plugin_run.md b/docs/docs/references/configuration/cli/trivy_plugin_run.md index 0dc7087a19d0..5befb58f90ef 100644 --- a/docs/docs/references/configuration/cli/trivy_plugin_run.md +++ b/docs/docs/references/configuration/cli/trivy_plugin_run.md @@ -3,7 +3,7 @@ Run a plugin on the fly ``` -trivy plugin run URL | FILE_PATH +trivy plugin run NAME | URL | FILE_PATH ``` ### Options diff --git a/docs/docs/references/configuration/cli/trivy_plugin_search.md b/docs/docs/references/configuration/cli/trivy_plugin_search.md new file mode 100644 index 000000000000..931babfd59b8 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_plugin_search.md @@ -0,0 +1,31 @@ +## trivy plugin search + +List Trivy plugins available on the plugin index and search among them + +``` +trivy plugin search [KEYWORD] +``` + +### Options + +``` + -h, --help help for search +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy plugin](trivy_plugin.md) - Manage plugins + diff --git a/docs/docs/references/configuration/cli/trivy_plugin_update.md b/docs/docs/references/configuration/cli/trivy_plugin_update.md index 532add27cfa3..da26290882b1 100644 --- a/docs/docs/references/configuration/cli/trivy_plugin_update.md +++ b/docs/docs/references/configuration/cli/trivy_plugin_update.md @@ -1,9 +1,9 @@ ## trivy plugin update -Update an existing plugin +Update the local copy of the plugin index ``` -trivy plugin update PLUGIN_NAME +trivy plugin update ``` ### Options diff --git a/docs/docs/references/configuration/cli/trivy_plugin_upgrade.md b/docs/docs/references/configuration/cli/trivy_plugin_upgrade.md new file mode 100644 index 000000000000..a3d363d5643a --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_plugin_upgrade.md @@ -0,0 +1,31 @@ +## trivy plugin upgrade + +Upgrade installed plugins to newer versions + +``` +trivy plugin upgrade [PLUGIN_NAMES] +``` + +### Options + +``` + -h, --help help for upgrade +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy plugin](trivy_plugin.md) - Manage plugins + diff --git a/mkdocs.yml b/mkdocs.yml index c3437fecf413..9fb769a7c921 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -128,9 +128,12 @@ nav: - VEX: docs/supply-chain/vex.md - Compliance: - Reports: docs/compliance/compliance.md + - Plugin: + - Overview: docs/plugin/index.md + - User Guide: docs/plugin/user-guide.md + - Developer Guide: docs/plugin/developer-guide.md - Advanced: - Modules: docs/advanced/modules.md - - Plugins: docs/advanced/plugins.md - Air-Gapped Environment: docs/advanced/air-gap.md - Container Image: - Embed in Dockerfile: docs/advanced/container/embed-in-dockerfile.md @@ -152,16 +155,20 @@ nav: - Filesystem: docs/references/configuration/cli/trivy_filesystem.md - Image: docs/references/configuration/cli/trivy_image.md - Kubernetes: docs/references/configuration/cli/trivy_kubernetes.md - - Module: docs/references/configuration/cli/trivy_module.md - - Module Install: docs/references/configuration/cli/trivy_module_install.md - - Module Uninstall: docs/references/configuration/cli/trivy_module_uninstall.md - - Plugin: docs/references/configuration/cli/trivy_plugin.md - - Plugin Info: docs/references/configuration/cli/trivy_plugin_info.md - - Plugin Install: docs/references/configuration/cli/trivy_plugin_install.md - - Plugin List: docs/references/configuration/cli/trivy_plugin_list.md - - Plugin Run: docs/references/configuration/cli/trivy_plugin_run.md - - Plugin Uninstall: docs/references/configuration/cli/trivy_plugin_uninstall.md - - Plugin Update: docs/references/configuration/cli/trivy_plugin_update.md + - Module: + - Module: docs/references/configuration/cli/trivy_module.md + - Module Install: docs/references/configuration/cli/trivy_module_install.md + - Module Uninstall: docs/references/configuration/cli/trivy_module_uninstall.md + - Plugin: + - Plugin: docs/references/configuration/cli/trivy_plugin.md + - Plugin Info: docs/references/configuration/cli/trivy_plugin_info.md + - Plugin Install: docs/references/configuration/cli/trivy_plugin_install.md + - Plugin List: docs/references/configuration/cli/trivy_plugin_list.md + - Plugin Run: docs/references/configuration/cli/trivy_plugin_run.md + - Plugin Uninstall: docs/references/configuration/cli/trivy_plugin_uninstall.md + - Plugin Update: docs/references/configuration/cli/trivy_plugin_update.md + - Plugin Upgrade: docs/references/configuration/cli/trivy_plugin_upgrade.md + - Plugin Search: docs/references/configuration/cli/trivy_plugin_search.md - Repository: docs/references/configuration/cli/trivy_repository.md - Rootfs: docs/references/configuration/cli/trivy_rootfs.md - SBOM: docs/references/configuration/cli/trivy_sbom.md diff --git a/pkg/clock/clock.go b/pkg/clock/clock.go index 91f6a5212bd2..38df49259e55 100644 --- a/pkg/clock/clock.go +++ b/pkg/clock/clock.go @@ -8,6 +8,11 @@ import ( clocktesting "k8s.io/utils/clock/testing" ) +type ( + RealClock = clock.RealClock + FakeClock = clocktesting.FakeClock +) + // clockKey is the context key for clock. It is unexported to prevent collisions with context keys defined in // other packages. type clockKey struct{} @@ -27,7 +32,7 @@ func Now(ctx context.Context) time.Time { func Clock(ctx context.Context) clock.Clock { t, ok := ctx.Value(clockKey{}).(clock.Clock) if !ok { - return clock.RealClock{} + return RealClock{} } return t } diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 38ef4c382765..6ae687276f2a 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -1,6 +1,7 @@ package commands import ( + "context" "encoding/json" "errors" "fmt" @@ -111,20 +112,24 @@ func NewApp() *cobra.Command { } func loadPluginCommands() []*cobra.Command { + ctx := context.Background() + manager := plugin.NewManager() + var commands []*cobra.Command - plugins, err := plugin.LoadAll() + plugins, err := manager.LoadAll(ctx) if err != nil { - log.Debug("No plugins loaded") + log.DebugContext(ctx, "No plugins loaded") return nil } for _, p := range plugins { p := p cmd := &cobra.Command{ Use: fmt.Sprintf("%s [flags]", p.Name), - Short: p.Usage, + Short: p.Summary, + Long: p.Description, GroupID: groupPlugin, RunE: func(cmd *cobra.Command, args []string) error { - if err = p.Run(cmd.Context(), plugin.RunOptions{Args: args}); err != nil { + if err = p.Run(cmd.Context(), plugin.Options{Args: args}); err != nil { return xerrors.Errorf("plugin error: %w", err) } return nil @@ -719,14 +724,15 @@ func NewPluginCommand() *cobra.Command { } cmd.AddCommand( &cobra.Command{ - Use: "install URL | FILE_PATH", + Use: "install NAME | URL | FILE_PATH", Aliases: []string{"i"}, Short: "Install a plugin", SilenceErrors: true, + SilenceUsage: true, DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - if _, err := plugin.Install(cmd.Context(), args[0], true); err != nil { + if _, err := plugin.Install(cmd.Context(), args[0], plugin.Options{}); err != nil { return xerrors.Errorf("plugin install error: %w", err) } return nil @@ -735,12 +741,13 @@ func NewPluginCommand() *cobra.Command { &cobra.Command{ Use: "uninstall PLUGIN_NAME", Aliases: []string{"u"}, - SilenceErrors: true, DisableFlagsInUseLine: true, Short: "Uninstall a plugin", + SilenceErrors: true, + SilenceUsage: true, Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - if err := plugin.Uninstall(args[0]); err != nil { + RunE: func(cmd *cobra.Command, args []string) error { + if err := plugin.Uninstall(cmd.Context(), args[0]); err != nil { return xerrors.Errorf("plugin uninstall error: %w", err) } return nil @@ -749,62 +756,86 @@ func NewPluginCommand() *cobra.Command { &cobra.Command{ Use: "list", Aliases: []string{"l"}, - SilenceErrors: true, DisableFlagsInUseLine: true, + SilenceErrors: true, + SilenceUsage: true, Short: "List installed plugin", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - info, err := plugin.List() - if err != nil { + if err := plugin.List(cmd.Context()); err != nil { return xerrors.Errorf("plugin list display error: %w", err) } - if _, err := fmt.Fprint(os.Stdout, info); err != nil { - return xerrors.Errorf("print error: %w", err) - } return nil }, }, &cobra.Command{ Use: "info PLUGIN_NAME", Short: "Show information about the specified plugin", - SilenceErrors: true, DisableFlagsInUseLine: true, + SilenceErrors: true, + SilenceUsage: true, Args: cobra.ExactArgs(1), RunE: func(_ *cobra.Command, args []string) error { - info, err := plugin.Information(args[0]) - if err != nil { + if err := plugin.Information(args[0]); err != nil { return xerrors.Errorf("plugin information display error: %w", err) } - if _, err := fmt.Fprint(os.Stdout, info); err != nil { - return xerrors.Errorf("print error: %w", err) - } return nil }, }, &cobra.Command{ - Use: "run URL | FILE_PATH", + Use: "run NAME | URL | FILE_PATH", Aliases: []string{"r"}, - SilenceErrors: true, DisableFlagsInUseLine: true, + SilenceErrors: true, + SilenceUsage: true, Short: "Run a plugin on the fly", Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return plugin.RunWithURL(cmd.Context(), args[0], plugin.RunOptions{Args: args[1:]}) + return plugin.RunWithURL(cmd.Context(), args[0], plugin.Options{Args: args[1:]}) }, }, &cobra.Command{ - Use: "update PLUGIN_NAME", - Short: "Update an existing plugin", - SilenceErrors: true, + Use: "update", + Short: "Update the local copy of the plugin index", DisableFlagsInUseLine: true, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - if err := plugin.Update(args[0]); err != nil { + SilenceErrors: true, + SilenceUsage: true, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + if err := plugin.Update(cmd.Context()); err != nil { return xerrors.Errorf("plugin update error: %w", err) } return nil }, }, + &cobra.Command{ + Use: "search [KEYWORD]", + DisableFlagsInUseLine: true, + SilenceErrors: true, + SilenceUsage: true, + Short: "List Trivy plugins available on the plugin index and search among them", + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + var keyword string + if len(args) == 1 { + keyword = args[0] + } + return plugin.Search(cmd.Context(), keyword) + }, + }, + &cobra.Command{ + Use: "upgrade [PLUGIN_NAMES]", + Short: "Upgrade installed plugins to newer versions", + DisableFlagsInUseLine: true, + SilenceErrors: true, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + if err := plugin.Upgrade(cmd.Context(), args); err != nil { + return xerrors.Errorf("plugin upgrade error: %w", err) + } + return nil + }, + }, ) cmd.SetFlagErrorFunc(flagErrorFunc) return cmd diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 744abbd1ddaa..9f4032fb9711 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -447,7 +447,7 @@ func (o *Options) outputPluginWriter(ctx context.Context) (io.Writer, func() err pluginName := strings.TrimPrefix(o.Output, "plugin=") pr, pw := io.Pipe() - wait, err := plugin.Start(ctx, pluginName, plugin.RunOptions{ + wait, err := plugin.Start(ctx, pluginName, plugin.Options{ Args: o.OutputPluginArgs, Stdin: pr, }) diff --git a/pkg/log/handler.go b/pkg/log/handler.go index 5e07b104715e..b2474cbee3c5 100644 --- a/pkg/log/handler.go +++ b/pkg/log/handler.go @@ -14,6 +14,8 @@ import ( "github.com/fatih/color" "github.com/samber/lo" "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/clock" ) const ( @@ -145,6 +147,11 @@ func (h *ColorHandler) Handle(ctx context.Context, r slog.Record) error { freeBuf(bufp) }() + // For tests, use the fake clock's time. + if c, ok := clock.Clock(ctx).(*clock.FakeClock); ok { + r.Time = c.Now() + } + buf = h.handle(ctx, buf, r) h.mu.Lock() diff --git a/pkg/plugin/index.go b/pkg/plugin/index.go new file mode 100644 index 000000000000..c825c16e67af --- /dev/null +++ b/pkg/plugin/index.go @@ -0,0 +1,117 @@ +package plugin + +import ( + "bytes" + "context" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/samber/lo" + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/downloader" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +const indexURL = "https://aquasecurity.github.io/trivy-plugin-index/v1/index.yaml" + +type Index struct { + Version int `yaml:"version"` + Plugins []struct { + Name string `yaml:"name"` + Maintainer string `yaml:"maintainer"` + Summary string `yaml:"summary"` + Repository string `yaml:"repository"` + Output bool `yaml:"output"` + } `yaml:"plugins"` +} + +func (m *Manager) Update(ctx context.Context) error { + m.logger.InfoContext(ctx, "Updating the plugin index...", log.String("url", m.indexURL)) + if err := downloader.Download(ctx, m.indexURL, filepath.Dir(m.indexPath), ""); err != nil { + return xerrors.Errorf("unable to download the plugin index: %w", err) + } + return nil +} + +func (m *Manager) Search(ctx context.Context, keyword string) error { + index, err := m.loadIndex() + if errors.Is(err, os.ErrNotExist) { + m.logger.ErrorContext(ctx, "The plugin index is not found. Please run 'trivy plugin update' to download the index.") + return xerrors.Errorf("plugin index not found: %w", err) + } else if err != nil { + return xerrors.Errorf("unable to load the plugin index: %w", err) + } + + var buf bytes.Buffer + buf.WriteString(fmt.Sprintf("%-20s %-60s %-20s %s\n", "NAME", "DESCRIPTION", "MAINTAINER", "OUTPUT")) + for _, p := range index.Plugins { + if keyword == "" || strings.Contains(p.Name, keyword) || strings.Contains(p.Summary, keyword) { + s := fmt.Sprintf("%-20s %-60s %-20s %s\n", truncateString(p.Name, 20), + truncateString(p.Summary, 60), truncateString(p.Maintainer, 20), + lo.Ternary(p.Output, " ✓", "")) + buf.WriteString(s) + } + } + + if _, err = fmt.Fprintf(m.w, buf.String()); err != nil { + return err + } + + return nil +} + +// tryIndex returns the repository URL if the plugin name is found in the index. +// Otherwise, it returns the input name. +func (m *Manager) tryIndex(ctx context.Context, name string) string { + // If the index file does not exist, download it first. + if !fsutils.FileExists(m.indexPath) { + if err := m.Update(ctx); err != nil { + m.logger.ErrorContext(ctx, "Failed to update the plugin index", log.Err(err)) + return name + } + } + + index, err := m.loadIndex() + if errors.Is(err, os.ErrNotExist) { + m.logger.WarnContext(ctx, "The plugin index is not found. Please run 'trivy plugin update' to download the index.") + return name + } else if err != nil { + m.logger.ErrorContext(ctx, "Unable to load the plugin index: %w", err) + return name + } + + for _, p := range index.Plugins { + if p.Name == name { + return p.Repository + } + } + return name +} + +func (m *Manager) loadIndex() (*Index, error) { + f, err := os.Open(m.indexPath) + if err != nil { + return nil, xerrors.Errorf("unable to open the index file: %w", err) + } + defer f.Close() + + var index Index + if err = yaml.NewDecoder(f).Decode(&index); err != nil { + return nil, xerrors.Errorf("unable to decode the index file: %w", err) + } + + return &index, nil +} + +func truncateString(str string, num int) string { + if len(str) <= num { + return str + } + return str[:num-3] + "..." +} diff --git a/pkg/plugin/index_test.go b/pkg/plugin/index_test.go new file mode 100644 index 000000000000..d918a9dac7ef --- /dev/null +++ b/pkg/plugin/index_test.go @@ -0,0 +1,87 @@ +package plugin_test + +import ( + "bytes" + "context" + "github.com/aquasecurity/trivy/pkg/plugin" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" +) + +func TestManager_Update(t *testing.T) { + tempDir := t.TempDir() + fsutils.SetCacheDir(tempDir) + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte(`this is index`)) + require.NoError(t, err) + })) + t.Cleanup(ts.Close) + + manager := plugin.NewManager(plugin.WithIndexURL(ts.URL + "/index.yaml")) + err := manager.Update(context.Background()) + require.NoError(t, err) + + indexPath := filepath.Join(tempDir, "plugin", "index.yaml") + assert.FileExists(t, indexPath) + + b, err := os.ReadFile(indexPath) + require.NoError(t, err) + assert.Equal(t, "this is index", string(b)) +} + +func TestManager_Search(t *testing.T) { + tests := []struct { + name string + keyword string + dir string + want string + wantErr string + }{ + { + name: "all plugins", + keyword: "", + dir: "testdata", + want: `NAME DESCRIPTION MAINTAINER OUTPUT +foo A foo plugin aquasecurity ✓ +bar A bar plugin aquasecurity +test A test plugin aquasecurity +`, + }, + { + name: "keyword", + keyword: "bar", + dir: "testdata", + want: `NAME DESCRIPTION MAINTAINER OUTPUT +bar A bar plugin aquasecurity +`, + }, + { + name: "no index", + keyword: "", + dir: "unknown", + wantErr: "plugin index not found", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fsutils.SetCacheDir(tt.dir) + + var got bytes.Buffer + m := plugin.NewManager(plugin.WithWriter(&got)) + err := m.Search(context.Background(), tt.keyword) + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got.String()) + }) + } +} diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go new file mode 100644 index 000000000000..8f79d744bfb0 --- /dev/null +++ b/pkg/plugin/manager.go @@ -0,0 +1,355 @@ +package plugin + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/samber/lo" + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/downloader" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +const configFile = "plugin.yaml" + +var ( + pluginsRelativeDir = filepath.Join(".trivy", "plugins") + + _defaultManager *Manager +) + +type ManagerOption func(indexer *Manager) + +func WithWriter(w io.Writer) ManagerOption { + return func(indexer *Manager) { + indexer.w = w + } +} + +func WithIndexURL(indexURL string) ManagerOption { + return func(indexer *Manager) { + indexer.indexURL = indexURL + } +} + +// Manager manages the plugins +type Manager struct { + w io.Writer + indexURL string + logger *log.Logger + pluginRoot string + indexPath string +} + +func NewManager(opts ...ManagerOption) *Manager { + m := &Manager{ + w: os.Stdout, + indexURL: indexURL, + logger: log.WithPrefix("plugin"), + pluginRoot: filepath.Join(fsutils.HomeDir(), pluginsRelativeDir), + indexPath: filepath.Join(fsutils.CacheDir(), "plugin", "index.yaml"), + } + for _, opt := range opts { + opt(m) + } + return m +} + +func defaultManager() *Manager { + if _defaultManager == nil { + _defaultManager = NewManager() + } + return _defaultManager +} + +func Install(ctx context.Context, name string, opts Options) (Plugin, error) { + return defaultManager().Install(ctx, name, opts) +} +func Start(ctx context.Context, name string, opts Options) (Wait, error) { + return defaultManager().Start(ctx, name, opts) +} +func RunWithURL(ctx context.Context, name string, opts Options) error { + return defaultManager().RunWithURL(ctx, name, opts) +} +func Upgrade(ctx context.Context, names []string) error { return defaultManager().Upgrade(ctx, names) } +func Uninstall(ctx context.Context, name string) error { return defaultManager().Uninstall(ctx, name) } +func Information(name string) error { return defaultManager().Information(name) } +func List(ctx context.Context) error { return defaultManager().List(ctx) } +func Update(ctx context.Context) error { return defaultManager().Update(ctx) } +func Search(ctx context.Context, keyword string) error { return defaultManager().Search(ctx, keyword) } + +// Install installs a plugin +func (m *Manager) Install(ctx context.Context, name string, opts Options) (Plugin, error) { + src := m.tryIndex(ctx, name) + + // If the plugin is already installed, it skips installing the plugin. + if p, installed := m.isInstalled(ctx, src); installed { + m.logger.InfoContext(ctx, "The plugin is already installed", log.String("name", p.Name)) + return p, nil + } + + m.logger.InfoContext(ctx, "Installing the plugin...", log.String("src", src)) + return m.install(ctx, src, opts) +} + +func (m *Manager) install(ctx context.Context, src string, opts Options) (Plugin, error) { + tempDir, err := downloader.DownloadToTempDir(ctx, src) + if err != nil { + return Plugin{}, xerrors.Errorf("download failed: %w", err) + } + defer os.RemoveAll(tempDir) + + m.logger.DebugContext(ctx, "Loading the plugin metadata...") + plugin, err := m.loadMetadata(tempDir) + if err != nil { + return Plugin{}, xerrors.Errorf("failed to load the plugin metadata: %w", err) + } + + if err = plugin.install(ctx, plugin.Dir(), tempDir, opts); err != nil { + return Plugin{}, xerrors.Errorf("failed to install the plugin: %w", err) + } + + // Copy plugin.yaml into the plugin dir + f, err := os.Create(filepath.Join(plugin.Dir(), configFile)) + if err != nil { + return Plugin{}, xerrors.Errorf("failed to create plugin.yaml: %w", err) + } + defer f.Close() + + if err = yaml.NewEncoder(f).Encode(plugin); err != nil { + return Plugin{}, xerrors.Errorf("yaml encode error: %w", err) + } + + m.logger.InfoContext(ctx, "Plugin successfully installed", log.String("name", plugin.Name)) + + return plugin, nil +} + +// Uninstall installs the plugin +func (m *Manager) Uninstall(ctx context.Context, name string) error { + pluginDir := filepath.Join(m.pluginRoot, name) + if !fsutils.DirExists(pluginDir) { + m.logger.ErrorContext(ctx, "No such plugin") + return nil + } + if err := os.RemoveAll(pluginDir); err != nil { + return xerrors.Errorf("failed to uninstall the plugin: %w", err) + } + m.logger.InfoContext(ctx, "Plugin successfully uninstalled", log.String("name", name)) + return nil +} + +// Information gets the information about an installed plugin +func (m *Manager) Information(name string) error { + plugin, err := m.load(name) + if err != nil { + return xerrors.Errorf("plugin load error: %w", err) + } + + _, err = fmt.Fprintf(m.w, ` +Plugin: %s + Version: %s + Summary: %s + Description: %s +`, plugin.Name, plugin.Version, plugin.Summary, plugin.Description) + + return err +} + +// List gets a list of all installed plugins +func (m *Manager) List(ctx context.Context) error { + s, err := m.list(ctx) + if err != nil { + return xerrors.Errorf("unable to list plugins: %w", err) + } + _, err = fmt.Fprintf(m.w, "%s\n", s) + return err +} + +func (m *Manager) list(ctx context.Context) (string, error) { + if _, err := os.Stat(m.pluginRoot); err != nil { + if os.IsNotExist(err) { + return "No Installed Plugins", nil + } + return "", xerrors.Errorf("stat error: %w", err) + } + plugins, err := m.LoadAll(ctx) + if err != nil { + return "", xerrors.Errorf("unable to load plugins: %w", err) + } else if len(plugins) == 0 { + return "No Installed Plugins", nil + } + pluginList := []string{"Installed Plugins:"} + for _, plugin := range plugins { + pluginList = append(pluginList, fmt.Sprintf(" Name: %s\n Version: %s\n", plugin.Name, plugin.Version)) + } + + return strings.Join(pluginList, "\n"), nil +} + +// Upgrade upgrades an existing plugins +func (m *Manager) Upgrade(ctx context.Context, names []string) error { + if len(names) == 0 { + plugins, err := m.LoadAll(ctx) + if err != nil { + return xerrors.Errorf("unable to load plugins: %w", err) + } else if len(plugins) == 0 { + m.logger.InfoContext(ctx, "No installed plugins") + return nil + } + names = lo.Map(plugins, func(p Plugin, _ int) string { return p.Name }) + } + for _, name := range names { + if err := m.upgrade(ctx, name); err != nil { + return xerrors.Errorf("unable to upgrade '%s' plugin: %w", name, err) + } + } + return nil +} + +func (m *Manager) upgrade(ctx context.Context, name string) error { + plugin, err := m.load(name) + if err != nil { + return xerrors.Errorf("plugin load error: %w", err) + } + + logger := m.logger.With("name", name) + logger.InfoContext(ctx, "Upgrading plugin...") + updated, err := m.install(ctx, plugin.Repository, Options{ + // Use the current installed platform + Platform: ftypes.Platform{ + Platform: &v1.Platform{ + OS: plugin.Installed.Platform.OS, + Architecture: plugin.Installed.Platform.Arch, + }, + }, + }) + if err != nil { + return xerrors.Errorf("unable to perform an upgrade installation: %w", err) + } + + if plugin.Version == updated.Version { + logger.InfoContext(ctx, "The plugin is up-to-date", log.String("version", plugin.Version)) + } else { + logger.InfoContext(ctx, "Plugin upgraded", + log.String("from", plugin.Version), log.String("to", updated.Version)) + } + return nil +} + +// LoadAll loads all plugins +func (m *Manager) LoadAll(ctx context.Context) ([]Plugin, error) { + dirs, err := os.ReadDir(m.pluginRoot) + if err != nil { + return nil, xerrors.Errorf("failed to read %s: %w", m.pluginRoot, err) + } + + var plugins []Plugin + for _, d := range dirs { + if !d.IsDir() { + continue + } + plugin, err := m.loadMetadata(filepath.Join(m.pluginRoot, d.Name())) + if err != nil { + m.logger.WarnContext(ctx, "Plugin load error", log.Err(err)) + continue + } + plugins = append(plugins, plugin) + } + return plugins, nil +} + +// Start starts the plugin +func (m *Manager) Start(ctx context.Context, name string, opts Options) (Wait, error) { + plugin, err := m.load(name) + if err != nil { + return nil, xerrors.Errorf("plugin load error: %w", err) + } + + wait, err := plugin.Start(ctx, opts) + if err != nil { + return nil, xerrors.Errorf("unable to run %s plugin: %w", plugin.Name, err) + } + return wait, nil +} + +// RunWithURL runs the plugin +func (m *Manager) RunWithURL(ctx context.Context, name string, opts Options) error { + plugin, err := m.Install(ctx, name, opts) + if err != nil { + return xerrors.Errorf("plugin install error: %w", err) + } + + if err = plugin.Run(ctx, opts); err != nil { + return xerrors.Errorf("unable to run %s plugin: %w", plugin.Name, err) + } + return nil +} + +func (m *Manager) load(name string) (Plugin, error) { + pluginDir := filepath.Join(m.pluginRoot, name) + if _, err := os.Stat(pluginDir); err != nil { + if os.IsNotExist(err) { + return Plugin{}, xerrors.Errorf("could not find a plugin called '%s', did you install it?", name) + } + return Plugin{}, xerrors.Errorf("plugin stat error: %w", err) + } + + plugin, err := m.loadMetadata(pluginDir) + if err != nil { + return Plugin{}, xerrors.Errorf("unable to load plugin metadata: %w", err) + } + + return plugin, nil +} + +func (m *Manager) loadMetadata(dir string) (Plugin, error) { + filePath := filepath.Join(dir, configFile) + f, err := os.Open(filePath) + if err != nil { + return Plugin{}, xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + var plugin Plugin + if err = yaml.NewDecoder(f).Decode(&plugin); err != nil { + return Plugin{}, xerrors.Errorf("yaml decode error: %w", err) + } + + if plugin.Name == "" { + return Plugin{}, xerrors.Errorf("'name' is empty") + } + + // e.g. ~/.trivy/plugins/kubectl + plugin.dir = filepath.Join(m.pluginRoot, plugin.Name) + + if plugin.Summary == "" && plugin.Usage != "" { + plugin.Summary = plugin.Usage // For backward compatibility + plugin.Usage = "" + } + + return plugin, nil +} + +func (m *Manager) isInstalled(ctx context.Context, url string) (Plugin, bool) { + installedPlugins, err := m.LoadAll(ctx) + if err != nil { + return Plugin{}, false + } + + for _, plugin := range installedPlugins { + if plugin.Repository == url { + return plugin, true + } + } + return Plugin{}, false +} diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/manager_test.go similarity index 56% rename from pkg/plugin/plugin_test.go rename to pkg/plugin/manager_test.go index d3f5aa1a0fec..958a320512d5 100644 --- a/pkg/plugin/plugin_test.go +++ b/pkg/plugin/manager_test.go @@ -1,11 +1,21 @@ package plugin_test import ( + "archive/zip" + "bytes" "context" + "github.com/aquasecurity/trivy/pkg/clock" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" + v1 "github.com/google/go-containerregistry/pkg/v1" + "log/slog" + "net/http" + "net/http/httptest" "os" "path/filepath" "runtime" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -14,7 +24,7 @@ import ( "github.com/aquasecurity/trivy/pkg/plugin" ) -func TestPlugin_Run(t *testing.T) { +func TestManager_Run(t *testing.T) { if runtime.GOOS == "windows" { // the test.sh script can't be run on windows so skipping t.Skip("Test satisfied adequately by Linux tests") @@ -23,7 +33,7 @@ func TestPlugin_Run(t *testing.T) { Name string Repository string Version string - Usage string + Summary string Description string Platforms []plugin.Platform GOOS string @@ -32,7 +42,7 @@ func TestPlugin_Run(t *testing.T) { tests := []struct { name string fields fields - opts plugin.RunOptions + opts plugin.Options wantErr string }{ { @@ -41,7 +51,7 @@ func TestPlugin_Run(t *testing.T) { Name: "test_plugin", Repository: "github.com/aquasecurity/trivy-plugin-test", Version: "0.1.0", - Usage: "test", + Summary: "test", Description: "test", Platforms: []plugin.Platform{ { @@ -63,7 +73,7 @@ func TestPlugin_Run(t *testing.T) { Name: "test_plugin", Repository: "github.com/aquasecurity/trivy-plugin-test", Version: "0.1.0", - Usage: "test", + Summary: "test", Description: "test", Platforms: []plugin.Platform{ { @@ -79,7 +89,7 @@ func TestPlugin_Run(t *testing.T) { Name: "test_plugin", Repository: "github.com/aquasecurity/trivy-plugin-test", Version: "0.1.0", - Usage: "test", + Summary: "test", Description: "test", Platforms: []plugin.Platform{ { @@ -102,7 +112,7 @@ func TestPlugin_Run(t *testing.T) { Name: "test_plugin", Repository: "github.com/aquasecurity/trivy-plugin-test", Version: "0.1.0", - Usage: "test", + Summary: "test", Description: "test", Platforms: []plugin.Platform{ { @@ -125,7 +135,7 @@ func TestPlugin_Run(t *testing.T) { Name: "error_plugin", Repository: "github.com/aquasecurity/trivy-plugin-error", Version: "0.1.0", - Usage: "test", + Summary: "test", Description: "test", Platforms: []plugin.Platform{ { @@ -145,24 +155,27 @@ func TestPlugin_Run(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - os.Setenv("XDG_DATA_HOME", "testdata") - defer os.Unsetenv("XDG_DATA_HOME") + t.Setenv("XDG_DATA_HOME", "testdata") p := plugin.Plugin{ Name: tt.fields.Name, Repository: tt.fields.Repository, Version: tt.fields.Version, - Usage: tt.fields.Usage, + Summary: tt.fields.Summary, Description: tt.fields.Description, Platforms: tt.fields.Platforms, - GOOS: tt.fields.GOOS, - GOARCH: tt.fields.GOARCH, } - err := p.Run(context.Background(), tt.opts) + err := p.Run(context.Background(), plugin.Options{ + Platform: ftypes.Platform{ + Platform: &v1.Platform{ + OS: "linux", + Architecture: "amd64", + }, + }, + }) if tt.wantErr != "" { - require.NotNil(t, err) - assert.Contains(t, err.Error(), tt.wantErr) + require.ErrorContains(t, err, tt.wantErr) return } assert.NoError(t, err) @@ -170,142 +183,145 @@ func TestPlugin_Run(t *testing.T) { } } -func TestInstall(t *testing.T) { +func TestManager_Install(t *testing.T) { if runtime.GOOS == "windows" { // the test.sh script can't be run on windows so skipping t.Skip("Test satisfied adequately by Linux tests") } + wantPlugin := plugin.Plugin{ + Name: "test_plugin", + Repository: "github.com/aquasecurity/trivy-plugin-test", + Version: "0.1.0", + Summary: "test", + Description: "test", + Platforms: []plugin.Platform{ + { + Selector: &plugin.Selector{ + OS: "linux", + Arch: "amd64", + }, + URI: "./test.sh", + Bin: "./test.sh", + }, + }, + Installed: plugin.Installed{ + Platform: plugin.Selector{ + OS: "linux", + Arch: "amd64", + }, + }, + } + tests := []struct { - name string - url string - want plugin.Plugin - wantFile string - wantErr string + name string + pluginName string + want plugin.Plugin + wantFile string + wantErr string }{ { - name: "happy path", - url: "testdata/test_plugin", - want: plugin.Plugin{ - Name: "test_plugin", - Repository: "github.com/aquasecurity/trivy-plugin-test", - Version: "0.1.0", - Usage: "test", - Description: "test", - Platforms: []plugin.Platform{ - { - Selector: &plugin.Selector{ - OS: "linux", - Arch: "amd64", - }, - URI: "./test.sh", - Bin: "./test.sh", - }, - }, - GOOS: "linux", - GOARCH: "amd64", - }, + name: "http", + want: wantPlugin, wantFile: ".trivy/plugins/test_plugin/test.sh", }, { - name: "plugin not found", - url: "testdata/not_found", - want: plugin.Plugin{ - Name: "test_plugin", - Repository: "github.com/aquasecurity/trivy-plugin-test", - Version: "0.1.0", - Usage: "test", - Description: "test", - Platforms: []plugin.Platform{ - { - Selector: &plugin.Selector{ - OS: "linux", - Arch: "amd64", - }, - URI: "./test.sh", - Bin: "./test.sh", - }, - }, - GOOS: "linux", - GOARCH: "amd64", - }, - wantErr: "no such file or directory", + name: "local path", + pluginName: "testdata/test_plugin", + want: wantPlugin, + wantFile: ".trivy/plugins/test_plugin/test.sh", }, { - name: "no plugin.yaml", - url: "testdata/no_yaml", - want: plugin.Plugin{ - Name: "no_yaml", - Repository: "github.com/aquasecurity/trivy-plugin-test", - Version: "0.1.0", - Usage: "test", - Description: "test", - Platforms: []plugin.Platform{ - { - Selector: &plugin.Selector{ - OS: "linux", - Arch: "amd64", - }, - URI: "./test.sh", - Bin: "./test.sh", - }, - }, - GOOS: "linux", - GOARCH: "amd64", - }, - wantErr: "file open error", + name: "index", + pluginName: "test", + want: wantPlugin, + wantFile: ".trivy/plugins/test_plugin/test.sh", + }, + { + name: "plugin not found", + pluginName: "testdata/not_found", + wantErr: "no such file or directory", + }, + { + name: "no plugin.yaml", + pluginName: "testdata/no_yaml", + wantErr: "file open error", }, } - log.InitLogger(false, true) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // The test plugin will be installed here dst := t.TempDir() - os.Setenv("XDG_DATA_HOME", dst) + t.Setenv("XDG_DATA_HOME", dst) + + // For plugin index + fsutils.SetCacheDir("testdata") + + if tt.pluginName == "" { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + zr := zip.NewWriter(w) + require.NoError(t, zr.AddFS(os.DirFS("testdata/test_plugin"))) + require.NoError(t, zr.Close()) + })) + t.Cleanup(ts.Close) + tt.pluginName = ts.URL + "/test_plugin.zip" + } - got, err := plugin.Install(context.Background(), tt.url, false) + got, err := plugin.NewManager().Install(context.Background(), tt.pluginName, plugin.Options{ + Platform: ftypes.Platform{ + Platform: &v1.Platform{ + Architecture: "amd64", + OS: "linux", + }, + }, + }) if tt.wantErr != "" { - require.NotNil(t, err) - assert.Contains(t, err.Error(), tt.wantErr) + require.ErrorContains(t, err, tt.wantErr) return } assert.NoError(t, err) - assert.Equal(t, tt.want, got) + assert.EqualExportedValues(t, tt.want, got) assert.FileExists(t, filepath.Join(dst, tt.wantFile)) }) } } -func TestUninstall(t *testing.T) { - if runtime.GOOS == "windows" { - // the test.sh script can't be run on windows so skipping - t.Skip("Test satisfied adequately by Linux tests") - } +func TestManager_Uninstall(t *testing.T) { + ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) pluginName := "test_plugin" tempDir := t.TempDir() + t.Setenv("XDG_DATA_HOME", tempDir) pluginDir := filepath.Join(tempDir, ".trivy", "plugins", pluginName) - // Create the test plugin directory - err := os.MkdirAll(pluginDir, os.ModePerm) - require.NoError(t, err) - - // Create the test file - err = os.WriteFile(filepath.Join(pluginDir, "test.sh"), []byte(`foo`), os.ModePerm) - require.NoError(t, err) - - // Uninstall the plugin - err = plugin.Uninstall(pluginName) - assert.NoError(t, err) - assert.NoFileExists(t, pluginDir) + t.Run("plugin found", func(t *testing.T) { + // Create the test plugin directory + err := os.MkdirAll(pluginDir, os.ModePerm) + require.NoError(t, err) + + // Create the test file + err = os.WriteFile(filepath.Join(pluginDir, "test.sh"), []byte(`foo`), os.ModePerm) + require.NoError(t, err) + + // Uninstall the plugin + err = plugin.NewManager().Uninstall(ctx, pluginName) + assert.NoError(t, err) + assert.NoDirExists(t, pluginDir) + }) + + t.Run("plugin not found", func(t *testing.T) { + t.Setenv("NO_COLOR", tempDir) + buf := bytes.NewBuffer(nil) + slog.SetDefault(slog.New(log.NewHandler(buf, &log.Options{Level: log.LevelInfo}))) + + err := plugin.NewManager().Uninstall(ctx, pluginName) + assert.NoError(t, err) + assert.Equal(t, "2021-08-25T12:20:30Z\tERROR\t[plugin] No such plugin\n", buf.String()) + }) } -func TestInformation(t *testing.T) { - if runtime.GOOS == "windows" { - // the test.sh script can't be run on windows so skipping - t.Skip("Test satisfied adequately by Linux tests") - } +func TestManager_Information(t *testing.T) { pluginName := "test_plugin" tempDir := t.TempDir() @@ -327,22 +343,27 @@ description: A simple test plugin` err = os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginMetadata), os.ModePerm) require.NoError(t, err) + var got bytes.Buffer + manager := plugin.NewManager(plugin.WithWriter(&got)) + // Get Information for the plugin - info, err := plugin.Information(pluginName) + err = manager.Information(pluginName) require.NoError(t, err) - assert.Equal(t, "\nPlugin: test_plugin\n Description: A simple test plugin\n Version: 0.1.0\n Usage: test\n", info) + assert.Equal(t, ` +Plugin: test_plugin + Version: 0.1.0 + Summary: test + Description: A simple test plugin +`, got.String()) + got.Reset() // Get Information for unknown plugin - info, err = plugin.Information("unknown") + err = manager.Information("unknown") require.Error(t, err) assert.ErrorContains(t, err, "could not find a plugin called 'unknown', did you install it?") } -func TestLoadAll1(t *testing.T) { - if runtime.GOOS == "windows" { - // the test.sh script can't be run on windows so skipping - t.Skip("Test satisfied adequately by Linux tests") - } +func TestManager_LoadAll(t *testing.T) { tests := []struct { name string dir string @@ -357,7 +378,7 @@ func TestLoadAll1(t *testing.T) { Name: "test_plugin", Repository: "github.com/aquasecurity/trivy-plugin-test", Version: "0.1.0", - Usage: "test", + Summary: "test", Description: "test", Platforms: []plugin.Platform{ { @@ -369,35 +390,34 @@ func TestLoadAll1(t *testing.T) { Bin: "./test.sh", }, }, - GOOS: "linux", - GOARCH: "amd64", }, }, }, { name: "sad path", dir: "sad", - wantErr: "no such file or directory", + wantErr: "failed to read", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - os.Setenv("XDG_DATA_HOME", tt.dir) - defer os.Unsetenv("XDG_DATA_HOME") + t.Setenv("XDG_DATA_HOME", tt.dir) - got, err := plugin.LoadAll() + got, err := plugin.NewManager().LoadAll(context.Background()) if tt.wantErr != "" { - require.NotNil(t, err) - assert.Contains(t, err.Error(), tt.wantErr) + require.ErrorContains(t, err, tt.wantErr) return } assert.NoError(t, err) - assert.Equal(t, tt.want, got) + require.Len(t, got, len(tt.want)) + for i := range tt.want { + assert.EqualExportedValues(t, tt.want[i], got[i]) + } }) } } -func TestUpdate(t *testing.T) { +func TestManager_Upgrade(t *testing.T) { if runtime.GOOS == "windows" { // the test.sh script can't be run on windows so skipping t.Skip("Test satisfied adequately by Linux tests") @@ -418,28 +438,35 @@ func TestUpdate(t *testing.T) { repository: testdata/test_plugin version: "0.0.5" usage: test -description: A simple test plugin` +description: A simple test plugin +installed: + platform: + os: linux + arch: amd64` err = os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginMetadata), os.ModePerm) require.NoError(t, err) + ctx := context.Background() + m := plugin.NewManager() + // verify initial version - verifyVersion(t, pluginName, "0.0.5") + verifyVersion(t, ctx, m, pluginName, "0.0.5") - // Update the existing plugin - err = plugin.Update(pluginName) + // Upgrade the existing plugin + err = m.Upgrade(ctx, nil) require.NoError(t, err) // verify plugin updated - verifyVersion(t, pluginName, "0.1.0") + verifyVersion(t, ctx, m, pluginName, "0.1.0") } -func verifyVersion(t *testing.T, pluginName, expectedVersion string) { - plugins, err := plugin.LoadAll() +func verifyVersion(t *testing.T, ctx context.Context, m *plugin.Manager, pluginName, expectedVersion string) { + plugins, err := m.LoadAll(ctx) require.NoError(t, err) - for _, plugin := range plugins { - if plugin.Name == pluginName { - assert.Equal(t, expectedVersion, plugin.Version) + for _, p := range plugins { + if p.Name == pluginName { + assert.Equal(t, expectedVersion, p.Version) } } } diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 11e46a4488a0..68a50ae31780 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -3,48 +3,41 @@ package plugin import ( "context" "errors" - "fmt" "io" "os" "os/exec" "path/filepath" "runtime" - "strings" + "github.com/samber/lo" "golang.org/x/xerrors" - "gopkg.in/yaml.v3" "github.com/aquasecurity/trivy/pkg/downloader" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) -const ( - configFile = "plugin.yaml" -) - -var ( - pluginsRelativeDir = filepath.Join(".trivy", "plugins") - - officialPlugins = map[string]string{ - "kubectl": "github.com/aquasecurity/trivy-plugin-kubectl", - "aqua": "github.com/aquasecurity/trivy-plugin-aqua", - } -) - // Plugin represents a plugin. type Plugin struct { Name string `yaml:"name"` Repository string `yaml:"repository"` Version string `yaml:"version"` - Usage string `yaml:"usage"` + Summary string `yaml:"summary"` + Usage string `yaml:"usage"` // Deprecated: Use summary instead Description string `yaml:"description"` Platforms []Platform `yaml:"platforms"` - // runtime environment for testability - GOOS string `yaml:"_goos"` - GOARCH string `yaml:"_goarch"` + // Installed holds the metadata about installation + Installed Installed `yaml:"installed"` + + // dir points to the directory where the plugin is installed + dir string +} + +type Installed struct { + Platform Selector `yaml:"platform"` } // Platform represents where the execution file exists per platform. @@ -56,22 +49,23 @@ type Platform struct { // Selector represents the environment. type Selector struct { - OS string - Arch string + OS string `yaml:"os"` + Arch string `yaml:"arch"` } -type RunOptions struct { - Args []string - Stdin io.Reader +type Options struct { + Args []string + Stdin io.Reader // For output plugin + Platform ftypes.Platform } -func (p Plugin) Cmd(ctx context.Context, opts RunOptions) (*exec.Cmd, error) { - platform, err := p.selectPlatform() +func (p *Plugin) Cmd(ctx context.Context, opts Options) (*exec.Cmd, error) { + platform, err := p.selectPlatform(ctx, opts) if err != nil { return nil, xerrors.Errorf("platform selection error: %w", err) } - execFile := filepath.Join(dir(), p.Name, platform.Bin) + execFile := filepath.Join(p.Dir(), platform.Bin) cmd := exec.CommandContext(ctx, execFile, opts.Args...) cmd.Stdin = os.Stdin @@ -90,7 +84,7 @@ type Wait func() error // Start starts the plugin // // After a successful call to Start the Wait method must be called. -func (p Plugin) Start(ctx context.Context, opts RunOptions) (Wait, error) { +func (p *Plugin) Start(ctx context.Context, opts Options) (Wait, error) { cmd, err := p.Cmd(ctx, opts) if err != nil { return nil, xerrors.Errorf("cmd: %w", err) @@ -103,7 +97,7 @@ func (p Plugin) Start(ctx context.Context, opts RunOptions) (Wait, error) { } // Run runs the plugin -func (p Plugin) Run(ctx context.Context, opts RunOptions) error { +func (p *Plugin) Run(ctx context.Context, opts Options) error { cmd, err := p.Cmd(ctx, opts) if err != nil { return xerrors.Errorf("cmd: %w", err) @@ -124,13 +118,15 @@ func (p Plugin) Run(ctx context.Context, opts RunOptions) error { return nil } -func (p Plugin) selectPlatform() (Platform, error) { +func (p *Plugin) selectPlatform(ctx context.Context, opts Options) (Platform, error) { // These values are only filled in during unit tests. - if p.GOOS == "" { - p.GOOS = runtime.GOOS + goos := runtime.GOOS + if opts.Platform.Platform != nil && opts.Platform.OS != "" { + goos = opts.Platform.OS } - if p.GOARCH == "" { - p.GOARCH = runtime.GOARCH + goarch := runtime.GOARCH + if opts.Platform.Platform != nil && opts.Platform.Architecture != "" { + goarch = opts.Platform.Architecture } for _, platform := range p.Platforms { @@ -139,9 +135,9 @@ func (p Plugin) selectPlatform() (Platform, error) { } selector := platform.Selector - if (selector.OS == "" || p.GOOS == selector.OS) && - (selector.Arch == "" || p.GOARCH == selector.Arch) { - log.Debug("Platform found", + if (selector.OS == "" || goos == selector.OS) && + (selector.Arch == "" || goarch == selector.Arch) { + log.DebugContext(ctx, "Platform found", log.String("os", selector.OS), log.String("arch", selector.Arch)) return platform, nil } @@ -149,240 +145,24 @@ func (p Plugin) selectPlatform() (Platform, error) { return Platform{}, xerrors.New("platform not found") } -func (p Plugin) install(ctx context.Context, dst, pwd string) error { - log.Debug("Installing the plugin...", log.String("path", dst)) - platform, err := p.selectPlatform() +func (p *Plugin) install(ctx context.Context, dst, pwd string, opts Options) error { + log.DebugContext(ctx, "Installing the plugin...", log.String("path", dst)) + platform, err := p.selectPlatform(ctx, opts) if err != nil { return xerrors.Errorf("platform selection error: %w", err) } + p.Installed.Platform = lo.FromPtr(platform.Selector) - log.Debug("Downloading the execution file...", log.String("uri", platform.URI)) + log.DebugContext(ctx, "Downloading the execution file...", log.String("uri", platform.URI)) if err = downloader.Download(ctx, platform.URI, dst, pwd); err != nil { return xerrors.Errorf("unable to download the execution file (%s): %w", platform.URI, err) } return nil } -func (p Plugin) dir() (string, error) { - if p.Name == "" { - return "", xerrors.Errorf("'name' is empty") - } - - // e.g. ~/.trivy/plugins/kubectl - return filepath.Join(dir(), p.Name), nil -} - -// Install installs a plugin -func Install(ctx context.Context, url string, force bool) (Plugin, error) { - // Replace short names with full qualified names - // e.g. kubectl => github.com/aquasecurity/trivy-plugin-kubectl - if v, ok := officialPlugins[url]; ok { - url = v - } - - if !force { - // If the plugin is already installed, it skips installing the plugin. - if p, installed := isInstalled(url); installed { - return p, nil - } - } - - log.Info("Installing the plugin...", log.String("url", url)) - tempDir, err := downloader.DownloadToTempDir(ctx, url) - if err != nil { - return Plugin{}, xerrors.Errorf("download failed: %w", err) - } - defer os.RemoveAll(tempDir) - - log.Info("Loading the plugin metadata...") - plugin, err := loadMetadata(tempDir) - if err != nil { - return Plugin{}, xerrors.Errorf("failed to load the plugin metadata: %w", err) - } - - pluginDir, err := plugin.dir() - if err != nil { - return Plugin{}, xerrors.Errorf("failed to determine the plugin dir: %w", err) - } - - if err = plugin.install(ctx, pluginDir, tempDir); err != nil { - return Plugin{}, xerrors.Errorf("failed to install the plugin: %w", err) - } - - // Copy plugin.yaml into the plugin dir - if _, err = fsutils.CopyFile(filepath.Join(tempDir, configFile), filepath.Join(pluginDir, configFile)); err != nil { - return Plugin{}, xerrors.Errorf("failed to copy plugin.yaml: %w", err) - } - - return plugin, nil -} - -// Uninstall installs the plugin -func Uninstall(name string) error { - pluginDir := filepath.Join(dir(), name) - return os.RemoveAll(pluginDir) -} - -// Information gets the information about an installed plugin -func Information(name string) (string, error) { - plugin, err := load(name) - if err != nil { - return "", xerrors.Errorf("plugin load error: %w", err) - } - - return fmt.Sprintf(` -Plugin: %s - Description: %s - Version: %s - Usage: %s -`, plugin.Name, plugin.Description, plugin.Version, plugin.Usage), nil -} - -// List gets a list of all installed plugins -func List() (string, error) { - if _, err := os.Stat(dir()); err != nil { - if os.IsNotExist(err) { - return "No Installed Plugins\n", nil - } - return "", xerrors.Errorf("stat error: %w", err) - } - plugins, err := LoadAll() - if err != nil { - return "", xerrors.Errorf("unable to load plugins: %w", err) - } - pluginList := []string{"Installed Plugins:"} - for _, plugin := range plugins { - pluginList = append(pluginList, fmt.Sprintf(" Name: %s\n Version: %s\n", plugin.Name, plugin.Version)) - } - - return strings.Join(pluginList, "\n"), nil -} - -// Update updates an existing plugin -func Update(name string) error { - plugin, err := load(name) - if err != nil { - return xerrors.Errorf("plugin load error: %w", err) - } - - logger := log.With("name", name) - logger.Info("Updating plugin...") - updated, err := Install(nil, plugin.Repository, true) - if err != nil { - return xerrors.Errorf("unable to perform an update installation: %w", err) - } - - if plugin.Version == updated.Version { - logger.Info("The plugin is up-to-date", log.String("version", plugin.Version)) - } else { - logger.Info("Plugin updated", - log.String("from", plugin.Version), log.String("to", updated.Version)) - } - return nil -} - -// LoadAll loads all plugins -func LoadAll() ([]Plugin, error) { - pluginsDir := dir() - dirs, err := os.ReadDir(pluginsDir) - if err != nil { - return nil, xerrors.Errorf("failed to read %s: %w", pluginsDir, err) - } - - var plugins []Plugin - for _, d := range dirs { - if !d.IsDir() { - continue - } - plugin, err := loadMetadata(filepath.Join(pluginsDir, d.Name())) - if err != nil { - log.Warn("Plugin load error", log.Err(err)) - continue - } - plugins = append(plugins, plugin) - } - return plugins, nil -} - -// Start starts the plugin -func Start(ctx context.Context, name string, opts RunOptions) (Wait, error) { - plugin, err := load(name) - if err != nil { - return nil, xerrors.Errorf("plugin load error: %w", err) - } - - wait, err := plugin.Start(ctx, opts) - if err != nil { - return nil, xerrors.Errorf("unable to run %s plugin: %w", plugin.Name, err) - } - return wait, nil -} - -// RunWithURL runs the plugin with URL -func RunWithURL(ctx context.Context, url string, opts RunOptions) error { - plugin, err := Install(ctx, url, false) - if err != nil { - return xerrors.Errorf("plugin install error: %w", err) - } - - if err = plugin.Run(ctx, opts); err != nil { - return xerrors.Errorf("unable to run %s plugin: %w", plugin.Name, err) - } - return nil -} - -func IsPredefined(name string) bool { - _, ok := officialPlugins[name] - return ok -} - -func load(name string) (Plugin, error) { - pluginDir := filepath.Join(dir(), name) - if _, err := os.Stat(pluginDir); err != nil { - if os.IsNotExist(err) { - return Plugin{}, xerrors.Errorf("could not find a plugin called '%s', did you install it?", name) - } - return Plugin{}, xerrors.Errorf("plugin stat error: %w", err) - } - - plugin, err := loadMetadata(pluginDir) - if err != nil { - return Plugin{}, xerrors.Errorf("unable to load plugin metadata: %w", err) - } - - return plugin, nil -} - -func loadMetadata(dir string) (Plugin, error) { - filePath := filepath.Join(dir, configFile) - f, err := os.Open(filePath) - if err != nil { - return Plugin{}, xerrors.Errorf("file open error: %w", err) - } - defer f.Close() - - var plugin Plugin - if err = yaml.NewDecoder(f).Decode(&plugin); err != nil { - return Plugin{}, xerrors.Errorf("yaml decode error: %w", err) - } - - return plugin, nil -} - -func dir() string { - return filepath.Join(fsutils.HomeDir(), pluginsRelativeDir) -} - -func isInstalled(url string) (Plugin, bool) { - installedPlugins, err := LoadAll() - if err != nil { - return Plugin{}, false - } - - for _, plugin := range installedPlugins { - if plugin.Repository == url { - return plugin, true - } +func (p *Plugin) Dir() string { + if p.dir != "" { + return p.dir } - return Plugin{}, false + return filepath.Join(fsutils.HomeDir(), pluginsRelativeDir, p.Name) } diff --git a/pkg/plugin/testdata/plugin/index.yaml b/pkg/plugin/testdata/plugin/index.yaml new file mode 100644 index 000000000000..17fbb1987939 --- /dev/null +++ b/pkg/plugin/testdata/plugin/index.yaml @@ -0,0 +1,15 @@ +version: 1 +plugins: + - name: foo + output: true + maintainer: aquasecurity + summary: A foo plugin + repository: github.com/aquasecurity/trivy-plugin-foo + - name: bar + maintainer: aquasecurity + summary: A bar plugin + repository: github.com/aquasecurity/trivy-plugin-bar + - name: test + maintainer: aquasecurity + summary: A test plugin + repository: testdata/test_plugin \ No newline at end of file diff --git a/pkg/plugin/testdata/test_plugin/plugin.yaml b/pkg/plugin/testdata/test_plugin/plugin.yaml index 7c2021b29196..272c8d5760a7 100644 --- a/pkg/plugin/testdata/test_plugin/plugin.yaml +++ b/pkg/plugin/testdata/test_plugin/plugin.yaml @@ -1,7 +1,7 @@ name: "test_plugin" repository: github.com/aquasecurity/trivy-plugin-test version: "0.1.0" -usage: test +summary: test description: test platforms: - selector: diff --git a/pkg/utils/fsutils/fs.go b/pkg/utils/fsutils/fs.go index 915581f08ad9..6d0502d8cc18 100644 --- a/pkg/utils/fsutils/fs.go +++ b/pkg/utils/fsutils/fs.go @@ -1,6 +1,7 @@ package fsutils import ( + "errors" "fmt" "io" "io/fs" @@ -84,6 +85,14 @@ func DirExists(path string) bool { return true } +func FileExists(filename string) bool { + _, err := os.Stat(filename) + if errors.Is(err, os.ErrNotExist) { + return false + } + return err == nil +} + type WalkDirRequiredFunc func(path string, d fs.DirEntry) bool type WalkDirFunc func(path string, d fs.DirEntry, r io.Reader) error From fa3cf993eace4be793f85907b42365269c597b91 Mon Sep 17 00:00:00 2001 From: Kristina Trotsko Date: Tue, 14 May 2024 11:34:31 +0200 Subject: [PATCH 063/352] feat(report): Include licenses and secrets filtered by rego to ModifiedFindings (#6483) --- pkg/result/filter.go | 4 ++ pkg/result/filter_test.go | 65 ++++++------------- ...st-ignore-policy-licenses-and-secrets.rego | 4 +- 3 files changed, 27 insertions(+), 46 deletions(-) diff --git a/pkg/result/filter.go b/pkg/result/filter.go index dc92d8aff5ec..6e92dbd3de98 100644 --- a/pkg/result/filter.go +++ b/pkg/result/filter.go @@ -303,6 +303,8 @@ func applyPolicy(ctx context.Context, result *types.Result, policyFile string) e return err } if ignored { + result.ModifiedFindings = append(result.ModifiedFindings, + types.NewModifiedFinding(scrt, types.FindingStatusIgnored, "Filtered by Rego", policyFile)) continue } filteredSecrets = append(filteredSecrets, scrt) @@ -317,6 +319,8 @@ func applyPolicy(ctx context.Context, result *types.Result, policyFile string) e return err } if ignored { + result.ModifiedFindings = append(result.ModifiedFindings, + types.NewModifiedFinding(lic, types.FindingStatusIgnored, "Filtered by Rego", policyFile)) continue } filteredLicenses = append(filteredLicenses, lic) diff --git a/pkg/result/filter_test.go b/pkg/result/filter_test.go index dec620817afe..740caa6693d0 100644 --- a/pkg/result/filter_test.go +++ b/pkg/result/filter_test.go @@ -648,65 +648,42 @@ func TestFilter(t *testing.T) { Results: types.Results{ { Licenses: []types.DetectedLicense{ - { - Name: "GPL-3.0", - Severity: dbTypes.SeverityLow.String(), - FilePath: "usr/share/gcc/python/libstdcxx/v6/__init__.py", - Category: "restricted", - Confidence: 1, - }, - { - Name: "GPL-3.0", - Severity: dbTypes.SeverityLow.String(), - FilePath: "usr/share/gcc/python/libstdcxx/v6/printers.py", - Category: "restricted", - Confidence: 1, - }, + license1, + license2, }, Secrets: []types.DetectedSecret{ - { - RuleID: "generic-passed-rule", - Severity: dbTypes.SeverityLow.String(), - Title: "Secret should pass filter", - StartLine: 1, - EndLine: 2, - Match: "*****", - }, - { - RuleID: "generic-ignored-rule", - Severity: dbTypes.SeverityLow.String(), - Title: "Secret should be ignored", - StartLine: 3, - EndLine: 4, - Match: "*****", - }, + secret1, + secret2, }, }, }, }, - severities: []dbTypes.Severity{dbTypes.SeverityLow}, + severities: []dbTypes.Severity{dbTypes.SeverityLow, dbTypes.SeverityHigh}, policyFile: "./testdata/test-ignore-policy-licenses-and-secrets.rego", }, want: types.Report{ Results: types.Results{ { Licenses: []types.DetectedLicense{ - { - Name: "GPL-3.0", - Severity: dbTypes.SeverityLow.String(), - FilePath: "usr/share/gcc/python/libstdcxx/v6/__init__.py", - Category: "restricted", - Confidence: 1, - }, + license1, }, Secrets: []types.DetectedSecret{ + secret1, + }, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeSecret, + Status: types.FindingStatusIgnored, + Statement: "Filtered by Rego", + Source: "testdata/test-ignore-policy-licenses-and-secrets.rego", + Finding: secret2, + }, { - RuleID: "generic-passed-rule", - Severity: dbTypes.SeverityLow.String(), - Title: "Secret should pass filter", - StartLine: 1, - EndLine: 2, - Match: "*****", + Type: types.FindingTypeLicense, + Status: types.FindingStatusIgnored, + Statement: "Filtered by Rego", + Source: "testdata/test-ignore-policy-licenses-and-secrets.rego", + Finding: license2, }, }, }, diff --git a/pkg/result/testdata/test-ignore-policy-licenses-and-secrets.rego b/pkg/result/testdata/test-ignore-policy-licenses-and-secrets.rego index b53c16a11ffa..59cae4b8ca5b 100644 --- a/pkg/result/testdata/test-ignore-policy-licenses-and-secrets.rego +++ b/pkg/result/testdata/test-ignore-policy-licenses-and-secrets.rego @@ -10,6 +10,6 @@ ignore { } ignore { - input.RuleID == "generic-ignored-rule" - input.Title == "Secret should be ignored" + input.RuleID == "generic-unwanted-rule" + input.Title == "Secret that should not pass filter on rule id" } From 3d388d8552ef42d4d54176309a38c1879008527b Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 14 May 2024 15:43:03 +0600 Subject: [PATCH 064/352] fix(report): hide empty tables if all vulns has been filtered (#6352) --- pkg/report/table/vulnerability.go | 14 +++- pkg/report/table/vulnerability_test.go | 105 +++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 4 deletions(-) diff --git a/pkg/report/table/vulnerability.go b/pkg/report/table/vulnerability.go index a54809bc80da..6284993be5af 100644 --- a/pkg/report/table/vulnerability.go +++ b/pkg/report/table/vulnerability.go @@ -52,10 +52,16 @@ func NewVulnerabilityRenderer(result types.Result, isTerminal, tree, suppressed } func (r *vulnerabilityRenderer) Render() string { - r.renderDetectedVulnerabilities() - - if r.tree { - r.renderDependencyTree() + // There are 3 cases when we show the vulnerability table (or only target and `Total: 0...`): + // When Result contains vulnerabilities; + // When Result target is OS packages even if no vulnerabilities are found; + // When we show non-empty `Suppressed Vulnerabilities` table. + if len(r.result.Vulnerabilities) > 0 || r.result.Class == types.ClassOSPkg || (r.showSuppressed && len(r.result.ModifiedFindings) > 0) { + r.renderDetectedVulnerabilities() + + if r.tree { + r.renderDependencyTree() + } } if r.showSuppressed { diff --git a/pkg/report/table/vulnerability_test.go b/pkg/report/table/vulnerability_test.go index b7fbc5bce1a5..9a773c90eafe 100644 --- a/pkg/report/table/vulnerability_test.go +++ b/pkg/report/table/vulnerability_test.go @@ -396,6 +396,111 @@ Suppressed Vulnerabilities (Total: 1) ├─────────┼───────────────┼──────────┼─────────┼─────────────────┼───────────────────┤ │ bar │ CVE-2020-0002 │ MEDIUM │ ignored │ Not exploitable │ .trivyignore.yaml │ └─────────┴───────────────┴──────────┴─────────┴─────────────────┴───────────────────┘ +`, + }, + { + name: "suppressed all OS package vulnerabilities without `showSuppressed` flag", + result: types.Result{ + Target: "test", + Class: types.ClassOSPkg, + Type: ftypes.Alpine, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Statement: "Not exploitable", + Source: ".trivyignore.yaml", + Finding: types.DetectedVulnerability{ + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + Status: dbTypes.StatusWillNotFix, + Vulnerability: dbTypes.Vulnerability{ + Title: "title1", + Description: "desc1", + Severity: "MEDIUM", + }, + }, + }, + }, + }, + showSuppressed: false, + want: ` +test +==== +Total: 0 (MEDIUM: 0, HIGH: 0) + +`, + }, + { + name: "suppressed all language package vulnerabilities without `showSuppressed` flag", + result: types.Result{ + Target: "test", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Statement: "Not exploitable", + Source: ".trivyignore.yaml", + Finding: types.DetectedVulnerability{ + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + Status: dbTypes.StatusWillNotFix, + Vulnerability: dbTypes.Vulnerability{ + Title: "title1", + Description: "desc1", + Severity: "MEDIUM", + }, + }, + }, + }, + }, + showSuppressed: false, + want: ``, + }, + { + name: "suppressed all language package vulnerabilities with `showSuppressed` flag", + result: types.Result{ + Target: "test", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + ModifiedFindings: []types.ModifiedFinding{ + { + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusIgnored, + Statement: "Not exploitable", + Source: ".trivyignore.yaml", + Finding: types.DetectedVulnerability{ + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + Status: dbTypes.StatusWillNotFix, + Vulnerability: dbTypes.Vulnerability{ + Title: "title1", + Description: "desc1", + Severity: "MEDIUM", + }, + }, + }, + }, + }, + showSuppressed: true, + want: ` +test (jar) +========== +Total: 0 (MEDIUM: 0, HIGH: 0) + + +Suppressed Vulnerabilities (Total: 1) +===================================== +┌─────────┬───────────────┬──────────┬─────────┬─────────────────┬───────────────────┐ +│ Library │ Vulnerability │ Severity │ Status │ Statement │ Source │ +├─────────┼───────────────┼──────────┼─────────┼─────────────────┼───────────────────┤ +│ foo │ CVE-2020-0001 │ MEDIUM │ ignored │ Not exploitable │ .trivyignore.yaml │ +└─────────┴───────────────┴──────────┴─────────┴─────────────────┴───────────────────┘ `, }, } From 7c22ee3df5ee51beb90e44428a99541b3d19ab98 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 15 May 2024 00:06:58 +0700 Subject: [PATCH 065/352] feat(misconf): register builtin Rego funcs from trivy-checks (#6616) --- go.mod | 3 ++- go.sum | 14 ++++++++------ pkg/iac/rego/custom.go | 5 +++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index ea55d6a7e25a..f2f0dcfbac45 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334 github.com/aquasecurity/tml v0.6.1 github.com/aquasecurity/trivy-aws v0.8.0 - github.com/aquasecurity/trivy-checks v0.10.5-0.20240430045208-6cc735de6b9e + github.com/aquasecurity/trivy-checks v0.10.5-0.20240514040354-93bcb2f8c233 github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240507080745-f6c5fb0a3f3f @@ -424,6 +424,7 @@ require ( modernc.org/memory v1.8.0 // indirect modernc.org/strutil v1.2.0 // indirect modernc.org/token v1.1.0 // indirect + mvdan.cc/sh/v3 v3.8.0 // indirect oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect diff --git a/go.sum b/go.sum index b75ba80f6db3..1ea2d226a903 100644 --- a/go.sum +++ b/go.sum @@ -775,8 +775,8 @@ github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gw github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= github.com/aquasecurity/trivy-aws v0.8.0 h1:4ij8MiZ2sJUH+vWpSeoGVhPr109ZBcNp7LNLfPuv5Cw= github.com/aquasecurity/trivy-aws v0.8.0/go.mod h1:Pb9xqOuTKMHVgjsnjvudjqZh3nmzdFqFVfRkXnoIZBM= -github.com/aquasecurity/trivy-checks v0.10.5-0.20240430045208-6cc735de6b9e h1:s0P4VeCqb7tWw06/L1cZ5/42AWy6VZFuLZ96THPJmmM= -github.com/aquasecurity/trivy-checks v0.10.5-0.20240430045208-6cc735de6b9e/go.mod h1:UIFQxYlKcL7EGhNVicFmZ6XxZ2UpFZU7bNKEv/Y/6XM= +github.com/aquasecurity/trivy-checks v0.10.5-0.20240514040354-93bcb2f8c233 h1:7TnJS1JEmrNfznu1Y9Rzbboxl7J4hxjIKQ8tV3k5UQs= +github.com/aquasecurity/trivy-checks v0.10.5-0.20240514040354-93bcb2f8c233/go.mod h1:+G8Ft1pJAmsSPzfSQHdSQ5zcWHWPOxVdQHHA+eHP3eU= github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTUAkbGqpzt9nJsO24RAdfG+ZSiLFj0G2jO8c= github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= @@ -1107,8 +1107,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0q github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/csaf-poc/csaf_distribution/v3 v3.0.0 h1:ob9+Fmpff0YWgTP3dYaw7G2hKQ9cegh9l3zksc+q3sM= github.com/csaf-poc/csaf_distribution/v3 v3.0.0/go.mod h1:uilCTiNKivq+6zrDvjtZaUeLk70oe21iwKivo6ILwlQ= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= @@ -1990,8 +1990,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -3192,6 +3192,8 @@ modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= +mvdan.cc/sh/v3 v3.8.0 h1:ZxuJipLZwr/HLbASonmXtcvvC9HXY9d2lXZHnKGjFc8= +mvdan.cc/sh/v3 v3.8.0/go.mod h1:w04623xkgBVo7/IUK89E0g8hBykgEpN0vgOj3RJr6MY= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkg/iac/rego/custom.go b/pkg/iac/rego/custom.go index c15b05a4577f..9de17beeadea 100644 --- a/pkg/iac/rego/custom.go +++ b/pkg/iac/rego/custom.go @@ -4,9 +4,14 @@ import ( "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/rego" "github.com/open-policy-agent/opa/types" + + checksrego "github.com/aquasecurity/trivy-checks/pkg/rego" ) func init() { + + checksrego.RegisterBuiltins() + rego.RegisterBuiltin2(®o.Function{ Name: "result.new", Decl: types.NewFunction(types.Args(types.S, types.A), types.A), From fecafb1fc5bb129c7485342a0775f0dd8bedd28e Mon Sep 17 00:00:00 2001 From: Octogonapus Date: Tue, 14 May 2024 22:56:48 -0400 Subject: [PATCH 066/352] feat: Add Julia language analyzer support (#5635) --- .github/workflows/semantic-pr.yaml | 1 + docs/community/contribute/pr.md | 1 + docs/docs/coverage/language/index.md | 1 + docs/docs/coverage/language/julia.md | 24 ++ integration/repo_test.go | 9 + .../fixtures/repo/julia/Manifest.toml | 16 ++ .../testdata/fixtures/repo/julia/Project.toml | 7 + integration/testdata/julia-spdx.json.golden | 138 ++++++++++++ mkdocs.yml | 1 + pkg/dependency/parser/julia/manifest/parse.go | 5 +- .../parser/julia/manifest/parse_test.go | 6 + .../parser/julia/manifest/parse_testcase.go | 15 ++ .../testdata/julia_v1.0_format/Manifest.toml | 28 +++ .../testdata/julia_v1.0_format/Project.toml | 6 + pkg/detector/library/driver.go | 3 + pkg/fanal/analyzer/all/import.go | 1 + pkg/fanal/analyzer/const.go | 4 + pkg/fanal/analyzer/language/julia/pkg/pkg.go | 190 ++++++++++++++++ .../analyzer/language/julia/pkg/pkg_test.go | 209 ++++++++++++++++++ .../pkg/testdata/dep_ext_v1.9/Manifest.toml | 16 ++ .../pkg/testdata/dep_ext_v1.9/Project.toml | 9 + .../julia/pkg/testdata/happy/Manifest.toml | 72 ++++++ .../julia/pkg/testdata/happy/Project.toml | 17 ++ .../pkg/testdata/no_deps_v1.6/Manifest.toml | 2 + .../pkg/testdata/no_deps_v1.6/Project.toml | 3 + .../pkg/testdata/no_manifest/Project.toml | 2 + .../testdata/shadowed_dep_v1.9/Manifest.toml | 16 ++ .../testdata/shadowed_dep_v1.9/Project.toml | 7 + pkg/fanal/types/const.go | 4 + pkg/purl/purl.go | 18 ++ pkg/purl/purl_test.go | 20 ++ 31 files changed, 849 insertions(+), 2 deletions(-) create mode 100644 docs/docs/coverage/language/julia.md create mode 100644 integration/testdata/fixtures/repo/julia/Manifest.toml create mode 100644 integration/testdata/fixtures/repo/julia/Project.toml create mode 100644 integration/testdata/julia-spdx.json.golden create mode 100644 pkg/dependency/parser/julia/manifest/testdata/julia_v1.0_format/Manifest.toml create mode 100644 pkg/dependency/parser/julia/manifest/testdata/julia_v1.0_format/Project.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/pkg.go create mode 100644 pkg/fanal/analyzer/language/julia/pkg/pkg_test.go create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Manifest.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Project.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Manifest.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Project.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Manifest.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Project.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/no_manifest/Project.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Manifest.toml create mode 100644 pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Project.toml diff --git a/.github/workflows/semantic-pr.yaml b/.github/workflows/semantic-pr.yaml index ead9c4cccdd8..5db6bd3a76a8 100644 --- a/.github/workflows/semantic-pr.yaml +++ b/.github/workflows/semantic-pr.yaml @@ -77,6 +77,7 @@ jobs: swift bitnami conda + julia os lang diff --git a/docs/community/contribute/pr.md b/docs/community/contribute/pr.md index 072d7358b8c9..0f7cc70ec22e 100644 --- a/docs/community/contribute/pr.md +++ b/docs/community/contribute/pr.md @@ -143,6 +143,7 @@ language: - go - elixir - dart +- julia vuln: diff --git a/docs/docs/coverage/language/index.md b/docs/docs/coverage/language/index.md index 470250216eea..eb694bbcc228 100644 --- a/docs/docs/coverage/language/index.md +++ b/docs/docs/coverage/language/index.md @@ -47,6 +47,7 @@ On the other hand, when the target is a post-build artifact, like a container im | [Dart](dart.md) | pubspec.lock | - | - | ✅ | ✅ | | [Swift](swift.md) | Podfile.lock | - | - | ✅ | ✅ | | | Package.resolved | - | - | ✅ | ✅ | +| [Julia](julia.md) | Manifest.toml | ✅ | ✅ | ✅ | ✅ | The path of these files does not matter. diff --git a/docs/docs/coverage/language/julia.md b/docs/docs/coverage/language/julia.md new file mode 100644 index 000000000000..3817dacfeb1b --- /dev/null +++ b/docs/docs/coverage/language/julia.md @@ -0,0 +1,24 @@ +# Julia + +## Features + +Trivy supports [Pkg.jl](https://pkgdocs.julialang.org/v1/), which is the Julia package manager. +The following table provides an outline of the features Trivy offers. + +| Package manager | File | Transitive dependencies | Dev dependencies | License | Dependency graph | Position | +| --------------- | ------------- | :---------------------: | :--------------- | :-----: | :--------------: | :------: | +| Pkg.jl | Manifest.toml | ✅ | Excluded[^1] | - | ✅ | ✅ | + +### Pkg.jl + +Trivy searches for `Manifest.toml` to detect dependencies. + +Trivy also supports dependency trees; however, to display an accurate tree, it needs to know whether each package is a direct dependency of the project. +Since this information is not included in `Manifest.toml`, Trivy parses `Project.toml`, which should be located next to `Project.toml`. +If you want to see the dependency tree, please ensure that `Project.toml` is present. + +Scanning `Manifest.toml` and `Project.toml` together also removes developer dependencies. + +Dependency extensions are currently ignored. + +[^1]: When you scan `Manifest.toml` and `Project.toml` together. diff --git a/integration/repo_test.go b/integration/repo_test.go index 72edba8a562f..3bc94416710a 100644 --- a/integration/repo_test.go +++ b/integration/repo_test.go @@ -397,6 +397,15 @@ func TestRepository(t *testing.T) { want.ArtifactType = artifact.TypeFilesystem }, }, + { + name: "julia generating SPDX SBOM", + args: args{ + command: "rootfs", + format: "spdx-json", + input: "testdata/fixtures/repo/julia", + }, + golden: "testdata/julia-spdx.json.golden", + }, } // Set up testing DB diff --git a/integration/testdata/fixtures/repo/julia/Manifest.toml b/integration/testdata/fixtures/repo/julia/Manifest.toml new file mode 100644 index 000000000000..dd4ea00b943d --- /dev/null +++ b/integration/testdata/fixtures/repo/julia/Manifest.toml @@ -0,0 +1,16 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "f0a796fb78285c02ad123fec6e14c8bac09a2ccc" + +[[deps.A]] +uuid = "ead4f63c-334e-11e9-00e6-e7f0a5f21b60" + + [deps.A.deps] + B = "f41f7b98-334e-11e9-1257-49272045fb24" + +[[deps.B]] +uuid = "f41f7b98-334e-11e9-1257-49272045fb24" +[[deps.B]] +uuid = "edca9bc6-334e-11e9-3554-9595dbb4349c" diff --git a/integration/testdata/fixtures/repo/julia/Project.toml b/integration/testdata/fixtures/repo/julia/Project.toml new file mode 100644 index 000000000000..24fe6178c480 --- /dev/null +++ b/integration/testdata/fixtures/repo/julia/Project.toml @@ -0,0 +1,7 @@ +name = "packageName" +uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773" +version = "0.1.0" + +[deps] +A = "ead4f63c-334e-11e9-00e6-e7f0a5f21b60" +B = "edca9bc6-334e-11e9-3554-9595dbb4349c" diff --git a/integration/testdata/julia-spdx.json.golden b/integration/testdata/julia-spdx.json.golden new file mode 100644 index 000000000000..483991784365 --- /dev/null +++ b/integration/testdata/julia-spdx.json.golden @@ -0,0 +1,138 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "testdata/fixtures/repo/julia", + "documentNamespace": "http://aquasecurity.github.io/trivy/filesystem/testdata/fixtures/repo/julia-3ff14136-e09f-4df9-80ea-000000000006", + "creationInfo": { + "creators": [ + "Organization: aquasecurity", + "Tool: trivy-dev" + ], + "created": "2021-08-25T12:20:30Z" + }, + "packages": [ + { + "name": "Manifest.toml", + "SPDXID": "SPDXRef-Application-18fc3597717a3e56", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "attributionTexts": [ + "Class: lang-pkgs", + "Type: julia" + ], + "primaryPackagePurpose": "APPLICATION" + }, + { + "name": "A", + "SPDXID": "SPDXRef-Package-2a46714189f3b9de", + "versionInfo": "1.9.0", + "supplier": "NOASSERTION", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "sourceInfo": "package found in: Manifest.toml", + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:julia/A@1.9.0?uuid=ead4f63c-334e-11e9-00e6-e7f0a5f21b60" + } + ], + "attributionTexts": [ + "PkgID: ead4f63c-334e-11e9-00e6-e7f0a5f21b60", + "PkgType: julia" + ], + "primaryPackagePurpose": "LIBRARY" + }, + { + "name": "B", + "SPDXID": "SPDXRef-Package-4a8e351c4c9b7318", + "versionInfo": "1.9.0", + "supplier": "NOASSERTION", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "sourceInfo": "package found in: Manifest.toml", + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:julia/B@1.9.0?uuid=edca9bc6-334e-11e9-3554-9595dbb4349c" + } + ], + "attributionTexts": [ + "PkgID: edca9bc6-334e-11e9-3554-9595dbb4349c", + "PkgType: julia" + ], + "primaryPackagePurpose": "LIBRARY" + }, + { + "name": "B", + "SPDXID": "SPDXRef-Package-d10d5e4a30a43fff", + "versionInfo": "1.9.0", + "supplier": "NOASSERTION", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "sourceInfo": "package found in: Manifest.toml", + "licenseConcluded": "NONE", + "licenseDeclared": "NONE", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:julia/B@1.9.0?uuid=f41f7b98-334e-11e9-1257-49272045fb24" + } + ], + "attributionTexts": [ + "PkgID: f41f7b98-334e-11e9-1257-49272045fb24", + "PkgType: julia" + ], + "primaryPackagePurpose": "LIBRARY" + }, + { + "name": "testdata/fixtures/repo/julia", + "SPDXID": "SPDXRef-Filesystem-1be792dd0077c431", + "downloadLocation": "NONE", + "filesAnalyzed": false, + "attributionTexts": [ + "SchemaVersion: 2" + ], + "primaryPackagePurpose": "SOURCE" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Application-18fc3597717a3e56", + "relatedSpdxElement": "SPDXRef-Package-2a46714189f3b9de", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-Application-18fc3597717a3e56", + "relatedSpdxElement": "SPDXRef-Package-4a8e351c4c9b7318", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-Application-18fc3597717a3e56", + "relatedSpdxElement": "SPDXRef-Package-d10d5e4a30a43fff", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-Filesystem-1be792dd0077c431", + "relationshipType": "DESCRIBES" + }, + { + "spdxElementId": "SPDXRef-Filesystem-1be792dd0077c431", + "relatedSpdxElement": "SPDXRef-Application-18fc3597717a3e56", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-Package-2a46714189f3b9de", + "relatedSpdxElement": "SPDXRef-Package-d10d5e4a30a43fff", + "relationshipType": "DEPENDS_ON" + } + ] +} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 9fb769a7c921..0a7947b4d703 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -102,6 +102,7 @@ nav: - Ruby: docs/coverage/language/ruby.md - Rust: docs/coverage/language/rust.md - Swift: docs/coverage/language/swift.md + - Julia: docs/coverage/language/julia.md - IaC: - Overview: docs/coverage/iac/index.md - Azure ARM Template: docs/coverage/iac/azure-arm.md diff --git a/pkg/dependency/parser/julia/manifest/parse.go b/pkg/dependency/parser/julia/manifest/parse.go index 061676d3f599..13d1cb208bcb 100644 --- a/pkg/dependency/parser/julia/manifest/parse.go +++ b/pkg/dependency/parser/julia/manifest/parse.go @@ -36,8 +36,9 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc var primMan primitiveManifest var manMetadata toml.MetaData decoder := toml.NewDecoder(r) - // Try to read the old Manifest format. If that fails, try the new format. - if _, err := decoder.Decode(&oldDeps); err != nil { + // Try to read the old Manifest format. This can also read the v1.0 Manifest format, which we parse out later. + var err error + if manMetadata, err = decoder.Decode(&oldDeps); err != nil { if _, err = r.Seek(0, io.SeekStart); err != nil { return nil, nil, xerrors.Errorf("seek error: %w", err) } diff --git a/pkg/dependency/parser/julia/manifest/parse_test.go b/pkg/dependency/parser/julia/manifest/parse_test.go index 229499c6cb71..efbc0bfcbee5 100644 --- a/pkg/dependency/parser/julia/manifest/parse_test.go +++ b/pkg/dependency/parser/julia/manifest/parse_test.go @@ -54,6 +54,12 @@ func TestParse(t *testing.T) { want: juliaV1_9ShadowedDepPkgs, wantDeps: juliaV1_9ShadowedDepDeps, }, + { + name: "julia v1.0 format", + file: "testdata/julia_v1.0_format/Manifest.toml", + want: juliaV10FormatPkgs, + wantDeps: juliaV10FormatDeps, + }, } for _, tt := range tests { diff --git a/pkg/dependency/parser/julia/manifest/parse_testcase.go b/pkg/dependency/parser/julia/manifest/parse_testcase.go index 75602f7311f8..232529065c55 100644 --- a/pkg/dependency/parser/julia/manifest/parse_testcase.go +++ b/pkg/dependency/parser/julia/manifest/parse_testcase.go @@ -74,4 +74,19 @@ var ( juliaV1_9ShadowedDepDeps = []ftypes.Dependency{ {ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", DependsOn: []string{"f41f7b98-334e-11e9-1257-49272045fb24"}}, } + + juliaV10FormatPkgs = []ftypes.Package{ + {ID: "767738be-2f1f-45a9-b806-0234f3164144", Name: "Foo", Version: "unknown", Locations: []ftypes.Location{{StartLine: 1, EndLine: 5}}}, + {ID: "6f418443-bd2e-4783-b551-cdbac608adf2", Name: "Foo", Version: "unknown", Locations: []ftypes.Location{{StartLine: 7, EndLine: 10}}}, + {ID: "2a550a13-6bab-4a91-a4ee-dff34d6b99d0", Name: "Bar", Version: "unknown", Locations: []ftypes.Location{{StartLine: 12, EndLine: 14}}}, + {ID: "6801f525-dc68-44e8-a4e8-cabd286279e7", Name: "Baz", Version: "unknown", Locations: []ftypes.Location{{StartLine: 19, EndLine: 21}}}, + {ID: "b5ec9b9c-e354-47fd-b367-a348bdc8f909", Name: "Qux", Version: "unknown", Locations: []ftypes.Location{{StartLine: 26, EndLine: 28}}}, + } + + juliaV10FormatDeps = []ftypes.Dependency{ + {ID: "767738be-2f1f-45a9-b806-0234f3164144", DependsOn: []string{"2a550a13-6bab-4a91-a4ee-dff34d6b99d0", "6801f525-dc68-44e8-a4e8-cabd286279e7", "b5ec9b9c-e354-47fd-b367-a348bdc8f909"}}, + {ID: "6f418443-bd2e-4783-b551-cdbac608adf2", DependsOn: []string{"b5ec9b9c-e354-47fd-b367-a348bdc8f909"}}, + {ID: "2a550a13-6bab-4a91-a4ee-dff34d6b99d0", DependsOn: []string{"6801f525-dc68-44e8-a4e8-cabd286279e7", "6f418443-bd2e-4783-b551-cdbac608adf2"}}, + {ID: "6801f525-dc68-44e8-a4e8-cabd286279e7", DependsOn: []string{"6f418443-bd2e-4783-b551-cdbac608adf2", "b5ec9b9c-e354-47fd-b367-a348bdc8f909"}}, + } ) diff --git a/pkg/dependency/parser/julia/manifest/testdata/julia_v1.0_format/Manifest.toml b/pkg/dependency/parser/julia/manifest/testdata/julia_v1.0_format/Manifest.toml new file mode 100644 index 000000000000..a5a893066c36 --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/testdata/julia_v1.0_format/Manifest.toml @@ -0,0 +1,28 @@ +[[Foo]] +deps = ["Bar", "Baz", "Qux"] +uuid = "767738be-2f1f-45a9-b806-0234f3164144" +git-tree-sha1 = "7c626031568a5e432112a74009c3763f9b851e3e" +path = "deps/Foo1" + +[[Foo]] +deps = ["Qux"] +uuid = "6f418443-bd2e-4783-b551-cdbac608adf2" +path = "deps/Foo2.jl" + +[[Bar]] +uuid = "2a550a13-6bab-4a91-a4ee-dff34d6b99d0" +path = "deps/Bar" +[Bar.deps] +Baz = "6801f525-dc68-44e8-a4e8-cabd286279e7" +Foo = "6f418443-bd2e-4783-b551-cdbac608adf2" + +[[Baz]] +uuid = "6801f525-dc68-44e8-a4e8-cabd286279e7" +git-tree-sha1 = "efc7e24c53d6a328011975294a2c75fed2f9800a" +[Baz.deps] +Foo = "6f418443-bd2e-4783-b551-cdbac608adf2" +Qux = "b5ec9b9c-e354-47fd-b367-a348bdc8f909" + +[[Qux]] +uuid = "b5ec9b9c-e354-47fd-b367-a348bdc8f909" +path = "deps/Qux.jl" diff --git a/pkg/dependency/parser/julia/manifest/testdata/julia_v1.0_format/Project.toml b/pkg/dependency/parser/julia/manifest/testdata/julia_v1.0_format/Project.toml new file mode 100644 index 000000000000..9d42e37cd67f --- /dev/null +++ b/pkg/dependency/parser/julia/manifest/testdata/julia_v1.0_format/Project.toml @@ -0,0 +1,6 @@ +name = "TestProject" +uuid = "84c38c17-0c6f-4d12-a694-d20b69c16777" + +[deps] +Foo = "767738be-2f1f-45a9-b806-0234f3164144" +Bar = "2a550a13-6bab-4a91-a4ee-dff34d6b99d0" diff --git a/pkg/detector/library/driver.go b/pkg/detector/library/driver.go index 64bd140ed57e..f78932b13442 100644 --- a/pkg/detector/library/driver.go +++ b/pkg/detector/library/driver.go @@ -81,6 +81,9 @@ func NewDriver(libType ftypes.LangType) (Driver, bool) { case ftypes.K8sUpstream: ecosystem = vulnerability.Kubernetes comparer = compare.GenericComparer{} + case ftypes.Julia: + log.Warn("Julia is supported for SBOM, not for vulnerability scanning") + return Driver{}, false default: log.Warn("The library type is not supported for vulnerability scanning", log.String("type", string(libType))) diff --git a/pkg/fanal/analyzer/all/import.go b/pkg/fanal/analyzer/all/import.go index acd0b4cc70e2..a5b0d05298a1 100644 --- a/pkg/fanal/analyzer/all/import.go +++ b/pkg/fanal/analyzer/all/import.go @@ -20,6 +20,7 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/gradle" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/jar" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/pom" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/julia/pkg" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/npm" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/pkg" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/pnpm" diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index ef20482a782c..99ac3cf4bee5 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -94,6 +94,9 @@ const ( // Dart TypePubSpecLock Type = "pubspec-lock" + // Julia + TypeJulia Type = "julia" + // ============ // Non-packaged // ============ @@ -191,6 +194,7 @@ var ( TypeSwift, TypePubSpecLock, TypeMixLock, + TypeJulia, } // TypeLockfiles has all lock file analyzers diff --git a/pkg/fanal/analyzer/language/julia/pkg/pkg.go b/pkg/fanal/analyzer/language/julia/pkg/pkg.go new file mode 100644 index 000000000000..c2b9fda035e3 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/pkg.go @@ -0,0 +1,190 @@ +package pkgjl + +import ( + "context" + "errors" + "io" + "io/fs" + "os" + "path/filepath" + "sort" + + "github.com/BurntSushi/toml" + "github.com/samber/lo" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + julia "github.com/aquasecurity/trivy/pkg/dependency/parser/julia/manifest" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeJulia, newJuliaAnalyzer) +} + +const version = 1 + +var requiredFiles = []string{ + types.JuliaManifest, + types.JuliaProject, +} + +type juliaAnalyzer struct { + lockParser language.Parser + logger *log.Logger +} + +type Project struct { + Dependencies map[string]string `toml:"deps"` + Extras map[string]string `toml:"extras"` +} + +func newJuliaAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &juliaAnalyzer{ + lockParser: julia.NewParser(), + logger: log.WithPrefix("julia"), + }, nil +} + +func (a juliaAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application + + required := func(path string, d fs.DirEntry) bool { + return filepath.Base(path) == types.JuliaManifest + } + + err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + // Parse Manifest.toml + app, err := a.parseJuliaManifest(path, r) + if err != nil { + return xerrors.Errorf("parse error: %w", err) + } else if app == nil { + return nil + } + + // Parse Project.toml alongside Manifest.toml to identify the direct dependencies. This mutates `app`. + if err = a.analyzeDependencies(input.FS, filepath.Dir(path), app); err != nil { + a.logger.Warn("Unable to parse file to analyze dependencies", + log.String("FILEPATH", filepath.Join(filepath.Dir(path), types.JuliaProject)), log.Err(err)) + } + + sort.Sort(app.Packages) + apps = append(apps, *app) + return nil + }) + if err != nil { + return nil, xerrors.Errorf("julia walk error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a juliaAnalyzer) Required(filePath string, _ os.FileInfo) bool { + fileName := filepath.Base(filePath) + return slices.Contains(requiredFiles, fileName) +} + +func (a juliaAnalyzer) Type() analyzer.Type { + return analyzer.TypeJulia +} + +func (a juliaAnalyzer) Version() int { + return version +} + +func (a juliaAnalyzer) parseJuliaManifest(path string, r io.Reader) (*types.Application, error) { + return language.Parse(types.Julia, path, r, a.lockParser) +} + +func (a juliaAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.Application) error { + deps, devDeps, err := a.getProjectDeps(fsys, dir) + if err != nil { + return err + } + + pkgs := walkDependencies(deps, app.Packages, false) + devPkgs := walkDependencies(devDeps, app.Packages, true) + app.Packages = append(pkgs, devPkgs...) + return nil +} + +// getProjectDeps parses project.toml and returns root and dev dependencies. +func (a juliaAnalyzer) getProjectDeps(fsys fs.FS, dir string) (map[string]string, map[string]string, error) { + projectPath := filepath.Join(dir, types.JuliaProject) + project, err := parseJuliaProject(fsys, projectPath) + if errors.Is(err, fs.ErrNotExist) { + a.logger.Debug("Julia project not found", log.String("PROJECT_PATH", projectPath)) + return nil, nil, nil + } else if err != nil { + return nil, nil, xerrors.Errorf("unable to parse %s: %w", projectPath, err) + } + return project.Dependencies, project.Extras, nil +} + +// Parses Project.toml +func parseJuliaProject(fsys fs.FS, path string) (Project, error) { + proj := Project{} + f, err := fsys.Open(path) + if err != nil { + return proj, xerrors.Errorf("file open error: %w", err) + } + defer func() { _ = f.Close() }() + + if _, err = toml.NewDecoder(f).Decode(&proj); err != nil { + return proj, xerrors.Errorf("decode error: %w", err) + } + return proj, nil +} + +// Marks the given direct dependencies as direct, then marks those packages' dependencies as indirect. +// Marks all encountered packages' Dev flag according to `dev`. +// Modifies the packages in `allPackages`. +func walkDependencies(directDeps map[string]string, allPackages types.Packages, dev bool) []types.Package { + pkgsByID := lo.SliceToMap(allPackages, func(pkg types.Package) (string, types.Package) { + return pkg.ID, pkg + }) + + // Identify direct dependencies + // Everything in `directDeps` is assumed to be direct + visited := make(map[string]types.Package) + for _, uuid := range directDeps { + if pkg, ok := pkgsByID[uuid]; ok { + pkg.Indirect = false + pkg.Dev = dev + visited[pkg.ID] = pkg + } + } + + // Identify indirect dependencies + for _, pkg := range visited { + walkIndirectDependencies(pkg, pkgsByID, visited) + } + + return maps.Values(visited) +} + +// Marks all indirect dependencies as indirect. Starts from `rootPkg`. Visited deps are added to `visited`. +func walkIndirectDependencies(rootPkg types.Package, allPkgIDs, visited map[string]types.Package) { + for _, pkgID := range rootPkg.DependsOn { + if _, ok := visited[pkgID]; ok { + continue + } + + dep, ok := allPkgIDs[pkgID] + if !ok { + continue + } + + dep.Indirect = true + dep.Dev = rootPkg.Dev + visited[dep.ID] = dep + walkIndirectDependencies(dep, allPkgIDs, visited) + } +} diff --git a/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go b/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go new file mode 100644 index 000000000000..8e96e0574d93 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go @@ -0,0 +1,209 @@ +package pkgjl + +import ( + "context" + "os" + "testing" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_juliaAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + dir string + want *analyzer.AnalysisResult + }{ + { + name: "happy path", + dir: "testdata/happy", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Julia, + FilePath: "Manifest.toml", + Packages: types.Packages{ + { + ID: "ade2ca70-3891-5945-98fb-dc099432e06a", + Name: "Dates", + Version: "1.9.0", + Indirect: false, + Locations: []types.Location{{StartLine: 7, EndLine: 9}}, + DependsOn: []string{"de0858da-6303-5e67-8744-51eddeeeb8d7"}, + }, + { + ID: "d9a60922-03b4-4a1b-81be-b8d05b827236", + Name: "DevDep", + Version: "1.0.0", + Indirect: false, + Dev: true, + Locations: []types.Location{{StartLine: 65, EndLine: 68}}, + DependsOn: []string{"b637660b-5035-4894-8335-b3805a4b50d8"}, + }, + { + ID: "b637660b-5035-4894-8335-b3805a4b50d8", + Name: "IndirectDevDep", + Version: "2.0.0", + Indirect: true, + Dev: true, + Locations: []types.Location{{StartLine: 70, EndLine: 72}}, + }, + { + ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", + Name: "JSON", + Version: "0.21.4", + Indirect: false, + Locations: []types.Location{{StartLine: 11, EndLine: 15}}, + DependsOn: []string{ + "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", + "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", + "a63ad114-7e13-5084-954f-fe012c677804", + "ade2ca70-3891-5945-98fb-dc099432e06a", + }, + }, + { + ID: "a63ad114-7e13-5084-954f-fe012c677804", + Name: "Mmap", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 17, EndLine: 18}}, + }, + { + ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", + Name: "Parsers", + Version: "2.5.10", + Indirect: true, + Locations: []types.Location{{StartLine: 20, EndLine: 24}}, + DependsOn: []string{ + "ade2ca70-3891-5945-98fb-dc099432e06a", + "aea7be01-6a6a-4083-8856-8a6e6704d82a", + "cf7118a7-6976-5b1a-9a39-7adc72f591a4", + }, + }, + { + ID: "aea7be01-6a6a-4083-8856-8a6e6704d82a", + Name: "PrecompileTools", + Version: "1.1.1", + Indirect: true, + Locations: []types.Location{{StartLine: 26, EndLine: 30}}, + DependsOn: []string{"21216c6a-2e73-6563-6e65-726566657250"}, + }, + { + ID: "21216c6a-2e73-6563-6e65-726566657250", + Name: "Preferences", + Version: "1.4.0", + Indirect: true, + Locations: []types.Location{{StartLine: 32, EndLine: 36}}, + DependsOn: []string{"fa267f1f-6049-4f14-aa54-33bafae1ed76"}, + }, + { + ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", + Name: "Printf", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 38, EndLine: 40}}, + DependsOn: []string{"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"}, + }, + { + ID: "9a3f8284-a2c9-5f02-9a11-845980a1fd5c", + Name: "Random", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 42, EndLine: 44}}, + DependsOn: []string{"9e88b42a-f829-5b0c-bbe9-9e923198166b", "ea8e919c-243c-51af-8825-aaa63cd721ce"}, + }, + { + ID: "ea8e919c-243c-51af-8825-aaa63cd721ce", + Name: "SHA", + Version: "0.7.0", + Indirect: true, + Locations: []types.Location{{StartLine: 46, EndLine: 48}}, + }, + { + ID: "9e88b42a-f829-5b0c-bbe9-9e923198166b", + Name: "Serialization", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 50, EndLine: 51}}, + }, + { + ID: "fa267f1f-6049-4f14-aa54-33bafae1ed76", + Name: "TOML", + Version: "1.0.3", + Indirect: true, + Locations: []types.Location{{StartLine: 53, EndLine: 56}}, + DependsOn: []string{"ade2ca70-3891-5945-98fb-dc099432e06a"}, + }, + { + ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4", + Name: "UUIDs", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 58, EndLine: 60}}, + DependsOn: []string{"9a3f8284-a2c9-5f02-9a11-845980a1fd5c", "ea8e919c-243c-51af-8825-aaa63cd721ce"}, + }, + { + ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", + Name: "Unicode", + Version: "1.9.0", + Indirect: true, + Locations: []types.Location{{StartLine: 62, EndLine: 63}}, + }, + }, + }, + }, + }, + }, + { + name: "no_deps_v1.6", + dir: "testdata/no_deps_v1.6", + want: &analyzer.AnalysisResult{}, + }, + { + name: "dep_ext_v1.9", + dir: "testdata/dep_ext_v1.9", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Julia, + FilePath: "Manifest.toml", + Packages: types.Packages{ + { + ID: "621f4979-c628-5d54-868e-fcf4e3e8185c", + Name: "AbstractFFTs", + Version: "1.3.1", + Indirect: false, + Locations: []types.Location{{StartLine: 7, EndLine: 10}}, + DependsOn: nil, + }, + }, + }, + }, + }, + }, + { + name: "no_manifest", + dir: "testdata/no_manifest", + want: &analyzer.AnalysisResult{ + Applications: nil, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newJuliaAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Manifest.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Manifest.toml new file mode 100644 index 000000000000..c3b22a724c15 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Manifest.toml @@ -0,0 +1,16 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "f0a796fb78285c02ad123fec6e14c8bac09a2ccc" + +[[deps.AbstractFFTs]] +git-tree-sha1 = "16b6dbc4cf7caee4e1e75c49485ec67b667098a0" +uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c" +version = "1.3.1" + + [deps.AbstractFFTs.extensions] + AbstractFFTsChainRulesCoreExt = "ChainRulesCore" + + [deps.AbstractFFTs.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Project.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Project.toml new file mode 100644 index 000000000000..7d99fdb4f598 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Project.toml @@ -0,0 +1,9 @@ +name = "packageName" +uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773" +version = "0.1.0" + +[deps] +AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c" + +[compat] +AbstractFFTs = "1.3" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Manifest.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Manifest.toml new file mode 100644 index 000000000000..316bf1497e44 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Manifest.toml @@ -0,0 +1,72 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "f65b9de676a27ce78ee011db6d477b3d44d1a7c5" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "a5aef8d4a6e8d81f171b2bd4be5265b01384c74c" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.5.10" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "259e206946c293698122f63e2b513a7c99a244e8" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.1.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "7eb1686b4f04b82f96ed7a4ea5890a4f0c7a09f1" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.0" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.Random]] +deps = ["SHA", "Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.DevDep]] +deps = ["IndirectDevDep"] +uuid = "d9a60922-03b4-4a1b-81be-b8d05b827236" +version = "1.0.0" + +[[deps.IndirectDevDep]] +uuid = "b637660b-5035-4894-8335-b3805a4b50d8" +version = "2.0.0" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Project.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Project.toml new file mode 100644 index 000000000000..f24e31bc961a --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Project.toml @@ -0,0 +1,17 @@ +name = "packageName" +uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773" +version = "0.1.0" + +[deps] +Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" + +[compat] +JSON = "0.21" +julia = "1.8" + +[extras] +DevDep = "d9a60922-03b4-4a1b-81be-b8d05b827236" + +[targets] +test = ["DevDep"] diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Manifest.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Manifest.toml new file mode 100644 index 000000000000..f45eecff031f --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Manifest.toml @@ -0,0 +1,2 @@ +# This file is machine-generated - editing it directly is not advised + diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Project.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Project.toml new file mode 100644 index 000000000000..84c8138906dd --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Project.toml @@ -0,0 +1,3 @@ +name = "packageName" +uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773" +version = "0.1.0" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/no_manifest/Project.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_manifest/Project.toml new file mode 100644 index 000000000000..dec02c4d6cd3 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/no_manifest/Project.toml @@ -0,0 +1,2 @@ +[deps] +A = "ead4f63c-334e-11e9-00e6-e7f0a5f21b62" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Manifest.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Manifest.toml new file mode 100644 index 000000000000..dd4ea00b943d --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Manifest.toml @@ -0,0 +1,16 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "f0a796fb78285c02ad123fec6e14c8bac09a2ccc" + +[[deps.A]] +uuid = "ead4f63c-334e-11e9-00e6-e7f0a5f21b60" + + [deps.A.deps] + B = "f41f7b98-334e-11e9-1257-49272045fb24" + +[[deps.B]] +uuid = "f41f7b98-334e-11e9-1257-49272045fb24" +[[deps.B]] +uuid = "edca9bc6-334e-11e9-3554-9595dbb4349c" diff --git a/pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Project.toml b/pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Project.toml new file mode 100644 index 000000000000..24fe6178c480 --- /dev/null +++ b/pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Project.toml @@ -0,0 +1,7 @@ +name = "packageName" +uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773" +version = "0.1.0" + +[deps] +A = "ead4f63c-334e-11e9-00e6-e7f0a5f21b60" +B = "edca9bc6-334e-11e9-3554-9595dbb4349c" diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index 56f56036a590..6874b8a40b06 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -73,6 +73,7 @@ const ( Pub LangType = "pub" Hex LangType = "hex" Bitnami LangType = "bitnami" + Julia LangType = "julia" K8sUpstream LangType = "kubernetes" EKS LangType = "eks" // Amazon Elastic Kubernetes Service @@ -143,4 +144,7 @@ const ( CondaEnvYaml = "environment.yaml" CondaEnvYml = "environment.yml" + + JuliaProject = "Project.toml" + JuliaManifest = "Manifest.toml" ) diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index 608c7b0b4029..92ce07be9741 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -109,6 +109,10 @@ func New(t ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) (*Pac return nil, nil } return (*PackageURL)(purl), nil + case packageurl.TypeJulia: + var qs packageurl.Qualifiers + namespace, name, qs = parseJulia(name, pkg.ID) // for Julia, the ID is set to the package UUID + qualifiers = append(qualifiers, qs...) } return (*PackageURL)(packageurl.NewPackageURL(ptype, namespace, name, ver, qualifiers, subpath)), nil @@ -424,6 +428,18 @@ func parseNpm(pkgName string) (string, string) { return parsePkgName(name) } +// ref. https://github.com/package-url/purl-spec/blob/7759d1cf81629267742eeeb0cdfccf5ebd624cc5/PURL-TYPES.rst#julia +func parseJulia(pkgName, pkgUUID string) (string, string, packageurl.Qualifiers) { + namespace, name := parsePkgName(pkgName) + qualifiers := packageurl.Qualifiers{ + { + Key: "uuid", + Value: pkgUUID, + }, + } + return namespace, name, qualifiers +} + func purlType(t ftypes.TargetType) string { switch t { case ftypes.Jar, ftypes.Pom, ftypes.Gradle: @@ -462,6 +478,8 @@ func purlType(t ftypes.TargetType) string { return packageurl.TypeRPM case TypeOCI: return packageurl.TypeOCI + case ftypes.Julia: + return packageurl.TypeJulia } return string(t) } diff --git a/pkg/purl/purl_test.go b/pkg/purl/purl_test.go index 646930ee5b76..90af61d7b949 100644 --- a/pkg/purl/purl_test.go +++ b/pkg/purl/purl_test.go @@ -406,6 +406,26 @@ func TestNewPackageURL(t *testing.T) { }, wantErr: "failed to parse digest", }, + { + name: "julia project", + typ: ftypes.Julia, + pkg: ftypes.Package{ + ID: "ade2ca70-3891-5945-98fb-dc099432e06a", + Name: "Dates", + Version: "1.9.0", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeJulia, + Name: "Dates", + Version: "1.9.0", + Qualifiers: packageurl.Qualifiers{ + { + Key: "uuid", + Value: "ade2ca70-3891-5945-98fb-dc099432e06a", + }, + }, + }, + }, } for _, tc := range testCases { From 1ad47c24efe16262e281dd6a443b2e2b033430f0 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 15 May 2024 09:30:00 +0200 Subject: [PATCH 067/352] chore(deps): use `google.golang.org/protobuf/types/known` instead of `github.com/golang/protobuf/ptypes` (#6681) Signed-off-by: Matthieu MOREL --- go.mod | 43 ++++++++++++++++------------------- pkg/cache/remote_test.go | 20 ++++++++-------- pkg/rpc/client/client_test.go | 6 ++--- pkg/rpc/server/server.go | 14 ++++++------ pkg/rpc/server/server_test.go | 19 ++++++++-------- 5 files changed, 49 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index f2f0dcfbac45..0849da324bc6 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,10 @@ require ( github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible github.com/Masterminds/sprig/v3 v3.2.3 github.com/NYTimes/gziphandler v1.1.1 + github.com/alecthomas/chroma v0.10.0 github.com/alicebob/miniredis/v2 v2.31.1 + github.com/antchfx/htmlquery v1.3.0 + github.com/apparentlymart/go-cidr v1.1.0 github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 @@ -36,6 +39,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4 github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 + github.com/aws/smithy-go v1.20.2 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cenkalti/backoff v2.2.1+incompatible @@ -50,7 +54,6 @@ require ( github.com/go-openapi/strfmt v0.23.0 github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/golang/protobuf v1.5.4 github.com/google/go-containerregistry v0.19.1 github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/uuid v1.6.0 @@ -58,7 +61,12 @@ require ( github.com/hashicorp/go-getter v1.7.4 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.5 + github.com/hashicorp/go-uuid v1.0.3 + github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/golang-lru/v2 v2.0.7 + github.com/hashicorp/hc-install v0.6.3 + github.com/hashicorp/hcl/v2 v2.19.1 + github.com/hashicorp/terraform-exec v0.20.0 github.com/in-toto/in-toto-golang v0.9.0 github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 @@ -66,9 +74,10 @@ require ( github.com/knqyf263/go-rpmdb v0.0.0-20231008124120-ac49267ab4e1 github.com/knqyf263/nested v0.0.1 github.com/kylelemons/godebug v1.1.0 + github.com/liamg/iamgo v0.0.9 github.com/liamg/jfather v0.0.7 + github.com/liamg/memoryfs v1.6.0 github.com/magefile/mage v1.15.0 - github.com/mailru/easyjson v0.7.7 // indirect github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 github.com/masahiro331/go-ext4-filesystem v0.0.0-20231208112839-4339555a0cd4 @@ -77,6 +86,7 @@ require ( github.com/masahiro331/go-xfs-filesystem v0.0.0-20230608043311-a335f4599b70 github.com/mattn/go-shellwords v1.0.12 github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 + github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 github.com/moby/buildkit v0.12.5 @@ -85,6 +95,7 @@ require ( github.com/opencontainers/image-spec v1.1.0 github.com/openvex/go-vex v0.2.5 github.com/owenrumney/go-sarif/v2 v2.3.0 + github.com/owenrumney/squealer v1.2.2 github.com/package-url/packageurl-go v0.1.2 github.com/quasilyte/go-ruleguard/dsl v0.3.22 github.com/samber/lo v1.39.0 @@ -104,8 +115,10 @@ require ( github.com/twitchtv/twirp v8.1.2+incompatible github.com/xeipuuv/gojsonschema v1.2.0 github.com/xlab/treeprint v1.2.0 + github.com/zclconf/go-cty v1.14.4 + github.com/zclconf/go-cty-yaml v1.0.3 go.etcd.io/bbolt v1.3.9 - go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.22.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/mod v0.16.0 golang.org/x/net v0.24.0 @@ -115,29 +128,10 @@ require ( golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/protobuf v1.34.0 gopkg.in/yaml.v3 v3.0.1 + helm.sh/helm/v3 v3.14.2 k8s.io/api v0.29.3 k8s.io/utils v0.0.0-20231127182322-b307cd553661 modernc.org/sqlite v1.29.7 -) - -require ( - github.com/alecthomas/chroma v0.10.0 - github.com/antchfx/htmlquery v1.3.0 - github.com/apparentlymart/go-cidr v1.1.0 - github.com/aws/smithy-go v1.20.2 - github.com/hashicorp/go-uuid v1.0.3 - github.com/hashicorp/go-version v1.6.0 - github.com/hashicorp/hc-install v0.6.3 - github.com/hashicorp/hcl/v2 v2.19.1 - github.com/hashicorp/terraform-exec v0.20.0 - github.com/liamg/iamgo v0.0.9 - github.com/liamg/memoryfs v1.6.0 - github.com/mitchellh/go-homedir v1.1.0 - github.com/owenrumney/squealer v1.2.2 - github.com/zclconf/go-cty v1.14.4 - github.com/zclconf/go-cty-yaml v1.0.3 - golang.org/x/crypto v0.22.0 - helm.sh/helm/v3 v3.14.2 sigs.k8s.io/yaml v1.4.0 ) @@ -282,6 +276,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -317,6 +312,7 @@ require ( github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect @@ -394,6 +390,7 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect diff --git a/pkg/cache/remote_test.go b/pkg/cache/remote_test.go index e396b71ae4ef..9ed9802096df 100644 --- a/pkg/cache/remote_test.go +++ b/pkg/cache/remote_test.go @@ -8,11 +8,11 @@ import ( "testing" "time" - google_protobuf "github.com/golang/protobuf/ptypes/empty" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/twitchtv/twirp" "golang.org/x/xerrors" + "google.golang.org/protobuf/types/known/emptypb" "github.com/aquasecurity/trivy/pkg/cache" fcache "github.com/aquasecurity/trivy/pkg/fanal/cache" @@ -25,18 +25,18 @@ type mockCacheServer struct { cache fcache.Cache } -func (s *mockCacheServer) PutArtifact(_ context.Context, in *rpcCache.PutArtifactRequest) (*google_protobuf.Empty, error) { +func (s *mockCacheServer) PutArtifact(_ context.Context, in *rpcCache.PutArtifactRequest) (*emptypb.Empty, error) { if strings.Contains(in.ArtifactId, "invalid") { - return &google_protobuf.Empty{}, xerrors.New("invalid image ID") + return &emptypb.Empty{}, xerrors.New("invalid image ID") } - return &google_protobuf.Empty{}, nil + return &emptypb.Empty{}, nil } -func (s *mockCacheServer) PutBlob(_ context.Context, in *rpcCache.PutBlobRequest) (*google_protobuf.Empty, error) { +func (s *mockCacheServer) PutBlob(_ context.Context, in *rpcCache.PutBlobRequest) (*emptypb.Empty, error) { if strings.Contains(in.DiffId, "invalid") { - return &google_protobuf.Empty{}, xerrors.New("invalid layer ID") + return &emptypb.Empty{}, xerrors.New("invalid layer ID") } - return &google_protobuf.Empty{}, nil + return &emptypb.Empty{}, nil } func (s *mockCacheServer) MissingBlobs(_ context.Context, in *rpcCache.MissingBlobsRequest) (*rpcCache.MissingBlobsResponse, error) { @@ -50,13 +50,13 @@ func (s *mockCacheServer) MissingBlobs(_ context.Context, in *rpcCache.MissingBl return &rpcCache.MissingBlobsResponse{MissingArtifact: true, MissingBlobIds: layerIDs}, nil } -func (s *mockCacheServer) DeleteBlobs(_ context.Context, in *rpcCache.DeleteBlobsRequest) (*google_protobuf.Empty, error) { +func (s *mockCacheServer) DeleteBlobs(_ context.Context, in *rpcCache.DeleteBlobsRequest) (*emptypb.Empty, error) { for _, blobId := range in.GetBlobIds() { if strings.Contains(blobId, "invalid") { - return &google_protobuf.Empty{}, xerrors.New("invalid layer ID") + return &emptypb.Empty{}, xerrors.New("invalid layer ID") } } - return &google_protobuf.Empty{}, nil + return &emptypb.Empty{}, nil } func withToken(base http.Handler, token, tokenHeader string) http.Handler { diff --git a/pkg/rpc/client/client_test.go b/pkg/rpc/client/client_test.go index 012d5799ade9..b3adeeed376c 100644 --- a/pkg/rpc/client/client_test.go +++ b/pkg/rpc/client/client_test.go @@ -9,10 +9,10 @@ import ( "net/http/httptest" "testing" - "github.com/golang/protobuf/ptypes/timestamp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/timestamppb" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/utils" @@ -95,10 +95,10 @@ func TestScanner_Scan(t *testing.T) { Layer: &common.Layer{ DiffId: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10", }, - LastModifiedDate: ×tamp.Timestamp{ + LastModifiedDate: ×tamppb.Timestamp{ Seconds: 1577840460, }, - PublishedDate: ×tamp.Timestamp{ + PublishedDate: ×tamppb.Timestamp{ Seconds: 978310860, }, }, diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 051eab982255..5d9bc426703f 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -3,10 +3,10 @@ package server import ( "context" - google_protobuf "github.com/golang/protobuf/ptypes/empty" "github.com/google/wire" "github.com/samber/lo" "golang.org/x/xerrors" + "google.golang.org/protobuf/types/known/emptypb" "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/log" @@ -71,7 +71,7 @@ func NewCacheServer(c cache.Cache) *CacheServer { } // PutArtifact puts the artifacts in cache -func (s *CacheServer) PutArtifact(_ context.Context, in *rpcCache.PutArtifactRequest) (*google_protobuf.Empty, error) { +func (s *CacheServer) PutArtifact(_ context.Context, in *rpcCache.PutArtifactRequest) (*emptypb.Empty, error) { if in.ArtifactInfo == nil { return nil, teeError(xerrors.Errorf("empty image info")) } @@ -79,11 +79,11 @@ func (s *CacheServer) PutArtifact(_ context.Context, in *rpcCache.PutArtifactReq if err := s.cache.PutArtifact(in.ArtifactId, imageInfo); err != nil { return nil, teeError(xerrors.Errorf("unable to store image info in cache: %w", err)) } - return &google_protobuf.Empty{}, nil + return &emptypb.Empty{}, nil } // PutBlob puts the blobs in cache -func (s *CacheServer) PutBlob(_ context.Context, in *rpcCache.PutBlobRequest) (*google_protobuf.Empty, error) { +func (s *CacheServer) PutBlob(_ context.Context, in *rpcCache.PutBlobRequest) (*emptypb.Empty, error) { if in.BlobInfo == nil { return nil, teeError(xerrors.Errorf("empty layer info")) } @@ -91,7 +91,7 @@ func (s *CacheServer) PutBlob(_ context.Context, in *rpcCache.PutBlobRequest) (* if err := s.cache.PutBlob(in.DiffId, layerInfo); err != nil { return nil, teeError(xerrors.Errorf("unable to store layer info in cache: %w", err)) } - return &google_protobuf.Empty{}, nil + return &emptypb.Empty{}, nil } // MissingBlobs returns missing blobs from cache @@ -107,10 +107,10 @@ func (s *CacheServer) MissingBlobs(_ context.Context, in *rpcCache.MissingBlobsR } // DeleteBlobs removes blobs by IDs -func (s *CacheServer) DeleteBlobs(_ context.Context, in *rpcCache.DeleteBlobsRequest) (*google_protobuf.Empty, error) { +func (s *CacheServer) DeleteBlobs(_ context.Context, in *rpcCache.DeleteBlobsRequest) (*emptypb.Empty, error) { blobIDs := rpc.ConvertFromDeleteBlobsRequest(in) if err := s.cache.DeleteBlobs(blobIDs); err != nil { return nil, teeError(xerrors.Errorf("failed to remove a blobs: %w", err)) } - return &google_protobuf.Empty{}, nil + return &emptypb.Empty{}, nil } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index a472fcbe8443..8c19e897035b 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -6,11 +6,10 @@ import ( "testing" "time" - google_protobuf "github.com/golang/protobuf/ptypes/empty" - "github.com/golang/protobuf/ptypes/timestamp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/xerrors" + "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" @@ -124,10 +123,10 @@ func TestScanServer_Scan(t *testing.T) { Title: "dos", Description: "dos vulnerability", References: []string{"http://example.com"}, - LastModifiedDate: ×tamp.Timestamp{ + LastModifiedDate: ×tamppb.Timestamp{ Seconds: 1577840460, }, - PublishedDate: ×tamp.Timestamp{ + PublishedDate: ×tamppb.Timestamp{ Seconds: 978310860, }, DataSource: &common.DataSource{ @@ -193,7 +192,7 @@ func TestCacheServer_PutArtifact(t *testing.T) { name string args args putImage cache.ArtifactCachePutArtifactExpectation - want *google_protobuf.Empty + want *emptypb.Empty wantErr string }{ { @@ -204,7 +203,7 @@ func TestCacheServer_PutArtifact(t *testing.T) { ArtifactInfo: &rpcCache.ArtifactInfo{ SchemaVersion: 1, Architecture: "amd64", - Created: func() *timestamp.Timestamp { + Created: func() *timestamppb.Timestamp { d := time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC) t := timestamppb.New(d) return t @@ -226,7 +225,7 @@ func TestCacheServer_PutArtifact(t *testing.T) { }, }, }, - want: &google_protobuf.Empty{}, + want: &emptypb.Empty{}, }, { name: "sad path", @@ -235,7 +234,7 @@ func TestCacheServer_PutArtifact(t *testing.T) { ArtifactId: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", ArtifactInfo: &rpcCache.ArtifactInfo{ SchemaVersion: 1, - Created: func() *timestamp.Timestamp { + Created: func() *timestamppb.Timestamp { d := time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC) t := timestamppb.New(d) return t @@ -294,7 +293,7 @@ func TestCacheServer_PutBlob(t *testing.T) { name string args args putLayer cache.ArtifactCachePutBlobExpectation - want *google_protobuf.Empty + want *emptypb.Empty wantErr string }{ { @@ -461,7 +460,7 @@ func TestCacheServer_PutBlob(t *testing.T) { }, }, }, - want: &google_protobuf.Empty{}, + want: &emptypb.Empty{}, }, { name: "sad path", From 88702cfd5918b093defc5b5580f7cbf16f5f2417 Mon Sep 17 00:00:00 2001 From: simar7 <1254783+simar7@users.noreply.github.com> Date: Wed, 15 May 2024 19:14:51 -0600 Subject: [PATCH 068/352] feat(misconf): Add support for deprecating a check (#6664) Signed-off-by: Simar --- docs/docs/advanced/air-gap.md | 4 +- docs/docs/configuration/cache.md | 4 +- docs/docs/configuration/filtering.md | 2 +- docs/docs/coverage/iac/helm.md | 2 +- .../references/configuration/cli/trivy_aws.md | 1 + .../configuration/cli/trivy_config.md | 1 + .../configuration/cli/trivy_filesystem.md | 1 + .../configuration/cli/trivy_image.md | 1 + .../configuration/cli/trivy_kubernetes.md | 1 + .../configuration/cli/trivy_repository.md | 1 + .../configuration/cli/trivy_rootfs.md | 1 + .../references/configuration/config-file.md | 8 +- .../misconfiguration/check/exceptions.md | 8 +- .../scanner/misconfiguration/custom/data.md | 2 +- .../scanner/misconfiguration/custom/debug.md | 4 +- .../scanner/misconfiguration/custom/index.md | 9 +- .../scanner/misconfiguration/custom/schema.md | 12 +-- .../misconfiguration/custom/testing.md | 12 +-- docs/docs/scanner/misconfiguration/index.md | 16 +-- docs/docs/target/aws.md | 12 +-- .../additional-resources/community.md | 2 +- docs/tutorials/misconfiguration/terraform.md | 2 +- go.mod | 2 +- go.sum | 4 +- mkdocs.yml | 2 +- pkg/commands/artifact/run.go | 1 + pkg/flag/rego_flags.go | 50 +++++---- pkg/iac/rego/embed_test.go | 100 ++++++++++++++++++ pkg/iac/rego/load.go | 4 +- pkg/iac/rego/metadata.go | 8 ++ pkg/iac/rego/metadata_test.go | 17 +++ pkg/iac/rego/scanner.go | 43 +++++--- pkg/iac/rego/scanner_test.go | 75 +++++++++++++ pkg/iac/scan/flat.go | 2 + pkg/iac/scan/rule.go | 5 + pkg/iac/scanners/azure/arm/scanner.go | 2 + pkg/iac/scanners/cloudformation/scanner.go | 2 + pkg/iac/scanners/dockerfile/scanner.go | 2 + pkg/iac/scanners/helm/scanner.go | 2 + pkg/iac/scanners/json/scanner.go | 2 + pkg/iac/scanners/kubernetes/scanner.go | 2 + pkg/iac/scanners/options/scanner.go | 7 ++ pkg/iac/scanners/terraform/scanner.go | 2 + .../scanners/terraformplan/tfjson/scanner.go | 2 + pkg/iac/scanners/toml/scanner.go | 2 + pkg/iac/scanners/yaml/scanner.go | 2 + pkg/misconf/scanner.go | 2 + 47 files changed, 358 insertions(+), 90 deletions(-) diff --git a/docs/docs/advanced/air-gap.md b/docs/docs/advanced/air-gap.md index 3cb7eff65c9f..171b80249eac 100644 --- a/docs/docs/advanced/air-gap.md +++ b/docs/docs/advanced/air-gap.md @@ -129,8 +129,8 @@ $ trivy image --skip-db-update --skip-java-db-update --offline-scan alpine:3.12 No special measures are required to detect misconfigurations in an air-gapped environment. -### Run Trivy with `--skip-policy-update` option -In an air-gapped environment, specify `--skip-policy-update` so that Trivy doesn't attempt to download the latest misconfiguration policies. +### Run Trivy with `--skip-check-update` option +In an air-gapped environment, specify `--skip-check-update` so that Trivy doesn't attempt to download the latest misconfiguration checks. ``` $ trivy conf --skip-policy-update /path/to/conf diff --git a/docs/docs/configuration/cache.md b/docs/docs/configuration/cache.md index d8149f16ccab..ff3a373c22ce 100644 --- a/docs/docs/configuration/cache.md +++ b/docs/docs/configuration/cache.md @@ -3,7 +3,7 @@ The cache directory includes - [Vulnerability Database][trivy-db][^1] - [Java Index Database][trivy-java-db][^2] -- [Misconfiguration Policies][misconf-policies][^3] +- [Misconfiguration Checks][misconf-checks][^3] - Cache of previous scans. The cache option is common to all scanners. @@ -70,7 +70,7 @@ $ trivy server --cache-backend redis://localhost:6379 \ [trivy-db]: ./db.md#vulnerability-database [trivy-java-db]: ./db.md#java-index-database -[misconf-policies]: ../scanner/misconfiguration/check/builtin.md +[misconf-checks]: ../scanner/misconfiguration/check/builtin.md [^1]: Downloaded when scanning for vulnerabilities [^2]: Downloaded when scanning `jar/war/par/ear` files diff --git a/docs/docs/configuration/filtering.md b/docs/docs/configuration/filtering.md index 965c2873c25e..e3d38f3cdc15 100644 --- a/docs/docs/configuration/filtering.md +++ b/docs/docs/configuration/filtering.md @@ -483,7 +483,7 @@ trivy image --ignore-policy contrib/example_policy/basic.rego centos:7 For more advanced use cases, there is a built-in Rego library with helper functions that you can import into your policy using: `import data.lib.trivy`. More info about the helper functions are in the library [here](https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/pkg/result/module.go). -You can find more example policies [here](https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/pkg/result/module.go) +You can find more example checks [here](https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/pkg/result/module.go) ### By Vulnerability Exploitability Exchange (VEX) | Scanner | Supported | diff --git a/docs/docs/coverage/iac/helm.md b/docs/docs/coverage/iac/helm.md index cc8ddc0656a4..8d0352fc42f1 100644 --- a/docs/docs/coverage/iac/helm.md +++ b/docs/docs/coverage/iac/helm.md @@ -11,7 +11,7 @@ The following scanners are supported. Trivy recursively searches directories and scans all found Helm files. It evaluates variables, functions, and other elements within Helm templates and resolve the chart to Kubernetes manifests then run the Kubernetes checks. -See [here](../../scanner/misconfiguration/check/builtin.md) for more details on the built-in policies. +See [here](../../scanner/misconfiguration/check/builtin.md) for more details on the built-in checks. ### Value overrides There are a number of options for overriding values in Helm charts. diff --git a/docs/docs/references/configuration/cli/trivy_aws.md b/docs/docs/references/configuration/cli/trivy_aws.md index 44774dffcd16..aa0255a7ebcd 100644 --- a/docs/docs/references/configuration/cli/trivy_aws.md +++ b/docs/docs/references/configuration/cli/trivy_aws.md @@ -87,6 +87,7 @@ trivy aws [flags] -h, --help help for aws --ignore-policy string specify the Rego file path to evaluate each vulnerability --ignorefile string specify .trivyignore file (default ".trivyignore") + --include-deprecated-checks include deprecated checks --include-non-failures include successes and exceptions, available with '--scanners misconfig' --list-all-pkgs enabling the option will output all packages regardless of vulnerability --max-cache-age duration The maximum age of the cloud cache. Cached data will be required from the cloud provider if it is older than this. (default 24h0m0s) diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 73bf450244e0..993570f1587b 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -31,6 +31,7 @@ trivy config [flags] DIR -h, --help help for config --ignore-policy string specify the Rego file path to evaluate each vulnerability --ignorefile string specify .trivyignore file (default ".trivyignore") + --include-deprecated-checks include deprecated checks --include-non-failures include successes and exceptions, available with '--scanners misconfig' --k8s-version string specify k8s version to validate outdated api by it (example: 1.21.0) --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 79601ddc05e3..e79b923e3786 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -49,6 +49,7 @@ trivy filesystem [flags] PATH --ignore-unfixed display only fixed vulnerabilities --ignored-licenses strings specify a list of license to ignore --ignorefile string specify .trivyignore file (default ".trivyignore") + --include-deprecated-checks include deprecated checks --include-dev-deps include development dependencies in the report (supported: npm, yarn) --include-non-failures include successes and exceptions, available with '--scanners misconfig' --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index ab7951fa8d5e..980cf68a795f 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -67,6 +67,7 @@ trivy image [flags] IMAGE_NAME --ignorefile string specify .trivyignore file (default ".trivyignore") --image-config-scanners strings comma-separated list of what security issues to detect on container image configurations (misconfig,secret) --image-src strings image source(s) to use, in priority order (docker,containerd,podman,remote) (default [docker,containerd,podman,remote]) + --include-deprecated-checks include deprecated checks --include-non-failures include successes and exceptions, available with '--scanners misconfig' --input string input file path instead of image name --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 62ee6cd3b422..cdc50f9f5451 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -62,6 +62,7 @@ trivy kubernetes [flags] [CONTEXT] --ignore-unfixed display only fixed vulnerabilities --ignorefile string specify .trivyignore file (default ".trivyignore") --image-src strings image source(s) to use, in priority order (docker,containerd,podman,remote) (default [docker,containerd,podman,remote]) + --include-deprecated-checks include deprecated checks --include-kinds strings indicate the kinds included in scanning (example: node) --include-namespaces strings indicate the namespaces included in scanning (example: kube-system) --include-non-failures include successes and exceptions, available with '--scanners misconfig' diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index e3daa569d9f4..7efde1657cc7 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -49,6 +49,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --ignore-unfixed display only fixed vulnerabilities --ignored-licenses strings specify a list of license to ignore --ignorefile string specify .trivyignore file (default ".trivyignore") + --include-deprecated-checks include deprecated checks --include-dev-deps include development dependencies in the report (supported: npm, yarn) --include-non-failures include successes and exceptions, available with '--scanners misconfig' --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 4bc3fc61d2af..ea6a3093802f 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -52,6 +52,7 @@ trivy rootfs [flags] ROOTDIR --ignore-unfixed display only fixed vulnerabilities --ignored-licenses strings specify a list of license to ignore --ignorefile string specify .trivyignore file (default ".trivyignore") + --include-deprecated-checks include deprecated checks --include-non-failures include successes and exceptions, available with '--scanners misconfig' --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") --license-confidence-level float specify license classifier's confidence level (default 0.9) diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index 755913a0bf20..1a7020d94fe4 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -382,10 +382,14 @@ misconfiguration: # Same as '--include-non-failures' # Default is false include-non-failures: false + + # Same as '--include-deprecated-checks' + # Default is false + include-deprecated-checks: false - # Same as '--policy-bundle-repository' + # Same as '--check-bundle-repository' and '--policy-bundle-repository' # Default is 'ghcr.io/aquasecurity/trivy-checks:0' - policy-bundle-repository: ghcr.io/aquasecurity/trivy-checks:0 + check-bundle-repository: ghcr.io/aquasecurity/trivy-checks:0 # Same as '--miconfig-scanners' # Default is all scanners diff --git a/docs/docs/scanner/misconfiguration/check/exceptions.md b/docs/docs/scanner/misconfiguration/check/exceptions.md index e4020c029908..70ef974a4b74 100644 --- a/docs/docs/scanner/misconfiguration/check/exceptions.md +++ b/docs/docs/scanner/misconfiguration/check/exceptions.md @@ -3,10 +3,10 @@ Exceptions let you specify cases where you allow policy violations. Trivy supports two types of exceptions. !!! info - Exceptions can be applied to built-in policies as well as custom policies. + Exceptions can be applied to built-in checks as well as custom checks. ## Namespace-based exceptions -There are some cases where you need to disable built-in policies partially or fully. +There are some cases where you need to disable built-in checks partially or fully. Namespace-based exceptions lets you rough choose which individual packages to exempt. To use namespace-based exceptions, create a Rego rule with the name `exception` that returns the package names to exempt. @@ -26,7 +26,7 @@ The `exception` rule must be defined under `namespace.exceptions`. } ``` -This example exempts all built-in policies for Kubernetes. +This example exempts all built-in checks for Kubernetes. ## Rule-based exceptions There are some cases where you need more flexibility and granularity in defining which cases to exempt. @@ -73,7 +73,7 @@ The above would provide an exception from `deny_foo` and `deny_bar`. } ``` -If you want to apply rule-based exceptions to built-in policies, you have to define the exception under the same package. +If you want to apply rule-based exceptions to built-in checks, you have to define the exception under the same package. !!! example ``` rego diff --git a/docs/docs/scanner/misconfiguration/custom/data.md b/docs/docs/scanner/misconfiguration/custom/data.md index 6e858d86ed6f..51af206b4c63 100644 --- a/docs/docs/scanner/misconfiguration/custom/data.md +++ b/docs/docs/scanner/misconfiguration/custom/data.md @@ -1,6 +1,6 @@ # Custom Data -Custom policies may require additional data in order to determine an answer. +Custom checks may require additional data in order to determine an answer. For example, an allowed list of resources that can be created. Instead of hardcoding this information inside your policy, Trivy allows passing paths to data files with the `--data` flag. diff --git a/docs/docs/scanner/misconfiguration/custom/debug.md b/docs/docs/scanner/misconfiguration/custom/debug.md index 8ea0cc5e0e71..751e43633efc 100644 --- a/docs/docs/scanner/misconfiguration/custom/debug.md +++ b/docs/docs/scanner/misconfiguration/custom/debug.md @@ -1,10 +1,10 @@ -# Debugging policies +# Debugging checks When working on more complex queries (or when learning Rego), it's useful to see exactly how the policy is applied. For this purpose you can use the `--trace` flag. This will output a large trace from Open Policy Agent like the following: !!! tip - Only failed policies show traces. If you want to debug a passed policy, you need to make it fail on purpose. + Only failed checks show traces. If you want to debug a passed check, you need to make it fail on purpose. ```shell $ trivy conf --trace configs/ diff --git a/docs/docs/scanner/misconfiguration/custom/index.md b/docs/docs/scanner/misconfiguration/custom/index.md index 8b08c5e41292..9ce6250552bf 100644 --- a/docs/docs/scanner/misconfiguration/custom/index.md +++ b/docs/docs/scanner/misconfiguration/custom/index.md @@ -1,8 +1,8 @@ -# Custom Policies +# Custom Checks ## Overview -You can write custom policies in [Rego][rego]. -Once you finish writing custom policies, you can pass the policy files or the directory where those policies are stored with `--policy` option. +You can write custom checks in [Rego][rego]. +Once you finish writing custom checks, you can pass the policy files or the directory where those policies are stored with `--policy` option. ``` bash trivy conf --policy /path/to/policy.rego --policy /path/to/custom_policies --namespaces user /path/to/config_dir @@ -120,7 +120,7 @@ Trivy supports extra fields in the `custom` section as described below. ``` All fields are optional. The `schemas` field should be used to enable policy validation using a built-in schema. The -schema that will be used is based on the input document type. It is recommended to use this to ensure your policies are +schema that will be used is based on the input document type. It is recommended to use this to ensure your checks are correct and do not reference incorrect properties/values. | Field name | Allowed values | Default value | In table | In JSON | @@ -131,6 +131,7 @@ correct and do not reference incorrect properties/values. | custom.id | Any characters | N/A | :material-check: | :material-check: | | custom.severity | `LOW`, `MEDIUM`, `HIGH`, `CRITICAL` | UNKNOWN | :material-check: | :material-check: | | custom.recommended_actions | Any characters | | :material-close: | :material-check: | +| custom.deprecated | `true`, `false` | `false` | :material-close: | :material-check: | | custom.input.selector.type | Any item(s) in [this list][source-types] | | :material-close: | :material-check: | | url | Any characters | | :material-close: | :material-check: | diff --git a/docs/docs/scanner/misconfiguration/custom/schema.md b/docs/docs/scanner/misconfiguration/custom/schema.md index ea5efeb7b3e2..34872997238d 100644 --- a/docs/docs/scanner/misconfiguration/custom/schema.md +++ b/docs/docs/scanner/misconfiguration/custom/schema.md @@ -54,7 +54,7 @@ Currently, out of the box the following schemas are supported natively: 3. [Cloud](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/cloud.json) -## Custom Policies with Custom Schemas +## Custom Checks with Custom Schemas You can also bring a custom policy that defines one or more custom schema. @@ -71,21 +71,21 @@ You can also bring a custom policy that defines one or more custom schema. } ``` -The policies can be placed in a structure as follows +The checks can be placed in a structure as follows !!! example ``` - /Users/user/my-custom-policies + /Users/user/my-custom-checks ├── my_policy.rego └── schemas └── fooschema.json └── barschema.json ``` -To use such a policy with Trivy, use the `--config-policy` flag that points to the policy file or to the directory where the schemas and policies are contained. +To use such a policy with Trivy, use the `--config-policy` flag that points to the policy file or to the directory where the schemas and checks are contained. ```bash -$ trivy --config-policy=/Users/user/my-custom-policies +$ trivy --config-policy=/Users/user/my-custom-checks ``` -For more details on how to define schemas within Rego policies, please see the [OPA guide](https://www.openpolicyagent.org/docs/latest/policy-language/#schema-annotations) that describes it in more detail. \ No newline at end of file +For more details on how to define schemas within Rego checks, please see the [OPA guide](https://www.openpolicyagent.org/docs/latest/policy-language/#schema-annotations) that describes it in more detail. \ No newline at end of file diff --git a/docs/docs/scanner/misconfiguration/custom/testing.md b/docs/docs/scanner/misconfiguration/custom/testing.md index fcda218ff8eb..2db2fa823531 100644 --- a/docs/docs/scanner/misconfiguration/custom/testing.md +++ b/docs/docs/scanner/misconfiguration/custom/testing.md @@ -1,9 +1,9 @@ # Testing -It is highly recommended to write tests for your custom policies. +It is highly recommended to write tests for your custom checks. ## Rego testing -To help you verify the correctness of your custom policies, OPA gives you a framework that you can use to write tests for your policies. -By writing tests for your custom policies you can speed up the development process of new rules and reduce the amount of time it takes to modify rules as requirements evolve. +To help you verify the correctness of your custom checks, OPA gives you a framework that you can use to write tests for your checks. +By writing tests for your custom checks you can speed up the development process of new rules and reduce the amount of time it takes to modify rules as requirements evolve. For more details, see [Policy Testing][opa-testing]. @@ -22,12 +22,12 @@ For more details, see [Policy Testing][opa-testing]. } ``` -To write tests for custom policies, you can refer to existing tests under [trivy-checks][trivy-checks]. +To write tests for custom checks, you can refer to existing tests under [trivy-checks][trivy-checks]. ## Go testing [Fanal][fanal] which is a core library of Trivy can be imported as a Go library. -You can scan config files in Go and test your custom policies using Go's testing methods, such as [table-driven tests][table]. -This allows you to use the actual configuration file as input, making it easy to prepare test data and ensure that your custom policies work in practice. +You can scan config files in Go and test your custom checks using Go's testing methods, such as [table-driven tests][table]. +This allows you to use the actual configuration file as input, making it easy to prepare test data and ensure that your custom checks work in practice. In particular, Dockerfile and HCL need to be converted to structural data as input, which may be different from the expected input format. diff --git a/docs/docs/scanner/misconfiguration/index.md b/docs/docs/scanner/misconfiguration/index.md index b243d3e8dc17..701d469d658f 100644 --- a/docs/docs/scanner/misconfiguration/index.md +++ b/docs/docs/scanner/misconfiguration/index.md @@ -1,6 +1,6 @@ # Misconfiguration Scanning -Trivy provides built-in policies to detect configuration issues in popular Infrastructure as Code files, such as: Docker, Kubernetes, Terraform, CloudFormation, and more. -In addition to built-in policies, you can write your own custom policies, as you can see [here][custom]. +Trivy provides built-in checks to detect configuration issues in popular Infrastructure as Code files, such as: Docker, Kubernetes, Terraform, CloudFormation, and more. +In addition to built-in checks, you can write your own custom checks, as you can see [here][custom]. ## Quick start @@ -94,7 +94,7 @@ In the above example, Trivy detected vulnerabilities of Python dependencies and ## Type detection The specified directory can contain mixed types of IaC files. -Trivy automatically detects config types and applies relevant policies. +Trivy automatically detects config types and applies relevant checks. For example, the following example holds IaC files for Terraform, CloudFormation, Kubernetes, Helm Charts, and Dockerfile in the same directory. @@ -326,8 +326,8 @@ trivy config --misconfig-scanners=terraform,dockerfile . Will only scan for misconfigurations that pertain to Terraform and Dockerfiles. -### Passing custom policies -You can pass policy files or directories including your custom policies through `--policy` option. +### Passing custom checks +You can pass policy files or directories including your custom checks through `--policy` option. This can be repeated for specifying multiple files or directories. ```bash @@ -335,7 +335,7 @@ cd examplex/misconf/ trivy conf --policy custom-policy/policy --policy combine/policy --policy policy.rego --namespaces user misconf/mixed ``` -For more details, see [Custom Policies](./custom/index.md). +For more details, see [Custom Checks](./custom/index.md). !!! tip You also need to specify `--namespaces` option. @@ -352,8 +352,8 @@ trivy conf --policy ./policy --data ./data --namespaces user ./configs For more details, see [Custom Data](./custom/data.md). ### Passing namespaces -By default, Trivy evaluates policies defined in `builtin.*`. -If you want to evaluate custom policies in other packages, you have to specify package prefixes through `--namespaces` option. +By default, Trivy evaluates checks defined in `builtin.*`. +If you want to evaluate custom checks in other packages, you have to specify package prefixes through `--namespaces` option. This can be repeated for specifying multiple packages. ``` bash diff --git a/docs/docs/target/aws.md b/docs/docs/target/aws.md index 78781646b118..ef23825129f9 100644 --- a/docs/docs/target/aws.md +++ b/docs/docs/target/aws.md @@ -99,11 +99,11 @@ If you want to force the cache to be refreshed with the latest data, you can use Or if you'd like to use cached data for a different timeframe, you can specify `--max-cache-age` (e.g. `--max-cache-age 2h`.). Regardless of whether the cache is used or not, rules will be evaluated again with each run of `trivy aws`. -## Custom Policies +## Custom Checks -You can write custom policies for Trivy to evaluate against your AWS account. -These policies are written in [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/), the same language used by [Open Policy Agent](https://www.openpolicyagent.org/). -See the [Custom Policies](../scanner/misconfiguration/custom/index.md) page for more information on how to write custom policies. +You can write custom checks for Trivy to evaluate against your AWS account. +These checks are written in [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/), the same language used by [Open Policy Agent](https://www.openpolicyagent.org/). +See the [Custom Checks](../scanner/misconfiguration/custom/index.md) page for more information on how to write custom checks. -Custom policies in cloud scanning also support passing in custom data. This can be useful when you want to selectively enable/disable certain aspects of your cloud policies. -See the [Custom Data](../scanner/misconfiguration/custom/data.md) page for more information on how to provide custom data to custom policies. +Custom checks in cloud scanning also support passing in custom data. This can be useful when you want to selectively enable/disable certain aspects of your cloud checks. +See the [Custom Data](../scanner/misconfiguration/custom/data.md) page for more information on how to provide custom data to custom checks. diff --git a/docs/tutorials/additional-resources/community.md b/docs/tutorials/additional-resources/community.md index c1ab7241e4e5..2f5ab5a15c41 100644 --- a/docs/tutorials/additional-resources/community.md +++ b/docs/tutorials/additional-resources/community.md @@ -16,7 +16,7 @@ Below is a list of additional resources from the community. ## Misconfiguration Scanning - [Identifying Misconfigurations in your Terraform](https://youtu.be/cps1V5fOHtE) -- [How to write custom policies for Trivy](https://blog.ediri.io/how-to-write-custom-policies-for-trivy) +- [How to write custom checks for Trivy](https://blog.ediri.io/how-to-write-custom-policies-for-trivy) ## SBOM, Attestation & related diff --git a/docs/tutorials/misconfiguration/terraform.md b/docs/tutorials/misconfiguration/terraform.md index 8240e1ba53b2..24b8eebfa69a 100644 --- a/docs/tutorials/misconfiguration/terraform.md +++ b/docs/tutorials/misconfiguration/terraform.md @@ -90,7 +90,7 @@ trivy conf --tf-vars terraform.tfvars ./ ``` ### Custom Checks -We have lots of examples in the [documentation](https://aquasecurity.github.io/trivy/latest/docs/scanner/misconfiguration/custom/) on how you can write and pass custom Rego policies into terraform misconfiguration scans. +We have lots of examples in the [documentation](https://aquasecurity.github.io/trivy/latest/docs/scanner/misconfiguration/custom/) on how you can write and pass custom Rego checks into terraform misconfiguration scans. ## Secret and vulnerability scans diff --git a/go.mod b/go.mod index 0849da324bc6..d25d339b8a1d 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/aquasecurity/table v1.8.0 github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334 github.com/aquasecurity/tml v0.6.1 - github.com/aquasecurity/trivy-aws v0.8.0 + github.com/aquasecurity/trivy-aws v0.8.1-0.20240511051125-4393910b056b github.com/aquasecurity/trivy-checks v0.10.5-0.20240514040354-93bcb2f8c233 github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 diff --git a/go.sum b/go.sum index 1ea2d226a903..d62a5fb3c10b 100644 --- a/go.sum +++ b/go.sum @@ -773,8 +773,8 @@ github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334 h1:MgvbLyL github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334/go.mod h1:TKXn7bPfMM52ETP4sjjwkTKCZ18CqCs+I/vtFePSdBc= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= -github.com/aquasecurity/trivy-aws v0.8.0 h1:4ij8MiZ2sJUH+vWpSeoGVhPr109ZBcNp7LNLfPuv5Cw= -github.com/aquasecurity/trivy-aws v0.8.0/go.mod h1:Pb9xqOuTKMHVgjsnjvudjqZh3nmzdFqFVfRkXnoIZBM= +github.com/aquasecurity/trivy-aws v0.8.1-0.20240511051125-4393910b056b h1:mBMM6+kLTPaqSxNLO51rL6HiCKL1ElV5RXM+BEAK8fg= +github.com/aquasecurity/trivy-aws v0.8.1-0.20240511051125-4393910b056b/go.mod h1:z638DsULU5CCIk8QZqcj8u2D5IIRzvjq4jI1VDQGda4= github.com/aquasecurity/trivy-checks v0.10.5-0.20240514040354-93bcb2f8c233 h1:7TnJS1JEmrNfznu1Y9Rzbboxl7J4hxjIKQ8tV3k5UQs= github.com/aquasecurity/trivy-checks v0.10.5-0.20240514040354-93bcb2f8c233/go.mod h1:+G8Ft1pJAmsSPzfSQHdSQ5zcWHWPOxVdQHHA+eHP3eU= github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTUAkbGqpzt9nJsO24RAdfG+ZSiLFj0G2jO8c= diff --git a/mkdocs.yml b/mkdocs.yml index 0a7947b4d703..4f8a42c9cec3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -57,7 +57,7 @@ nav: - Policy: - Built-in Checks: docs/scanner/misconfiguration/check/builtin.md - Exceptions: docs/scanner/misconfiguration/check/exceptions.md - - Custom Policies: + - Custom Checks: - Overview: docs/scanner/misconfiguration/custom/index.md - Data: docs/scanner/misconfiguration/custom/data.md - Combine: docs/scanner/misconfiguration/custom/combine.md diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 14aae3659cac..f61e84928265 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -587,6 +587,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi K8sVersion: opts.K8sVersion, DisableEmbeddedPolicies: disableEmbedded, DisableEmbeddedLibraries: disableEmbedded, + IncludeDeprecatedChecks: opts.IncludeDeprecatedChecks, TfExcludeDownloaded: opts.TfExcludeDownloaded, } } diff --git a/pkg/flag/rego_flags.go b/pkg/flag/rego_flags.go index e7358e065b33..4b291f0a5eb3 100644 --- a/pkg/flag/rego_flags.go +++ b/pkg/flag/rego_flags.go @@ -7,6 +7,11 @@ package flag // config-policy: "custom-policy/policy" // policy-namespaces: "user" var ( + IncludeDeprecatedChecksFlag = Flag[bool]{ + Name: "include-deprecated-checks", + ConfigName: "rego.include-deprecated-checks", + Usage: "include deprecated checks", + } SkipCheckUpdateFlag = Flag[bool]{ Name: "skip-check-update", ConfigName: "rego.skip-check-update", @@ -53,28 +58,31 @@ var ( // RegoFlagGroup composes common printer flag structs used for commands providing misconfinguration scanning. type RegoFlagGroup struct { - SkipCheckUpdate *Flag[bool] - Trace *Flag[bool] - CheckPaths *Flag[[]string] - DataPaths *Flag[[]string] - CheckNamespaces *Flag[[]string] + IncludeDeprecatedChecks *Flag[bool] + SkipCheckUpdate *Flag[bool] + Trace *Flag[bool] + CheckPaths *Flag[[]string] + DataPaths *Flag[[]string] + CheckNamespaces *Flag[[]string] } type RegoOptions struct { - SkipCheckUpdate bool - Trace bool - CheckPaths []string - DataPaths []string - CheckNamespaces []string + IncludeDeprecatedChecks bool + SkipCheckUpdate bool + Trace bool + CheckPaths []string + DataPaths []string + CheckNamespaces []string } func NewRegoFlagGroup() *RegoFlagGroup { return &RegoFlagGroup{ - SkipCheckUpdate: SkipCheckUpdateFlag.Clone(), - Trace: TraceFlag.Clone(), - CheckPaths: ConfigCheckFlag.Clone(), - DataPaths: ConfigDataFlag.Clone(), - CheckNamespaces: CheckNamespaceFlag.Clone(), + IncludeDeprecatedChecks: IncludeDeprecatedChecksFlag.Clone(), + SkipCheckUpdate: SkipCheckUpdateFlag.Clone(), + Trace: TraceFlag.Clone(), + CheckPaths: ConfigCheckFlag.Clone(), + DataPaths: ConfigDataFlag.Clone(), + CheckNamespaces: CheckNamespaceFlag.Clone(), } } @@ -84,6 +92,7 @@ func (f *RegoFlagGroup) Name() string { func (f *RegoFlagGroup) Flags() []Flagger { return []Flagger{ + f.IncludeDeprecatedChecks, f.SkipCheckUpdate, f.Trace, f.CheckPaths, @@ -98,10 +107,11 @@ func (f *RegoFlagGroup) ToOptions() (RegoOptions, error) { } return RegoOptions{ - SkipCheckUpdate: f.SkipCheckUpdate.Value(), - Trace: f.Trace.Value(), - CheckPaths: f.CheckPaths.Value(), - DataPaths: f.DataPaths.Value(), - CheckNamespaces: f.CheckNamespaces.Value(), + IncludeDeprecatedChecks: f.IncludeDeprecatedChecks.Value(), + SkipCheckUpdate: f.SkipCheckUpdate.Value(), + Trace: f.Trace.Value(), + CheckPaths: f.CheckPaths.Value(), + DataPaths: f.DataPaths.Value(), + CheckNamespaces: f.CheckNamespaces.Value(), }, nil } diff --git a/pkg/iac/rego/embed_test.go b/pkg/iac/rego/embed_test.go index 36d136259a5d..35fd4a667e80 100644 --- a/pkg/iac/rego/embed_test.go +++ b/pkg/iac/rego/embed_test.go @@ -5,6 +5,7 @@ import ( checks "github.com/aquasecurity/trivy-checks" "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/open-policy-agent/opa/ast" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -103,3 +104,102 @@ deny[res]{ }) } } + +func Test_RegisterDeprecatedRule(t *testing.T) { + var testCases = []struct { + name string + id string + inputPolicy string + expected scan.Rule + }{ + { + name: "deprecated check", + id: "AVD-DEP-0001", + inputPolicy: `# METADATA +# title: "deprecated check" +# description: "some description" +# scope: package +# schemas: +# - input: schema["dockerfile"] +# custom: +# avd_id: AVD-DEP-0001 +# input: +# selector: +# - type: dockerfile +# deprecated: true +package builtin.dockerfile.DS1234 +deny[res]{ + res := true +}`, + expected: scan.Rule{ + Deprecated: true, + }, + }, + { + name: "not a deprecated check", + id: "AVD-NOTDEP-0001", + inputPolicy: `# METADATA +# title: "not a deprecated check" +# description: "some description" +# scope: package +# schemas: +# - input: schema["dockerfile"] +# custom: +# avd_id: AVD-NOTDEP-0001 +# input: +# selector: +# - type: dockerfile +package builtin.dockerfile.DS1234 +deny[res]{ + res := true +}`, + expected: scan.Rule{ + Deprecated: false, + }, + }, + { + name: "invalid deprecation value", + id: "AVD-BADDEP-0001", + inputPolicy: `# METADATA +# title: "badly deprecated check" +# description: "some description" +# scope: package +# schemas: +# - input: schema["dockerfile"] +# custom: +# avd_id: AVD-BADDEP-0001 +# input: +# selector: +# - type: dockerfile +# deprecated: "this is bad, deprecation is a bool value not a string" +package builtin.dockerfile.DS1234 +deny[res]{ + res := true +}`, + expected: scan.Rule{ + Deprecated: false, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + policies := make(map[string]*ast.Module) + newRule, err := ast.ParseModuleWithOpts("/rules/newrule.rego", tc.inputPolicy, ast.ParserOptions{ + ProcessAnnotation: true, + }) + require.NoError(t, err) + + policies["/rules/newrule.rego"] = newRule + assert.NotPanics(t, func() { + RegisterRegoRules(policies) + }) + + for _, rule := range rules.GetRegistered() { + if rule.AVDID == tc.id { + assert.Equal(t, tc.expected.Deprecated, rule.GetRule().Deprecated, tc.name) + } + } + }) + } +} diff --git a/pkg/iac/rego/load.go b/pkg/iac/rego/load.go index 2fd3955ce38f..f2e4c0645c4f 100644 --- a/pkg/iac/rego/load.go +++ b/pkg/iac/rego/load.go @@ -100,12 +100,12 @@ func (s *Scanner) LoadPolicies(enableEmbeddedLibraries, enableEmbeddedPolicies b if len(paths) > 0 { loaded, err := LoadPoliciesFromDirs(srcFS, paths...) if err != nil { - return fmt.Errorf("failed to load rego policies from %s: %w", paths, err) + return fmt.Errorf("failed to load rego checks from %s: %w", paths, err) } for name, policy := range loaded { s.policies[name] = policy } - s.debug.Log("Loaded %d policies from disk.", len(loaded)) + s.debug.Log("Loaded %d checks from disk.", len(loaded)) } if len(readers) > 0 { diff --git a/pkg/iac/rego/metadata.go b/pkg/iac/rego/metadata.go index 6d6996dd6828..5699276ad054 100644 --- a/pkg/iac/rego/metadata.go +++ b/pkg/iac/rego/metadata.go @@ -20,6 +20,7 @@ import ( const annotationScopePackage = "package" type StaticMetadata struct { + Deprecated bool ID string AVDID string Title string @@ -70,6 +71,12 @@ func (sm *StaticMetadata) Update(meta map[string]any) error { upd(&sm.RecommendedActions, "recommended_actions") upd(&sm.RecommendedActions, "recommended_action") + if raw, ok := meta["deprecated"]; ok { + if dep, ok := raw.(bool); ok { + sm.Deprecated = dep + } + } + if raw, ok := meta["severity"]; ok { sm.Severity = strings.ToUpper(fmt.Sprintf("%s", raw)) } @@ -208,6 +215,7 @@ func (m StaticMetadata) ToRule() scan.Rule { } return scan.Rule{ + Deprecated: m.Deprecated, AVDID: m.AVDID, Aliases: append(m.Aliases, m.ID), ShortCode: m.ShortCode, diff --git a/pkg/iac/rego/metadata_test.go b/pkg/iac/rego/metadata_test.go index d12b2d5d55f6..423ddc1a20d7 100644 --- a/pkg/iac/rego/metadata_test.go +++ b/pkg/iac/rego/metadata_test.go @@ -117,6 +117,23 @@ func Test_UpdateStaticMetadata(t *testing.T) { assert.Equal(t, expected, sm) }) + + t.Run("check is deprecated", func(t *testing.T) { + sm := StaticMetadata{ + Deprecated: false, + } + require.NoError(t, sm.Update(map[string]any{ + "deprecated": true, + })) + + expected := StaticMetadata{ + Deprecated: true, + CloudFormation: &scan.EngineMetadata{}, + Terraform: &scan.EngineMetadata{}, + } + + assert.Equal(t, expected, sm) + }) } func Test_getEngineMetadata(t *testing.T) { diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go index f2b9fff0fdf9..ceed9bd7ae6f 100644 --- a/pkg/iac/rego/scanner.go +++ b/pkg/iac/rego/scanner.go @@ -26,28 +26,33 @@ import ( var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { - ruleNamespaces map[string]struct{} - policies map[string]*ast.Module - store storage.Store - dataDirs []string - runtimeValues *ast.Term - compiler *ast.Compiler - regoErrorLimit int - debug debug.Logger - traceWriter io.Writer - tracePerResult bool - retriever *MetadataRetriever - policyFS fs.FS - dataFS fs.FS - frameworks []framework.Framework - spec string - inputSchema interface{} // unmarshalled into this from a json schema document - sourceType types.Source + ruleNamespaces map[string]struct{} + policies map[string]*ast.Module + store storage.Store + dataDirs []string + runtimeValues *ast.Term + compiler *ast.Compiler + regoErrorLimit int + debug debug.Logger + traceWriter io.Writer + tracePerResult bool + retriever *MetadataRetriever + policyFS fs.FS + dataFS fs.FS + frameworks []framework.Framework + spec string + inputSchema interface{} // unmarshalled into this from a json schema document + sourceType types.Source + includeDeprecatedChecks bool embeddedLibs map[string]*ast.Module embeddedChecks map[string]*ast.Module } +func (s *Scanner) SetIncludeDeprecatedChecks(b bool) { + s.includeDeprecatedChecks = b +} + func (s *Scanner) SetUseEmbeddedLibraries(b bool) { // handled externally } @@ -248,6 +253,10 @@ func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, continue } + if !s.includeDeprecatedChecks && staticMeta.Deprecated { + continue // skip deprecated checks + } + if isPolicyWithSubtype(s.sourceType) { // skip if check isn't relevant to what is being scanned if !isPolicyApplicable(staticMeta, inputs...) { diff --git a/pkg/iac/rego/scanner_test.go b/pkg/iac/rego/scanner_test.go index d2868764eda8..f2d115ba0582 100644 --- a/pkg/iac/rego/scanner_test.go +++ b/pkg/iac/rego/scanner_test.go @@ -1011,3 +1011,78 @@ deny { assert.Contains(t, buf.String(), `Error occurred while applying rule "deny" from check "checks/bad.rego"`) } + +func Test_RegoScanning_WithDeprecatedCheck(t *testing.T) { + var testCases = []struct { + name string + policy string + expectedResults int + }{ + { + name: "happy path check is deprecated", + policy: `# METADATA +# title: i am a deprecated check +# description: i am a description +# related_resources: +# - https://google.com +# custom: +# id: EG123 +# avd_id: AVD-EG-0123 +# severity: LOW +# recommended_action: have a cup of tea +# deprecated: true +package defsec.test + +deny { + input.text +} + +`, + expectedResults: 0, + }, + { + name: "happy path check is not deprecated", + policy: `# METADATA +# title: i am a deprecated check +# description: i am a description +# related_resources: +# - https://google.com +# custom: +# id: EG123 +# avd_id: AVD-EG-0123 +# severity: LOW +# recommended_action: have a cup of tea +package defsec.test + +deny { + input.text +} + +`, + expectedResults: 1, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + srcFS := CreateFS(t, map[string]string{ + "policies/test.rego": tc.policy, + }) + + scanner := NewScanner(types.SourceJSON) + require.NoError( + t, + scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), + ) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "/evil.lol", + Contents: map[string]interface{}{ + "text": "test", + }, + }) + require.NoError(t, err) + require.Len(t, results, tc.expectedResults, tc.name) + }) + } +} diff --git a/pkg/iac/scan/flat.go b/pkg/iac/scan/flat.go index c640b5fc14ac..a3abc143d273 100755 --- a/pkg/iac/scan/flat.go +++ b/pkg/iac/scan/flat.go @@ -6,6 +6,7 @@ import ( ) type FlatResult struct { + Deprecated bool `json:"deprecated,omitempty"` RuleID string `json:"rule_id"` LongID string `json:"long_id"` RuleSummary string `json:"rule_description"` @@ -48,6 +49,7 @@ func (r *Result) Flatten() FlatResult { } return FlatResult{ + Deprecated: r.rule.Deprecated, RuleID: r.rule.AVDID, LongID: r.Rule().LongID(), RuleSummary: r.rule.Summary, diff --git a/pkg/iac/scan/rule.go b/pkg/iac/scan/rule.go index a1a3ada18e99..c4318b7aad35 100755 --- a/pkg/iac/scan/rule.go +++ b/pkg/iac/scan/rule.go @@ -36,6 +36,7 @@ type TerraformCustomCheck struct { } type Rule struct { + Deprecated bool `json:"deprecated"` AVDID string `json:"avd_id"` Aliases []string `json:"aliases"` ShortCode string `json:"short_code"` @@ -55,6 +56,10 @@ type Rule struct { Check CheckFunc `json:"-"` } +func (r Rule) IsDeprecated() bool { + return r.Deprecated +} + func (r Rule) HasID(id string) bool { if r.AVDID == id || r.LongID() == id { return true diff --git a/pkg/iac/scanners/azure/arm/scanner.go b/pkg/iac/scanners/azure/arm/scanner.go index d9ae227a0992..b4bcfc539486 100644 --- a/pkg/iac/scanners/azure/arm/scanner.go +++ b/pkg/iac/scanners/azure/arm/scanner.go @@ -40,6 +40,8 @@ type Scanner struct { // nolint: gocritic sync.Mutex } +func (s *Scanner) SetIncludeDeprecatedChecks(b bool) {} + func (s *Scanner) SetSpec(spec string) { s.spec = spec } diff --git a/pkg/iac/scanners/cloudformation/scanner.go b/pkg/iac/scanners/cloudformation/scanner.go index 0920f4425fdb..1bbbe39f2117 100644 --- a/pkg/iac/scanners/cloudformation/scanner.go +++ b/pkg/iac/scanners/cloudformation/scanner.go @@ -64,6 +64,8 @@ type Scanner struct { // nolint: gocritic sync.Mutex } +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} + func (s *Scanner) addParserOptions(opt options.ParserOption) { s.parserOptions = append(s.parserOptions, opt) } diff --git a/pkg/iac/scanners/dockerfile/scanner.go b/pkg/iac/scanners/dockerfile/scanner.go index 88a18e35ed1a..29df54634d58 100644 --- a/pkg/iac/scanners/dockerfile/scanner.go +++ b/pkg/iac/scanners/dockerfile/scanner.go @@ -34,6 +34,8 @@ type Scanner struct { // nolint: gocritic loadEmbeddedPolicies bool } +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} + func (s *Scanner) SetSpec(spec string) { s.spec = spec } diff --git a/pkg/iac/scanners/helm/scanner.go b/pkg/iac/scanners/helm/scanner.go index e2b666082c97..fc54af44781f 100644 --- a/pkg/iac/scanners/helm/scanner.go +++ b/pkg/iac/scanners/helm/scanner.go @@ -43,6 +43,8 @@ type Scanner struct { mu sync.Mutex } +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} + func (s *Scanner) SetSpec(spec string) { s.spec = spec } diff --git a/pkg/iac/scanners/json/scanner.go b/pkg/iac/scanners/json/scanner.go index 5c53d0a10896..3d563c34c790 100644 --- a/pkg/iac/scanners/json/scanner.go +++ b/pkg/iac/scanners/json/scanner.go @@ -34,6 +34,8 @@ type Scanner struct { // nolint: gocritic loadEmbeddedLibraries bool } +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} + func (s *Scanner) SetRegoOnly(bool) { } diff --git a/pkg/iac/scanners/kubernetes/scanner.go b/pkg/iac/scanners/kubernetes/scanner.go index 121c954990a3..44f13ce5b003 100644 --- a/pkg/iac/scanners/kubernetes/scanner.go +++ b/pkg/iac/scanners/kubernetes/scanner.go @@ -38,6 +38,8 @@ type Scanner struct { // nolint: gocritic loadEmbeddedLibraries bool } +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} + func (s *Scanner) SetSpec(spec string) { s.spec = spec } diff --git a/pkg/iac/scanners/options/scanner.go b/pkg/iac/scanners/options/scanner.go index 02c01be5c95a..8e79b0c4a185 100644 --- a/pkg/iac/scanners/options/scanner.go +++ b/pkg/iac/scanners/options/scanner.go @@ -24,6 +24,7 @@ type ConfigurableScanner interface { SetRegoOnly(regoOnly bool) SetRegoErrorLimit(limit int) SetUseEmbeddedLibraries(bool) + SetIncludeDeprecatedChecks(bool) } type ScannerOption func(s ConfigurableScanner) @@ -65,6 +66,12 @@ func ScannerWithEmbeddedLibraries(enabled bool) ScannerOption { } } +func ScannerWithIncludeDeprecatedChecks(enabled bool) ScannerOption { + return func(s ConfigurableScanner) { + s.SetIncludeDeprecatedChecks(enabled) + } +} + // ScannerWithTrace specifies an io.Writer for trace logs (mainly rego tracing) - if not set, they are discarded func ScannerWithTrace(w io.Writer) ScannerOption { return func(s ConfigurableScanner) { diff --git a/pkg/iac/scanners/terraform/scanner.go b/pkg/iac/scanners/terraform/scanner.go index 1f051a166595..c999acf337f5 100644 --- a/pkg/iac/scanners/terraform/scanner.go +++ b/pkg/iac/scanners/terraform/scanner.go @@ -45,6 +45,8 @@ type Scanner struct { // nolint: gocritic loadEmbeddedPolicies bool } +func (s *Scanner) SetIncludeDeprecatedChecks(b bool) {} + func (s *Scanner) SetSpec(spec string) { s.spec = spec } diff --git a/pkg/iac/scanners/terraformplan/tfjson/scanner.go b/pkg/iac/scanners/terraformplan/tfjson/scanner.go index 1a73bd6af0cd..6f62d822177f 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/scanner.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner.go @@ -38,6 +38,8 @@ type Scanner struct { policyReaders []io.Reader } +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} + func (s *Scanner) SetUseEmbeddedLibraries(b bool) { s.loadEmbeddedLibraries = b } diff --git a/pkg/iac/scanners/toml/scanner.go b/pkg/iac/scanners/toml/scanner.go index 57f27df92db6..0a05fdbac18f 100644 --- a/pkg/iac/scanners/toml/scanner.go +++ b/pkg/iac/scanners/toml/scanner.go @@ -32,6 +32,8 @@ type Scanner struct { // nolint: gocritic loadEmbeddedLibraries bool } +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} + func (s *Scanner) SetRegoOnly(bool) {} func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { diff --git a/pkg/iac/scanners/yaml/scanner.go b/pkg/iac/scanners/yaml/scanner.go index 02a68f0d5566..0adc43bbd4cf 100644 --- a/pkg/iac/scanners/yaml/scanner.go +++ b/pkg/iac/scanners/yaml/scanner.go @@ -32,6 +32,8 @@ type Scanner struct { // nolint: gocritic loadEmbeddedPolicies bool } +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} + func (s *Scanner) SetRegoOnly(bool) {} func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { diff --git a/pkg/misconf/scanner.go b/pkg/misconf/scanner.go index 9d81851b844f..90ee90cb4216 100644 --- a/pkg/misconf/scanner.go +++ b/pkg/misconf/scanner.go @@ -54,6 +54,7 @@ type ScannerOption struct { DataPaths []string DisableEmbeddedPolicies bool DisableEmbeddedLibraries bool + IncludeDeprecatedChecks bool HelmValues []string HelmValueFiles []string @@ -217,6 +218,7 @@ func scannerOptions(t detection.FileType, opt ScannerOption) ([]options.ScannerO options.ScannerWithSkipRequiredCheck(true), options.ScannerWithEmbeddedPolicies(!opt.DisableEmbeddedPolicies), options.ScannerWithEmbeddedLibraries(!opt.DisableEmbeddedLibraries), + options.ScannerWithIncludeDeprecatedChecks(opt.IncludeDeprecatedChecks), } policyFS, policyPaths, err := CreatePolicyFS(opt.PolicyPaths) From 9d26ae88d66c92f87a9892ad98932693132d6fd6 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 16 May 2024 06:29:06 +0200 Subject: [PATCH 069/352] ci(deps): enable testifylint linter on .*_test.go$ (#6688) Signed-off-by: Matthieu MOREL --- .golangci.yaml | 45 +++++++--- pkg/cache/remote_test.go | 6 +- .../parser/dotnet/core_deps/parse_test.go | 2 +- .../parser/golang/binary/parse_test.go | 2 +- pkg/dependency/parser/java/pom/parse_test.go | 2 +- .../parser/nuget/config/parse_test.go | 2 +- .../parser/nuget/packagesprops/parse_test.go | 2 +- .../parser/ruby/gemspec/parse_test.go | 2 +- .../parser/rust/binary/parse_test.go | 2 +- pkg/detector/ospkg/oracle/oracle_test.go | 2 +- pkg/fanal/analyzer/analyzer_test.go | 4 +- pkg/fanal/analyzer/language/analyze_test.go | 2 +- .../language/dotnet/deps/deps_test.go | 2 +- .../analyzer/language/java/pom/pom_test.go | 2 +- .../analyzer/language/nodejs/pkg/pkg_test.go | 2 +- .../language/nodejs/pnpm/pnpm_test.go | 2 +- .../python/packaging/packaging_test.go | 2 +- .../analyzer/language/python/pip/pip_test.go | 2 +- .../language/ruby/gemspec/gemspec_test.go | 2 +- pkg/fanal/analyzer/os/alpine/alpine_test.go | 4 +- .../os/amazonlinux/amazonlinux_test.go | 2 +- pkg/fanal/analyzer/os/debian/debian_test.go | 2 +- .../analyzer/os/redhatbase/centos_test.go | 2 +- .../analyzer/os/redhatbase/fedora_test.go | 2 +- .../analyzer/os/redhatbase/oracle_test.go | 2 +- .../analyzer/os/redhatbase/redhatbase_test.go | 2 +- pkg/fanal/analyzer/os/ubuntu/ubuntu_test.go | 2 +- pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go | 2 +- pkg/fanal/applier/applier_test.go | 2 +- pkg/fanal/artifact/local/fs_test.go | 2 +- pkg/fanal/artifact/repo/git_test.go | 2 +- pkg/fanal/artifact/sbom/sbom_test.go | 2 +- pkg/fanal/cache/fs_test.go | 4 +- pkg/fanal/cache/redis_test.go | 10 +-- pkg/fanal/image/daemon/image_test.go | 4 +- pkg/fanal/image/daemon/podman_test.go | 2 +- pkg/fanal/image/image_test.go | 6 +- pkg/fanal/image/oci_test.go | 2 +- pkg/iac/adapters/arm/compute/adapt_test.go | 4 +- .../terraform/aws/dynamodb/adapt_test.go | 2 +- pkg/iac/detection/detect_test.go | 4 +- pkg/iac/rego/scanner_test.go | 88 +++++++++---------- pkg/iac/rules/register_test.go | 6 +- .../arm/parser/armjson/parse_array_test.go | 2 +- .../scanners/azure/arm/parser/parser_test.go | 2 +- .../cloudformation/parser/fn_builtin_test.go | 4 +- .../cloudformation/test/cf_scanning_test.go | 6 +- pkg/iac/scanners/helm/test/scanner_test.go | 2 +- pkg/iac/scanners/kubernetes/scanner_test.go | 10 +-- .../terraform/executor/executor_test.go | 4 +- pkg/iac/scanners/terraform/fs_test.go | 2 +- pkg/iac/scanners/terraform/ignore_test.go | 10 +-- .../parser/resolvers/registry_test.go | 2 +- pkg/module/memfs_test.go | 3 +- pkg/policy/policy_test.go | 4 +- pkg/rpc/client/client_test.go | 2 +- pkg/rpc/server/listen_test.go | 2 +- pkg/rpc/server/server_test.go | 8 +- pkg/scanner/local/scan_test.go | 2 +- pkg/scanner/scan_test.go | 2 +- pkg/utils/fsutils/fs_test.go | 2 +- 61 files changed, 168 insertions(+), 148 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 8a4d089f9524..92c4d6d3d164 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -66,26 +66,35 @@ linters-settings: ruleguard: failOn: all rules: '${configDir}/misc/lint/rules.go' + testifylint: + enable-all: true + disable: + - bool-compare + - expected-actual + - float-compare + - len + - require-error linters: disable-all: true enable: - - unused - - ineffassign - - typecheck - - govet - - revive - - gosec - - unconvert + - bodyclose + - gci - goconst + - gocritic - gocyclo - gofmt - - misspell - - bodyclose - - gci - gomodguard + - gosec + - govet + - ineffassign + - misspell + - revive - tenv - - gocritic + - testifylint + - typecheck + - unconvert + - unused run: go: '1.22' @@ -93,12 +102,24 @@ run: issues: exclude-files: - ".*_mock.go$" - - ".*_test.go$" - "integration/*" - "examples/*" exclude-dirs: - "pkg/iac/scanners/terraform/parser/funcs" # copies of Terraform functions exclude-rules: + - path: ".*_test.go$" + linters: + - bodyclose + - gci + - gocritic + - goconst + - gofmt + - gosec + - govet + - ineffassign + - misspell + - tenv + - unused - linters: - gosec text: "G304: Potential file inclusion" diff --git a/pkg/cache/remote_test.go b/pkg/cache/remote_test.go index 9ed9802096df..25ca003910b9 100644 --- a/pkg/cache/remote_test.go +++ b/pkg/cache/remote_test.go @@ -146,7 +146,7 @@ func TestRemoteCache_PutArtifact(t *testing.T) { c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) err := c.PutArtifact(tt.args.imageID, tt.args.imageInfo) if tt.wantErr != "" { - require.NotNil(t, err, tt.name) + require.Error(t, err, tt.name) assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { @@ -207,7 +207,7 @@ func TestRemoteCache_PutBlob(t *testing.T) { c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) err := c.PutBlob(tt.args.diffID, tt.args.layerInfo) if tt.wantErr != "" { - require.NotNil(t, err, tt.name) + require.Error(t, err, tt.name) assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { @@ -285,7 +285,7 @@ func TestRemoteCache_MissingBlobs(t *testing.T) { c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) gotMissingImage, gotMissingLayerIDs, err := c.MissingBlobs(tt.args.imageID, tt.args.layerIDs) if tt.wantErr != "" { - require.NotNil(t, err, tt.name) + require.Error(t, err, tt.name) assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { diff --git a/pkg/dependency/parser/dotnet/core_deps/parse_test.go b/pkg/dependency/parser/dotnet/core_deps/parse_test.go index dfd0a9cd96f7..a495fe0d61fe 100644 --- a/pkg/dependency/parser/dotnet/core_deps/parse_test.go +++ b/pkg/dependency/parser/dotnet/core_deps/parse_test.go @@ -41,7 +41,7 @@ func TestParse(t *testing.T) { got, _, err := NewParser().Parse(f) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) } else { require.NoError(t, err) diff --git a/pkg/dependency/parser/golang/binary/parse_test.go b/pkg/dependency/parser/golang/binary/parse_test.go index e3144064ffe3..c93d038c6d8b 100644 --- a/pkg/dependency/parser/golang/binary/parse_test.go +++ b/pkg/dependency/parser/golang/binary/parse_test.go @@ -128,7 +128,7 @@ func TestParse(t *testing.T) { got, _, err := binary.NewParser().Parse(f) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go index 3627a5c2eb90..1207f32adcf7 100644 --- a/pkg/dependency/parser/java/pom/parse_test.go +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -1408,7 +1408,7 @@ func TestPom_Parse(t *testing.T) { gotPkgs, gotDeps, err := p.Parse(f) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/dependency/parser/nuget/config/parse_test.go b/pkg/dependency/parser/nuget/config/parse_test.go index b216fd81d703..5ea128c2861a 100644 --- a/pkg/dependency/parser/nuget/config/parse_test.go +++ b/pkg/dependency/parser/nuget/config/parse_test.go @@ -46,7 +46,7 @@ func TestParse(t *testing.T) { got, _, err := config.NewParser().Parse(f) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/dependency/parser/nuget/packagesprops/parse_test.go b/pkg/dependency/parser/nuget/packagesprops/parse_test.go index 58c5209da333..a33dfb76611e 100644 --- a/pkg/dependency/parser/nuget/packagesprops/parse_test.go +++ b/pkg/dependency/parser/nuget/packagesprops/parse_test.go @@ -70,7 +70,7 @@ func TestParse(t *testing.T) { got, _, err := config.NewParser().Parse(f) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/dependency/parser/ruby/gemspec/parse_test.go b/pkg/dependency/parser/ruby/gemspec/parse_test.go index 292b6e7a74d7..dbf13826f7e0 100644 --- a/pkg/dependency/parser/ruby/gemspec/parse_test.go +++ b/pkg/dependency/parser/ruby/gemspec/parse_test.go @@ -83,7 +83,7 @@ func TestParse(t *testing.T) { got, _, err := gemspec.NewParser().Parse(f) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/dependency/parser/rust/binary/parse_test.go b/pkg/dependency/parser/rust/binary/parse_test.go index d914a93ffb4b..88da862c860a 100644 --- a/pkg/dependency/parser/rust/binary/parse_test.go +++ b/pkg/dependency/parser/rust/binary/parse_test.go @@ -77,7 +77,7 @@ func TestParse(t *testing.T) { got, gotDeps, err := binary.NewParser().Parse(f) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/detector/ospkg/oracle/oracle_test.go b/pkg/detector/ospkg/oracle/oracle_test.go index 530639c9ea73..8dd3dfc8900f 100644 --- a/pkg/detector/ospkg/oracle/oracle_test.go +++ b/pkg/detector/ospkg/oracle/oracle_test.go @@ -252,7 +252,7 @@ func TestScanner_Detect(t *testing.T) { s := NewScanner() got, err := s.Detect(nil, tt.args.osVer, nil, tt.args.pkgs) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { diff --git a/pkg/fanal/analyzer/analyzer_test.go b/pkg/fanal/analyzer/analyzer_test.go index 169bbce3e8e6..1aa2eab607e6 100644 --- a/pkg/fanal/analyzer/analyzer_test.go +++ b/pkg/fanal/analyzer/analyzer_test.go @@ -522,7 +522,7 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) { DisabledAnalyzers: tt.args.disabledAnalyzers, }) if err != nil && tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } @@ -549,7 +549,7 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) { wg.Wait() if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/fanal/analyzer/language/analyze_test.go b/pkg/fanal/analyzer/language/analyze_test.go index 8b6c9dad44de..6a996fc03172 100644 --- a/pkg/fanal/analyzer/language/analyze_test.go +++ b/pkg/fanal/analyzer/language/analyze_test.go @@ -97,7 +97,7 @@ func TestAnalyze(t *testing.T) { got, err := language.Analyze(tt.args.fileType, tt.args.filePath, tt.args.content, mp) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go b/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go index e3cdf2730e72..4e8cc0ba212b 100644 --- a/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go +++ b/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go @@ -63,7 +63,7 @@ func Test_depsLibraryAnalyzer_Analyze(t *testing.T) { }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/fanal/analyzer/language/java/pom/pom_test.go b/pkg/fanal/analyzer/language/java/pom/pom_test.go index 865f2cb5471b..8a169b929151 100644 --- a/pkg/fanal/analyzer/language/java/pom/pom_test.go +++ b/pkg/fanal/analyzer/language/java/pom/pom_test.go @@ -180,7 +180,7 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { }, }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go b/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go index c27032b45166..c2528c92f974 100644 --- a/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go +++ b/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go @@ -89,7 +89,7 @@ func Test_nodePkgLibraryAnalyzer_Analyze(t *testing.T) { }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go index c3a50922a569..130482746a20 100644 --- a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go @@ -54,7 +54,7 @@ func Test_pnpmPkgLibraryAnalyzer_Analyze(t *testing.T) { }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go index eac4d29f5e84..69eec5eba5f0 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go @@ -168,7 +168,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/fanal/analyzer/language/python/pip/pip_test.go b/pkg/fanal/analyzer/language/python/pip/pip_test.go index 0a930f5cce51..fc86c5a4fad8 100644 --- a/pkg/fanal/analyzer/language/python/pip/pip_test.go +++ b/pkg/fanal/analyzer/language/python/pip/pip_test.go @@ -65,7 +65,7 @@ func Test_pipAnalyzer_Analyze(t *testing.T) { }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go b/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go index 41e4746b68b6..69aff2a3ed61 100644 --- a/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go +++ b/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go @@ -92,7 +92,7 @@ func Test_gemspecLibraryAnalyzer_Analyze(t *testing.T) { }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/fanal/analyzer/os/alpine/alpine_test.go b/pkg/fanal/analyzer/os/alpine/alpine_test.go index 29a2496ba556..330695eaf117 100644 --- a/pkg/fanal/analyzer/os/alpine/alpine_test.go +++ b/pkg/fanal/analyzer/os/alpine/alpine_test.go @@ -39,10 +39,10 @@ func TestAlpineReleaseOSAnalyzer_Required(t *testing.T) { res, err := a.Analyze(context.Background(), test.input) if test.wantError != "" { - assert.NotNil(t, err) + assert.Error(t, err) assert.Equal(t, test.wantError, err.Error()) } else { - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, test.wantResult, res) } }) diff --git a/pkg/fanal/analyzer/os/amazonlinux/amazonlinux_test.go b/pkg/fanal/analyzer/os/amazonlinux/amazonlinux_test.go index 83fcd25fe76b..5ae7bc0d0f79 100644 --- a/pkg/fanal/analyzer/os/amazonlinux/amazonlinux_test.go +++ b/pkg/fanal/analyzer/os/amazonlinux/amazonlinux_test.go @@ -95,7 +95,7 @@ func Test_amazonlinuxOSAnalyzer_Analyze(t *testing.T) { ctx := context.Background() got, err := a.Analyze(ctx, tt.input) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { diff --git a/pkg/fanal/analyzer/os/debian/debian_test.go b/pkg/fanal/analyzer/os/debian/debian_test.go index 0366e87693e4..1fa36db41b07 100644 --- a/pkg/fanal/analyzer/os/debian/debian_test.go +++ b/pkg/fanal/analyzer/os/debian/debian_test.go @@ -59,7 +59,7 @@ func Test_debianOSAnalyzer_Analyze(t *testing.T) { Content: f, }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { diff --git a/pkg/fanal/analyzer/os/redhatbase/centos_test.go b/pkg/fanal/analyzer/os/redhatbase/centos_test.go index d85ea2494a89..bee789865363 100644 --- a/pkg/fanal/analyzer/os/redhatbase/centos_test.go +++ b/pkg/fanal/analyzer/os/redhatbase/centos_test.go @@ -45,7 +45,7 @@ func Test_centosOSAnalyzer_Analyze(t *testing.T) { Content: f, }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { diff --git a/pkg/fanal/analyzer/os/redhatbase/fedora_test.go b/pkg/fanal/analyzer/os/redhatbase/fedora_test.go index 48bbb3b42755..405bbd5356ba 100644 --- a/pkg/fanal/analyzer/os/redhatbase/fedora_test.go +++ b/pkg/fanal/analyzer/os/redhatbase/fedora_test.go @@ -45,7 +45,7 @@ func Test_fedoraOSAnalyzer_Analyze(t *testing.T) { Content: f, }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { diff --git a/pkg/fanal/analyzer/os/redhatbase/oracle_test.go b/pkg/fanal/analyzer/os/redhatbase/oracle_test.go index c30498481a23..1f3120b47763 100644 --- a/pkg/fanal/analyzer/os/redhatbase/oracle_test.go +++ b/pkg/fanal/analyzer/os/redhatbase/oracle_test.go @@ -45,7 +45,7 @@ func Test_oracleOSAnalyzer_Analyze(t *testing.T) { Content: f, }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { diff --git a/pkg/fanal/analyzer/os/redhatbase/redhatbase_test.go b/pkg/fanal/analyzer/os/redhatbase/redhatbase_test.go index 8d1688ef1b8a..b47174523a16 100644 --- a/pkg/fanal/analyzer/os/redhatbase/redhatbase_test.go +++ b/pkg/fanal/analyzer/os/redhatbase/redhatbase_test.go @@ -45,7 +45,7 @@ func Test_redhatOSAnalyzer_Analyze(t *testing.T) { Content: f, }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { diff --git a/pkg/fanal/analyzer/os/ubuntu/ubuntu_test.go b/pkg/fanal/analyzer/os/ubuntu/ubuntu_test.go index 042d28af9924..1aad32e8f124 100644 --- a/pkg/fanal/analyzer/os/ubuntu/ubuntu_test.go +++ b/pkg/fanal/analyzer/os/ubuntu/ubuntu_test.go @@ -45,7 +45,7 @@ func Test_ubuntuOSAnalyzer_Analyze(t *testing.T) { Content: f, }) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { diff --git a/pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go b/pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go index 1e01535db900..ae216d501fc9 100644 --- a/pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go +++ b/pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go @@ -63,7 +63,7 @@ glibc 2.35-2.cm2 1653816591 1653628955 Microsoft Corporation (none) 10855265 x86 a := rpmqaPkgAnalyzer{} result, err := a.parseRpmqaManifest(strings.NewReader(test.content)) if test.wantErr != "" { - assert.NotNil(t, err) + assert.Error(t, err) assert.Equal(t, test.wantErr, err.Error()) } else { assert.NoError(t, err) diff --git a/pkg/fanal/applier/applier_test.go b/pkg/fanal/applier/applier_test.go index a1844c5bb545..8ba2b3f1eb95 100644 --- a/pkg/fanal/applier/applier_test.go +++ b/pkg/fanal/applier/applier_test.go @@ -972,7 +972,7 @@ func TestApplier_ApplyLayers(t *testing.T) { got, err := a.ApplyLayers(tt.args.imageID, tt.args.layerIDs) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr, tt.name) } else { require.NoError(t, err, tt.name) diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index 176a53797616..3bbb9d2c3b79 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -248,7 +248,7 @@ func TestArtifact_Inspect(t *testing.T) { got, err := a.Inspect(context.Background()) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go index 0e7aadd411a9..e5ea1ad3a230 100644 --- a/pkg/fanal/artifact/repo/git_test.go +++ b/pkg/fanal/artifact/repo/git_test.go @@ -253,7 +253,7 @@ func Test_newURL(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got, err := newURL(tt.args.rawurl) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { diff --git a/pkg/fanal/artifact/sbom/sbom_test.go b/pkg/fanal/artifact/sbom/sbom_test.go index 7355c0c75abe..d7af10196a1c 100644 --- a/pkg/fanal/artifact/sbom/sbom_test.go +++ b/pkg/fanal/artifact/sbom/sbom_test.go @@ -408,7 +408,7 @@ func TestArtifact_Inspect(t *testing.T) { got, err := a.Inspect(context.Background()) if len(tt.wantErr) > 0 { - require.NotNil(t, err) + require.Error(t, err) found := false for _, wantErr := range tt.wantErr { if strings.Contains(err.Error(), wantErr) { diff --git a/pkg/fanal/cache/fs_test.go b/pkg/fanal/cache/fs_test.go index ba2d9fe33d01..4eb059f5c508 100644 --- a/pkg/fanal/cache/fs_test.go +++ b/pkg/fanal/cache/fs_test.go @@ -286,7 +286,7 @@ func TestFSCache_PutBlob(t *testing.T) { err = fs.PutBlob(tt.args.diffID, tt.args.layerInfo) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { @@ -366,7 +366,7 @@ func TestFSCache_PutArtifact(t *testing.T) { err = fs.PutArtifact(tt.args.imageID, tt.args.imageConfig) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { diff --git a/pkg/fanal/cache/redis_test.go b/pkg/fanal/cache/redis_test.go index 028860c4dfc2..99bdae01b3d4 100644 --- a/pkg/fanal/cache/redis_test.go +++ b/pkg/fanal/cache/redis_test.go @@ -73,7 +73,7 @@ func TestRedisCache_PutArtifact(t *testing.T) { err = c.PutArtifact(tt.args.artifactID, tt.args.artifactConfig) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { @@ -162,7 +162,7 @@ func TestRedisCache_PutBlob(t *testing.T) { err = c.PutBlob(tt.args.blobID, tt.args.blobConfig) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { @@ -247,7 +247,7 @@ func TestRedisCache_GetArtifact(t *testing.T) { got, err := c.GetArtifact(tt.artifactID) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } else { @@ -340,7 +340,7 @@ func TestRedisCache_GetBlob(t *testing.T) { got, err := c.GetBlob(tt.blobID) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } @@ -451,7 +451,7 @@ func TestRedisCache_MissingBlobs(t *testing.T) { missingArtifact, missingBlobIDs, err := c.MissingBlobs(tt.args.artifactID, tt.args.blobIDs) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/fanal/image/daemon/image_test.go b/pkg/fanal/image/daemon/image_test.go index a8462200e479..330041cfd42d 100644 --- a/pkg/fanal/image/daemon/image_test.go +++ b/pkg/fanal/image/daemon/image_test.go @@ -112,7 +112,7 @@ func Test_image_ConfigNameWithCustomDockerHost(t *testing.T) { Algorithm: "sha256", Hex: "a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", }, conf) - assert.Nil(t, err) + assert.NoError(t, err) } func Test_image_ConfigNameWithCustomPodmanHost(t *testing.T) { @@ -152,7 +152,7 @@ func Test_image_ConfigNameWithCustomPodmanHost(t *testing.T) { Algorithm: "sha256", Hex: "a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", }, conf) - assert.Nil(t, err) + assert.NoError(t, err) } func Test_image_ConfigFile(t *testing.T) { diff --git a/pkg/fanal/image/daemon/podman_test.go b/pkg/fanal/image/daemon/podman_test.go index d408def16e87..5d56facb8cd2 100644 --- a/pkg/fanal/image/daemon/podman_test.go +++ b/pkg/fanal/image/daemon/podman_test.go @@ -88,7 +88,7 @@ func TestPodmanImage(t *testing.T) { defer cleanup() if tt.wantErr { - assert.NotNil(t, err) + assert.Error(t, err) return } assert.NoError(t, err) diff --git a/pkg/fanal/image/image_test.go b/pkg/fanal/image/image_test.go index 551fbd6d682a..78147fb93f0e 100644 --- a/pkg/fanal/image/image_test.go +++ b/pkg/fanal/image/image_test.go @@ -281,7 +281,7 @@ func TestNewDockerImage(t *testing.T) { defer cleanup() if tt.wantErr { - assert.NotNil(t, err) + assert.Error(t, err) return } assert.NoError(t, err) @@ -399,7 +399,7 @@ func TestNewDockerImageWithPrivateRegistry(t *testing.T) { defer cleanup() if tt.wantErr != "" { - assert.NotNil(t, err) + assert.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr, err) } else { assert.NoError(t, err) @@ -490,7 +490,7 @@ func TestNewArchiveImage(t *testing.T) { img, err := NewArchiveImage(tt.args.fileName) switch { case tt.wantErr != "": - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr, tt.name) return default: diff --git a/pkg/fanal/image/oci_test.go b/pkg/fanal/image/oci_test.go index b7bcf4b8f640..6279fa1ed5c2 100644 --- a/pkg/fanal/image/oci_test.go +++ b/pkg/fanal/image/oci_test.go @@ -61,7 +61,7 @@ func TestTryOCI(t *testing.T) { t.Run(test.name, func(t *testing.T) { _, err := tryOCI(test.ociImagePath) if test.wantErr != "" { - assert.NotNil(t, err) + assert.Error(t, err) assert.Contains(t, err.Error(), test.wantErr, err) } else { assert.NoError(t, err) diff --git a/pkg/iac/adapters/arm/compute/adapt_test.go b/pkg/iac/adapters/arm/compute/adapt_test.go index 2021e08b95a8..8763a0aac28a 100644 --- a/pkg/iac/adapters/arm/compute/adapt_test.go +++ b/pkg/iac/adapters/arm/compute/adapt_test.go @@ -30,7 +30,7 @@ func Test_AdaptLinuxVM(t *testing.T) { output := Adapt(input) require.Len(t, output.LinuxVirtualMachines, 1) - require.Len(t, output.WindowsVirtualMachines, 0) + require.Empty(t, output.WindowsVirtualMachines) linuxVM := output.LinuxVirtualMachines[0] assert.True(t, linuxVM.OSProfileLinuxConfig.DisablePasswordAuthentication.IsTrue()) @@ -54,6 +54,6 @@ func Test_AdaptWindowsVM(t *testing.T) { output := Adapt(input) - require.Len(t, output.LinuxVirtualMachines, 0) + require.Empty(t, output.LinuxVirtualMachines) require.Len(t, output.WindowsVirtualMachines, 1) } diff --git a/pkg/iac/adapters/terraform/aws/dynamodb/adapt_test.go b/pkg/iac/adapters/terraform/aws/dynamodb/adapt_test.go index ae7002ac697b..3ebd86b70199 100644 --- a/pkg/iac/adapters/terraform/aws/dynamodb/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/dynamodb/adapt_test.go @@ -154,7 +154,7 @@ func TestLines(t *testing.T) { modules := tftestutil.CreateModulesFromSource(t, src, ".tf") adapted := Adapt(modules) - require.Len(t, adapted.DAXClusters, 0) + require.Empty(t, adapted.DAXClusters) require.Len(t, adapted.Tables, 1) table := adapted.Tables[0] diff --git a/pkg/iac/detection/detect_test.go b/pkg/iac/detection/detect_test.go index e5f875071e16..ceb7f65f143a 100644 --- a/pkg/iac/detection/detect_test.go +++ b/pkg/iac/detection/detect_test.go @@ -389,7 +389,7 @@ rules: func BenchmarkIsType_SmallFile(b *testing.B) { data, err := os.ReadFile(fmt.Sprintf("./testdata/%s", "small.file")) - assert.Nil(b, err) + assert.NoError(b, err) b.ReportAllocs() b.ResetTimer() @@ -400,7 +400,7 @@ func BenchmarkIsType_SmallFile(b *testing.B) { func BenchmarkIsType_BigFile(b *testing.B) { data, err := os.ReadFile(fmt.Sprintf("./testdata/%s", "big.file")) - assert.Nil(b, err) + assert.NoError(b, err) b.ReportAllocs() b.ResetTimer() diff --git a/pkg/iac/rego/scanner_test.go b/pkg/iac/rego/scanner_test.go index f2d115ba0582..83f3a2b1f959 100644 --- a/pkg/iac/rego/scanner_test.go +++ b/pkg/iac/rego/scanner_test.go @@ -59,8 +59,8 @@ deny { require.NoError(t, err) require.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) assert.Equal(t, "/evil.lol", results.GetFailed()[0].Metadata().Range().GetFilename()) assert.False(t, results.GetFailed()[0].IsWarning()) @@ -94,8 +94,8 @@ deny { require.NoError(t, err) require.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) assert.Equal(t, "/evil.lol", results.GetFailed()[0].Metadata().Range().GetFilename()) assert.False(t, results.GetFailed()[0].IsWarning()) @@ -128,8 +128,8 @@ warn { require.NoError(t, err) require.Equal(t, 1, len(results.GetFailed())) - require.Equal(t, 0, len(results.GetPassed())) - require.Equal(t, 0, len(results.GetIgnored())) + require.Empty(t, results.GetPassed()) + require.Empty(t, results.GetIgnored()) assert.True(t, results.GetFailed()[0].IsWarning()) } @@ -159,9 +159,9 @@ deny { }) require.NoError(t, err) - assert.Equal(t, 0, len(results.GetFailed())) + assert.Empty(t, results.GetFailed()) require.Equal(t, 1, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetIgnored()) assert.Equal(t, "/evil.lol", results.GetPassed()[0].Metadata().Range().GetFilename()) } @@ -202,8 +202,8 @@ exception[ns] { }) require.NoError(t, err) - assert.Equal(t, 0, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) + assert.Empty(t, results.GetFailed()) + assert.Empty(t, results.GetPassed()) assert.Equal(t, 1, len(results.GetIgnored())) } @@ -251,7 +251,7 @@ exception[ns] { require.NoError(t, err) assert.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) + assert.Empty(t, results.GetPassed()) assert.Equal(t, 1, len(results.GetIgnored())) } @@ -287,8 +287,8 @@ exception[rules] { }) require.NoError(t, err) - assert.Equal(t, 0, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) + assert.Empty(t, results.GetFailed()) + assert.Empty(t, results.GetPassed()) assert.Equal(t, 1, len(results.GetIgnored())) } @@ -324,8 +324,8 @@ exception[rules] { require.NoError(t, err) assert.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) } func Test_RegoScanning_WithRuntimeValues(t *testing.T) { @@ -358,8 +358,8 @@ deny_evil { require.NoError(t, err) assert.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) } func Test_RegoScanning_WithDenyMessage(t *testing.T) { @@ -389,8 +389,8 @@ deny[msg] { require.NoError(t, err) require.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) assert.Equal(t, "oh no", results.GetFailed()[0].Description()) assert.Equal(t, "/evil.lol", results.GetFailed()[0].Metadata().Range().GetFilename()) @@ -427,8 +427,8 @@ deny[res] { require.NoError(t, err) require.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) assert.Equal(t, "oh no", results.GetFailed()[0].Description()) assert.Equal(t, "/evil.lol", results.GetFailed()[0].Metadata().Range().GetFilename()) @@ -469,8 +469,8 @@ deny[res] { require.NoError(t, err) require.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) assert.Equal(t, "oh no", results.GetFailed()[0].Description()) assert.Equal(t, "/blah.txt", results.GetFailed()[0].Metadata().Range().GetFilename()) @@ -523,8 +523,8 @@ deny[res] { require.NoError(t, err) require.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) failure := results.GetFailed()[0] @@ -572,8 +572,8 @@ deny { require.NoError(t, err) assert.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) } func Test_RegoScanning_WithNonMatchingInputSelector(t *testing.T) { @@ -605,9 +605,9 @@ deny { }) require.NoError(t, err) - assert.Equal(t, 0, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetFailed()) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) } func Test_RegoScanning_NoTracingByDefault(t *testing.T) { @@ -637,10 +637,10 @@ deny { require.NoError(t, err) assert.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) - assert.Len(t, results.GetFailed()[0].Traces(), 0) + assert.Empty(t, results.GetFailed()[0].Traces()) } func Test_RegoScanning_GlobalTracingEnabled(t *testing.T) { @@ -672,11 +672,11 @@ deny { require.NoError(t, err) assert.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) - assert.Len(t, results.GetFailed()[0].Traces(), 0) - assert.Greater(t, len(traceBuffer.Bytes()), 0) + assert.Empty(t, results.GetFailed()[0].Traces()) + assert.NotEmpty(t, traceBuffer.Bytes()) } func Test_RegoScanning_PerResultTracingEnabled(t *testing.T) { @@ -706,10 +706,10 @@ deny { require.NoError(t, err) assert.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) - assert.Greater(t, len(results.GetFailed()[0].Traces()), 0) + assert.NotEmpty(t, results.GetFailed()[0].Traces()) } func Test_dynamicMetadata(t *testing.T) { @@ -934,8 +934,8 @@ deny { require.NoError(t, err) assert.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) } func Test_RegoScanning_InvalidFS(t *testing.T) { @@ -974,8 +974,8 @@ deny { require.NoError(t, err) assert.Equal(t, 1, len(results.GetFailed())) - assert.Equal(t, 0, len(results.GetPassed())) - assert.Equal(t, 0, len(results.GetIgnored())) + assert.Empty(t, results.GetPassed()) + assert.Empty(t, results.GetIgnored()) } func Test_NoErrorsWhenUsingBadRegoCheck(t *testing.T) { diff --git a/pkg/iac/rules/register_test.go b/pkg/iac/rules/register_test.go index 22eec16c0c66..e44acd3d94d2 100644 --- a/pkg/iac/rules/register_test.go +++ b/pkg/iac/rules/register_test.go @@ -16,7 +16,7 @@ func Test_Reset(t *testing.T) { _ = Register(rule) assert.Equal(t, 1, len(GetFrameworkRules())) Reset() - assert.Equal(t, 0, len(GetFrameworkRules())) + assert.Empty(t, GetFrameworkRules()) } func Test_Registration(t *testing.T) { @@ -112,7 +112,7 @@ func Test_Deregistration(t *testing.T) { require.Equal(t, 1, len(actual)) assert.Equal(t, "B", actual[0].GetRule().AVDID) Deregister(registrationB) - assert.Equal(t, 0, len(GetFrameworkRules())) + assert.Empty(t, GetFrameworkRules()) } func Test_DeregistrationMultipleFrameworks(t *testing.T) { @@ -135,5 +135,5 @@ func Test_DeregistrationMultipleFrameworks(t *testing.T) { require.Equal(t, 1, len(actual)) assert.Equal(t, "B", actual[0].GetRule().AVDID) Deregister(registrationB) - assert.Equal(t, 0, len(GetFrameworkRules())) + assert.Empty(t, GetFrameworkRules()) } diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go index 382da0b0c45d..87169460cd0d 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go @@ -14,7 +14,7 @@ func Test_Array_Empty(t *testing.T) { target := []int{} metadata := types.NewTestMetadata() require.NoError(t, Unmarshal(example, &target, &metadata)) - assert.Len(t, target, 0) + assert.Empty(t, target) } func Test_Array_ToSlice(t *testing.T) { diff --git a/pkg/iac/scanners/azure/arm/parser/parser_test.go b/pkg/iac/scanners/azure/arm/parser/parser_test.go index d54a147370e0..759127ca0b00 100644 --- a/pkg/iac/scanners/azure/arm/parser/parser_test.go +++ b/pkg/iac/scanners/azure/arm/parser/parser_test.go @@ -215,7 +215,7 @@ func TestParser_Parse(t *testing.T) { require.NoError(t, err) if !tt.wantDeployment { - assert.Len(t, got, 0) + assert.Empty(t, got) return } diff --git a/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go b/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go index 9a14029344a8..c5fbce41b489 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go @@ -20,7 +20,7 @@ func Test_cidr_generator(t *testing.T) { } ranges, err := calculateCidrs("10.1.0.0/16", 4, 4, original) - require.Nil(t, err) + require.NoError(t, err) require.Len(t, ranges, 4) results := make(map[int]string) @@ -47,7 +47,7 @@ func Test_cidr_generator_8_bits(t *testing.T) { } ranges, err := calculateCidrs("10.1.0.0/16", 4, 8, original) - require.Nil(t, err) + require.NoError(t, err) require.Len(t, ranges, 4) results := make(map[int]string) diff --git a/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go b/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go index 47063669a24d..3fd466e40ed8 100644 --- a/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go +++ b/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go @@ -19,7 +19,7 @@ func Test_basic_cloudformation_scanning(t *testing.T) { results, err := cfScanner.ScanFS(context.TODO(), os.DirFS("./examples/bucket"), ".") require.NoError(t, err) - assert.Greater(t, len(results.GetFailed()), 0) + assert.NotEmpty(t, results.GetFailed()) } func Test_cloudformation_scanning_has_expected_errors(t *testing.T) { @@ -28,7 +28,7 @@ func Test_cloudformation_scanning_has_expected_errors(t *testing.T) { results, err := cfScanner.ScanFS(context.TODO(), os.DirFS("./examples/bucket"), ".") require.NoError(t, err) - assert.Greater(t, len(results.GetFailed()), 0) + assert.NotEmpty(t, results.GetFailed()) } func Test_cloudformation_scanning_with_debug(t *testing.T) { @@ -44,5 +44,5 @@ func Test_cloudformation_scanning_with_debug(t *testing.T) { require.NoError(t, err) // check debug is as expected - assert.Greater(t, len(debugWriter.String()), 0) + assert.NotEmpty(t, debugWriter.String()) } diff --git a/pkg/iac/scanners/helm/test/scanner_test.go b/pkg/iac/scanners/helm/test/scanner_test.go index a46031a8fb98..fdcb0d3af813 100644 --- a/pkg/iac/scanners/helm/test/scanner_test.go +++ b/pkg/iac/scanners/helm/test/scanner_test.go @@ -357,5 +357,5 @@ deny[res] { require.NoError(t, err) require.Len(t, results, 1) - assert.Len(t, results.GetFailed(), 0) + assert.Empty(t, results.GetFailed()) } diff --git a/pkg/iac/scanners/kubernetes/scanner_test.go b/pkg/iac/scanners/kubernetes/scanner_test.go index 11d58b3ce30c..bf8ea32461eb 100644 --- a/pkg/iac/scanners/kubernetes/scanner_test.go +++ b/pkg/iac/scanners/kubernetes/scanner_test.go @@ -338,7 +338,7 @@ spec: `)) require.NoError(t, err) - assert.Greater(t, len(results.GetFailed()), 0) + assert.NotEmpty(t, results.GetFailed()) } func Test_FileScan_WithSeparator(t *testing.T) { @@ -358,7 +358,7 @@ spec: `)) require.NoError(t, err) - assert.Greater(t, len(results.GetFailed()), 0) + assert.NotEmpty(t, results.GetFailed()) } func Test_FileScan_MultiManifests(t *testing.T) { @@ -396,7 +396,7 @@ spec: for _, failure := range results.GetFailed() { actualCode, err := failure.GetCode() require.NoError(t, err) - assert.Greater(t, len(actualCode.Lines), 0) + assert.NotEmpty(t, actualCode.Lines) for _, line := range actualCode.Lines { assert.Greater(t, len(fileLines), line.Number) assert.Equal(t, line.Content, fileLines[line.Number-1]) @@ -514,7 +514,7 @@ spec: `)) require.NoError(t, err) - assert.Greater(t, len(results.GetFailed()), 0) + assert.NotEmpty(t, results.GetFailed()) firstResult := results.GetFailed()[0] assert.Equal(t, 2, firstResult.Metadata().Range().GetStartLine()) @@ -592,7 +592,7 @@ spec: `)) require.NoError(t, err) - require.Greater(t, len(results.GetFailed()), 0) + require.NotEmpty(t, results.GetFailed()) firstResult := results.GetFailed()[0] assert.Equal(t, 8, firstResult.Metadata().Range().GetStartLine()) diff --git a/pkg/iac/scanners/terraform/executor/executor_test.go b/pkg/iac/scanners/terraform/executor/executor_test.go index ac663c313c17..d33e5d999c38 100644 --- a/pkg/iac/scanners/terraform/executor/executor_test.go +++ b/pkg/iac/scanners/terraform/executor/executor_test.go @@ -56,7 +56,7 @@ resource "problem" "this" { results, err := New().Execute(modules) assert.Error(t, err) - assert.Equal(t, len(results.GetFailed()), 0) + assert.Empty(t, results.GetFailed()) } func Test_PanicInCheckAllowed(t *testing.T) { @@ -104,7 +104,7 @@ resource "problem" "this" { results, _ := New().Execute(modules) require.NoError(t, err) - assert.Equal(t, len(results.GetFailed()), 0) + assert.Empty(t, results.GetFailed()) } func Test_PanicNotInCheckNotIncludePassedStopOnError(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/fs_test.go b/pkg/iac/scanners/terraform/fs_test.go index 117b3c17ac6e..0089bc49a477 100644 --- a/pkg/iac/scanners/terraform/fs_test.go +++ b/pkg/iac/scanners/terraform/fs_test.go @@ -18,5 +18,5 @@ func Test_OS_FS(t *testing.T) { ) results, err := s.ScanFS(context.TODO(), os.DirFS("testdata"), "fail") require.NoError(t, err) - assert.Greater(t, len(results.GetFailed()), 0) + assert.NotEmpty(t, results.GetFailed()) } diff --git a/pkg/iac/scanners/terraform/ignore_test.go b/pkg/iac/scanners/terraform/ignore_test.go index ddddd7a6e04e..ce0596f157a9 100644 --- a/pkg/iac/scanners/terraform/ignore_test.go +++ b/pkg/iac/scanners/terraform/ignore_test.go @@ -673,7 +673,7 @@ func Test_IgnoreInline(t *testing.T) { secure = false # tfsec:ignore:%s } `, exampleRule.LongID())) - assert.Len(t, results.GetFailed(), 0) + assert.Empty(t, results.GetFailed()) } func Test_IgnoreWithAliasCodeStillIgnored(t *testing.T) { @@ -686,7 +686,7 @@ resource "bad" "my-rule" { } `, "testworkspace") - assert.Len(t, results.GetFailed(), 0) + assert.Empty(t, results.GetFailed()) } func Test_TrivyIgnoreWithAliasCodeStillIgnored(t *testing.T) { @@ -699,7 +699,7 @@ resource "bad" "my-rule" { } `, "testworkspace") - assert.Len(t, results.GetFailed(), 0) + assert.Empty(t, results.GetFailed()) } func Test_TrivyIgnoreInline(t *testing.T) { @@ -711,7 +711,7 @@ func Test_TrivyIgnoreInline(t *testing.T) { secure = false # trivy:ignore:%s } `, exampleRule.LongID())) - assert.Len(t, results.GetFailed(), 0) + assert.Empty(t, results.GetFailed()) } func Test_IgnoreInlineByAVDID(t *testing.T) { @@ -742,7 +742,7 @@ func Test_IgnoreInlineByAVDID(t *testing.T) { reg := rules.Register(exampleRule) defer rules.Deregister(reg) results := scanHCL(t, fmt.Sprintf(tc.input, id)) - assert.Len(t, results.GetFailed(), 0) + assert.Empty(t, results.GetFailed()) }) } } diff --git a/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go b/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go index a36c19ae4c1e..1adc8e9d2922 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go @@ -50,7 +50,7 @@ func Test_getPrivateRegistryTokenFromEnvVars_ConvertsSiteNameToEnvVar(t *testing t.Setenv(tt.tokenName, "abcd") token, err := getPrivateRegistryTokenFromEnvVars(tt.siteName) assert.Equal(t, "abcd", token) - assert.Equal(t, nil, err) + assert.NoError(t, err) }) } } diff --git a/pkg/module/memfs_test.go b/pkg/module/memfs_test.go index fe60004cb783..bac354087ae5 100644 --- a/pkg/module/memfs_test.go +++ b/pkg/module/memfs_test.go @@ -1,7 +1,6 @@ package module import ( - "errors" "io" "io/fs" "os" @@ -54,6 +53,6 @@ func TestMemFS_NilIsDirectory(t *testing.T) { t.Run("read invalid", func(t *testing.T) { buf := make([]byte, 4) _, err = f.Read(buf) - require.True(t, errors.Is(err, fs.ErrInvalid)) + require.ErrorIs(t, err, fs.ErrInvalid) }) } diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go index a12a8374f25e..2ac5d92399d9 100644 --- a/pkg/policy/policy_test.go +++ b/pkg/policy/policy_test.go @@ -124,7 +124,7 @@ func TestClient_LoadBuiltinPolicies(t *testing.T) { got, err := c.LoadBuiltinPolicies() if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } @@ -369,7 +369,7 @@ func TestClient_DownloadBuiltinPolicies(t *testing.T) { err = c.DownloadBuiltinPolicies(context.Background(), ftypes.RegistryOptions{}) if tt.wantErr != "" { - require.NotNil(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) return } diff --git a/pkg/rpc/client/client_test.go b/pkg/rpc/client/client_test.go index b3adeeed376c..e3c0b67ff433 100644 --- a/pkg/rpc/client/client_test.go +++ b/pkg/rpc/client/client_test.go @@ -201,7 +201,7 @@ func TestScanner_Scan(t *testing.T) { gotResults, gotOS, err := s.Scan(context.Background(), tt.args.target, tt.args.imageID, tt.args.layerIDs, tt.args.options) if tt.wantErr != "" { - require.NotNil(t, err, tt.name) + require.Error(t, err, tt.name) require.Contains(t, err.Error(), tt.wantErr, tt.name) return } diff --git a/pkg/rpc/server/listen_test.go b/pkg/rpc/server/listen_test.go index 2a3399bf5eb9..093aebca0c90 100644 --- a/pkg/rpc/server/listen_test.go +++ b/pkg/rpc/server/listen_test.go @@ -166,7 +166,7 @@ func Test_dbWorker_update(t *testing.T) { err := w.update(context.Background(), tt.args.appVersion, cacheDir, tt.needsUpdate.input.skip, &dbUpdateWg, &requestWg, ftypes.RegistryOptions{}) if tt.wantErr != "" { - require.NotNil(t, err, tt.name) + require.Error(t, err, tt.name) assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 8c19e897035b..ca4a66850faf 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -174,7 +174,7 @@ func TestScanServer_Scan(t *testing.T) { s := NewScanServer(mockDriver) got, err := s.Scan(context.Background(), tt.args.in) if tt.wantErr != "" { - require.NotNil(t, err, tt.name) + require.Error(t, err, tt.name) assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } @@ -273,7 +273,7 @@ func TestCacheServer_PutArtifact(t *testing.T) { got, err := s.PutArtifact(context.Background(), tt.args.in) if tt.wantErr != "" { - require.NotNil(t, err, tt.name) + require.Error(t, err, tt.name) assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { @@ -508,7 +508,7 @@ func TestCacheServer_PutBlob(t *testing.T) { got, err := s.PutBlob(context.Background(), tt.args.in) if tt.wantErr != "" { - require.NotNil(t, err, tt.name) + require.Error(t, err, tt.name) assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { @@ -573,7 +573,7 @@ func TestCacheServer_MissingBlobs(t *testing.T) { s := NewCacheServer(mockCache) got, err := s.MissingBlobs(tt.args.ctx, tt.args.in) if tt.wantErr != "" { - require.NotNil(t, err, tt.name) + require.Error(t, err, tt.name) assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go index f15c90f2964c..9bc8124d10ee 100644 --- a/pkg/scanner/local/scan_test.go +++ b/pkg/scanner/local/scan_test.go @@ -1371,7 +1371,7 @@ func TestScanner_Scan(t *testing.T) { s := NewScanner(applier, ospkg.NewScanner(), langpkg.NewScanner(), vulnerability.NewClient(db.Config{})) gotResults, gotOS, err := s.Scan(context.Background(), tt.args.target, "", tt.args.layerIDs, tt.args.options) if tt.wantErr != "" { - require.NotNil(t, err, tt.name) + require.Error(t, err, tt.name) require.Contains(t, err.Error(), tt.wantErr, tt.name) return } diff --git a/pkg/scanner/scan_test.go b/pkg/scanner/scan_test.go index bc6a0d6696e2..eaa0a6028e69 100644 --- a/pkg/scanner/scan_test.go +++ b/pkg/scanner/scan_test.go @@ -202,7 +202,7 @@ func TestScanner_ScanArtifact(t *testing.T) { s := NewScanner(d, mockArtifact) got, err := s.ScanArtifact(ctx, tt.args.options) if tt.wantErr != "" { - require.NotNil(t, err, tt.name) + require.Error(t, err, tt.name) require.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { diff --git a/pkg/utils/fsutils/fs_test.go b/pkg/utils/fsutils/fs_test.go index b6304ea7edb3..14d39c2fa5d4 100644 --- a/pkg/utils/fsutils/fs_test.go +++ b/pkg/utils/fsutils/fs_test.go @@ -64,7 +64,7 @@ func TestCopyFile(t *testing.T) { _, err := CopyFile(src, dst) if tt.wantErr != "" { - require.NotNil(t, err, tt.name) + require.Error(t, err, tt.name) assert.Equal(t, err.Error(), tt.wantErr, tt.name) } else { assert.NoError(t, err, tt.name) From 696f2ae0ecdd4f90303f41249924a09ace70dd78 Mon Sep 17 00:00:00 2001 From: Luke Young <91491244+lyoung-confluent@users.noreply.github.com> Date: Wed, 15 May 2024 23:03:41 -0700 Subject: [PATCH 070/352] fix: Golang version parsing from binaries w/GOEXPERIMENT (#6696) --- pkg/dependency/parser/golang/binary/parse.go | 6 +++++- .../parser/golang/binary/parse_test.go | 16 ++++++++++++++++ .../golang/binary/testdata/goexperiment | Bin 0 -> 1452446 bytes 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100755 pkg/dependency/parser/golang/binary/testdata/goexperiment diff --git a/pkg/dependency/parser/golang/binary/parse.go b/pkg/dependency/parser/golang/binary/parse.go index 171d3574800e..a50397db11b2 100644 --- a/pkg/dependency/parser/golang/binary/parse.go +++ b/pkg/dependency/parser/golang/binary/parse.go @@ -52,6 +52,10 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc return nil, nil, convertError(err) } + // Ex: "go1.22.3 X:boringcrypto" + stdlibVersion := strings.TrimPrefix(info.GoVersion, "go") + stdlibVersion, _, _ = strings.Cut(stdlibVersion, " ") + ldflags := p.ldFlags(info.Settings) pkgs := make(ftypes.Packages, 0, len(info.Deps)+2) pkgs = append(pkgs, []ftypes.Package{ @@ -69,7 +73,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc { // Add the Go version used to build this binary. Name: "stdlib", - Version: strings.TrimPrefix(info.GoVersion, "go"), + Version: stdlibVersion, Relationship: ftypes.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages. }, }...) diff --git a/pkg/dependency/parser/golang/binary/parse_test.go b/pkg/dependency/parser/golang/binary/parse_test.go index c93d038c6d8b..8b84c8dbbaf4 100644 --- a/pkg/dependency/parser/golang/binary/parse_test.go +++ b/pkg/dependency/parser/golang/binary/parse_test.go @@ -114,6 +114,22 @@ func TestParse(t *testing.T) { }, }, }, + { + name: "goexperiment", + inputFile: "testdata/goexperiment", + want: []ftypes.Package{ + { + Name: "", + Version: "", + Relationship: ftypes.RelationshipRoot, + }, + { + Name: "stdlib", + Version: "1.22.1", + Relationship: ftypes.RelationshipDirect, + }, + }, + }, { name: "sad path", inputFile: "testdata/dummy", diff --git a/pkg/dependency/parser/golang/binary/testdata/goexperiment b/pkg/dependency/parser/golang/binary/testdata/goexperiment new file mode 100755 index 0000000000000000000000000000000000000000..af68dd8a351d2876703cec87c0c64146380f23b4 GIT binary patch literal 1452446 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 z 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 z z8#KrT0=%~dxWyC#WaupfxInO}4Un;^gdxB!gf@i$7n*8-h?h2MlmNj-h#291P8gy_ z4A{}|*}+7^nm7~3h+&LtLWwnMoK`2J)fMI|<@^24Is3i)zNUm(e(PIvR-Qc1|JnQO zv(G-~?6c2#p69)-xBGejW6qgnHCvq$aow!#XT#;wLh4HpW$nQNoOcv9Twe#N zE}pQyJOcN5=RY?Gv6;j9&BZ4ujV-t~BzUEp8*ECqS1mM(W0JjM4b%3JZMUAspY;gf zpLNFQH`T?yt9bbm6%@xcBkIMt9VA!{XU| zl~BAlituc`_e_4q6~wlq!@~))YzVNFf&X?s-RV6cxQSJvMV4QUVS-|}!slxAU!NSb zo>_|WHjnywJHgiyM9*C9_`p)VKZv4;Q80qmnX53TjR=@jn47{hl3R`dYRsj<9 zsPO=&K2zZ(dAd5|)aTH%*T?l;Dj$LeYk8C6xf|a0qtAU8wTNGO=_&YF4QB-I)%EukN;CUI0_9usGTXPulXZKO z=d*PdE_z<}*kNNCgJ~HztH(qnrA>R=XFQ|5eKY=JCfY0Av~G=7$B`x@X~mEDL4?Z- z>W%}c6XPvqYdghBZJ6BT?^OAMw7Wg^`}6i{fqtUAeO{)$`p~rOQ`1$#4j_3H+*j!amC4(*rNWVQVFVN zHF|MtXj8mxbB**mUa9o$w%F2jC}60hP1zVotD^pSEV1@K+@Y1Q+04FWwQ1=j?ZHiH zPXiYg4nJ00D8COvtymiJPVjr~)IQ0%6#*CAY})Ydx0QXfHqW((KD}zSH+)LlMhn!3 zHt}IbxIMD8_l0^pX00LK;;RE#aVA!|&~&4D>vPTg6_G|&8L(}>9)5z5(%5D11v*>PF6R@Hn5US zBfKsH>)RddOSf^zrW($Kx59{q%7`z{m0rS1H9l+NHyg^LxxErn<3mE(x7?1qW!Y4w zpE?T4?A!|<%F{!?6pZ%{VQOzIsv4iJ;5S^z-Qku;ai_$cxO;@Z?PUy~4R^kk>fEA} z@wdMSG2G^SZ(Zoc3XpJxeZ_D?G>cC@+xBZ&gZQTEMsr8U>or}Q%4c@9>PNOEXyQXD z_nKc~Xxd&>Vi%yjvDBUfl{d&Y@vso-?6};%p>N;aHwNrwK9|Dj`PxP6bmd*y$9iY4 zK9s4$i99v1F2Q2`xGZ^w_6W0j=o0U^eZ*c!{jrGZlk4-E*s7YyzRX&qha6Iy=KEp$~1(r zuJ)bk>v`iO-ej!7B$Hm7V+<_c`bsx|O03=AkaVZI6Yf&7%N}W)K1VnDMnjLgn6h;< zdc891Lm}5;yvge1&B8gq2GloSmjKwu@N4Qj7FYLV=iR%q#oKmiA}T4dK##k$Sli0` zF1XX);$w1uhb_GFY^SeV)%}?=`)+VV8gu#*a@#yAKB2vjxXsmdC_rv%UfBT8&=>J{aVgvZmgfms~L9X zD!1&}vjXisrpWJ!l==M*Uh1ZY@uAI{SGKKJ-ovOb1=pTe-P;8w`JPkxPIm7tlVfeb zLb=xmnggBfI#Vr=yd>5&{snE8isxD0x_-N5G(WZH+H!Ln>F30^XL>R8k z+5@m&45$kIL!&)H?6Tk~`(wN;?2;jRbP0uedD=|F`(`iKvA?E8+Q+pn3gvwA_L+ov_CaZ#04Po-1G_6beAp?tf8 z9b0!Ui!MHUCO0!tjm%<>a!K1l2sWW5mtR||^Cds}aoby8+50^ECU*G&EI-l zWMy3zB}NJ}d+p_c&jpqQFQS`Y%=B$%7vq*MoO|yIU91aY{JQ930Q#l51B0fzgU$RH z>qM#-80)d#ODpT@aMIxDdr8H|12g;C-)Zmoj(58asxC9qdL4geYVM7f-iu)6G1H6d z)28;bY`j5NembT25(fdl#nBeXvGjF7XsDZB{^r-d=BBm$VjpjG+;ZDY*(rD`9rImu zjy?~6YkfG=9)a|ft*<(k$9#9bbz`jLBVsJZ>pttw`o#R2P0zaeIhWO6cBsz0G-vgj z*$Z2v%C_l!UJ$>tHNi%bo}#^*UgD+_jF;cYbgSFgj`r>?M4A@uWv8CXGxlXH-+KFP zzF1YeSLqx+;0p_5PAxw3<7Uon{^*r`C|>)(cw1^WccXw%nzvd-%1Xg`|3BxZw{3oPS!k?Qk%@l4QJrMZW%5hqn{Vgw zqF&VopS@_&q4wH4b@Te>T3uF`zG;i&+S&(xeUG=9an_bsbMCmQE*D>DsNt{I@G^|A zRx%sa7yhc3yyL}DB_{Ur4LHAnTvyffG&V1ZlFL%DOi;ZAp?!(Rar7*jEn2yEZx-(s z-}&@~NIu|rUfFc6lL+IktX*f`lp*qO26#^QX4Y)izxGw@*1YP6Z+P{qUbFTkA?5il z5Z-d9Z^qco>Ct|?x1s!r!uXrU>D=iIb*>)j40aYgY3TJsUH;z0;J0TVL*O`|J^HN+ z(E0lk1BGc`X8f~<@bPoNAe=uVZH1~V? ztDi_7Lhb;6HE(DY$7rW>#g*W>ouMLzTNgT)n}bq@hN#%k5ULLi&7s_Z`9<9P&cMLH zw;$~cS>?_*ILNXPer^qwn+V4*$gP4#P)Q1;;9V5`lga8iLewF=&Be`e(W_jCL6BXoqC+Mm6!la$o{d`0_f zKAq0@wfppxl4#(PCZ5x3a7{g*Yv<{u6(4Hn)9t*o(GRurns%OS=i^P@&c@$u_q)Gd zH5$LV%H7%Up%e9f_Y?KJrky8uRD7*7KpCseBXPQ}Otxsznd{V72%}EC2YHDjwh2%4zpI z4WGNI($}>6=i2>==_+^OFV^##Pu26zc3$@BR&M))Rg+Dh$#=E#-dO3KKWOFds(7lM z$FFYXPSyKsS~;Dct^8|#s-BN8tUc%SU)S@pkJtO%8*09Z#y9cdR!+-*=r1e1`-XOI z^iI=PYpX%EdOcqVS1o7g(T~eut0TYkPvuxr)k2_O7DTdfIyfc#nfS!P^&Ha!-u)EWB4aLB8kOK&whuk>QG#-Ja=S1r_M7N4ie#m`mU zzAaDvD>aD13+j`-S9^RB_V~&^aH&MNaG6fKyec_ zi#{*O>z8kyNFi@fCXzV!8?i-YdHvONYI8pYW%5{nU1Cy{xc#<<~ES9OA3qHGh7v$3tXOu1nnk zp^ue_YwP269dGrz=%3_detA9QA)G7M%Kw3OZ9RT1U2|D@tgnHedA)AK3qi$i4t3iY zWgM5GXnjkaDX2M=S@$pDzI64suf(-CytW*#;@bY+yDpT*!_6vxxP*D~6q$`jJRgPi zC^!k!Omb3Lb16{X!jC~b7aisfl2c> zd%6|=CbQV8$tZoD2J6XebuT(JJ-r^rK5cR+hLc}gnY=Fby>w_s&cM8$eP(7F)MOW9)uROIL z-uLM7v!wFc>*b~Y z^secXaZRhwoynVYyQ0dp?ev54c(_FLT-x`koni;EzRI&YbohIiUROILo$)T-FSedD zcfQxP&E}T-jemxRPA^X>>xoa9;c2##*16JEc$34*H?EP}(+y3o@P5&4-k|QT*{bSR zbf6D=--B*ABa^m}uKzblFZwhcip~sp@7KC?vJNwH29iFPURii+nDfrZnYY<(UhjJT zyb_j_`vaGhx+q`YIHOP1-+p}KjV~`{RXMeNYke=7xj!&(Mo!be@>Drq&zo26V0$uP zdwDIRSNF$PesO20wsny+XU4XjOKLyVR*zq}u;eZLS6(_4b-aWcdR^>>fVgkPy)HV; zeR3}^>h!K{BzctoJse(_@qEYgioCk^9b->~q%$Ly9Ufd<*A)L8v%gTLq+Yxm%*roz zcTkeVwap!w6_@@zI8Rcdi(=O3mibTy^<)cX!MvhWnFU9k2c?w)1_p~cdb|Vn#!9bx z%#>SX4dKKyo$;)kkT?GlcYi7_{Fe+B*XGSS-nb|}wajj1PL8rm!8z~HmBp+fo6d;0 zNoCEqch2bA!7o+gmzks|yD-fy#Gam=L3gcc-fG_(2KUOEr}rCr)V&)f;%_kz_wurL zW7dL;>^UJt?P^(3-1+|_H?Pbm7)s2 z^h+H?B=c9wjIF%)_yS8=j~b$7J6~c@M4BlZ5p6ymYynZoWe2UNN3j9jaPCZ$R^keI z)#*xnxGD0|@}(eg$vdyO>_RW?&>Fl)XULeLhcRHr^GG4T43_b(&0E^Olz-Xfmut)D z%UUL|a!{uzB;E2Y-h5)uZkgHzgMLX>zu?kFG_U!3^5-lZ&Z>z5D|M^TQsc{gf3ww7IV>yABtghmiYT|M`7n?o$z=%sF2F4t|4 zh=M?8CVv&w%Wq#nfY|ZddD7z-&be$}Pd+h18Ew*wK7*rIcRMfc(PM1)%Ic=d>RAUUv+=>ew}#6aYp`e8!6hZ0u)Ef}Gx_2w z#HB8CI`eRubHvSd#kOMGwe)TN>+vrfetc))qQWnI{`|T8<8h4F9G#~xTz$oYZpjyW znP0#B>&1J{$gkxWTRnf-l5XdDqp5tQ%*cDi56yYzv#u)gsvnw%UULe))~5`Ef{U(~ zU2(+~{OxP6YwS8T)NS&5D7;sf9G#AM zX~Oqm&?VrHS`g_M$hemC;~KsVgNTPElDB$!r~2fr^K2_e`fE0tl>eTBctS`16;Nq0`wSw3aq8zcYuIaD7pJ#fUuWk9S25$pDB$0IT z8Q7Qe#|x_bYvB$4n=rKUN{8gU6ML`Z%$7q*|L&?<-{npICmBqepiDRX+^1K2|J#QD z{JKtO0(y;tJDr!msM_-KR^Q{-bUNqF&tZCi#y-henWh)N9DZbJ1Jf0+=ycqR`wa&I z=|`#fa?Y={W0EdoKuCW^D_^=e=&uG(f!8(qe_90~{ba)@Z=g?rTQhc^b$!LJEL@$= z+kdFj*~0bzYWQ!!U9Q)+{MyU%!d~JfeRyfYzfOBn&JsPSNR(n z|II_Sy&r7&O+QlYeOtpXd0nS-j%)W3O}Bz~a{gq~_Yk-X{(cVACfZ{M=LZVHA3?CJ zs~Z1P-&Ey4)btathaOn|HHrT-6P?aMaFoCGma6|k?wMW+cKrNZL7k5FkQVkh`j)y6 zdw-)_-!ZPEzGCf}@z#X(2l9tMc-qEl?`yednmbYZ`_o$eer&S#wa>Tu{Uif-C46sg z3faGwfun!@DHXGQuWR`opXw9i^EcmK`^&;ccf86U-g9Mj`7j`sZM7X zcngQ=*T5?{U)J*Xx6gJM7eim(#-n&u!nW50INIxf{A}ev!9A1Zn?K6Gb!WBbXA717 z3s(QY{o}o#F777&e!SorRo=GN-V<-E{^VtYwZQ#9Px;jK39Y^>o>lu#)c=FDzwPnO zR^LBj0#v{7hu2M1`qfSTO^mOV(BH>ly8CIB|8Ru`FXuY&1oM^l_;92DIF4nS>tAX5 z=wH@=!{2D{J>ana>zJQL!9UdWbN*WZ?sAy^9eSA`w_lR}p^NPB5B=?J|5d-}zkmMt zDl7E+02S*(|3tIjXDCekY7Wzv8eYe>CRyao5(ZTRF1KBvLOW}^X(DWsJ{fS?H`qyb!hH8m%I&-ELuIY{}86ArFGL-Bu zj-d1Efo~H6#6I&J7ARDBmCLr)#_zP4@y*Z0XZS-hTeDkNgW0zwJ26J&$g>f0X-U-1AtL z^vvaEIo4|?^| zN8WzBg?wpKNq*?5-ADPhh4kA(&<8JIUPe_&|fk#Bw5Im0=v(|OI~ zUVG_lFaM)UUvufVFa5Tz?E=p5(dy42>Yr=A&d^lnuE8m`@7B)2m9zMH0yJl&`J}n! zf3IqCYExIHJ`?^KNeIFZa;thn=}f&P9hyIMX#S;#<_{m5KXz#T)bEw~2MT)tC6@ep z;}h@VId8^B=+5@=F{h>FS^9U2xFd8$e`lZ{ShzO7I_}qe*P}ftMNxLi?sfl60!n zqr11T2^@P1fBV$B_IgpPzk3TC$p5+~U%IhkrauA>y0NMHALcOGzUmY0`y;INt?xL8 z>6!5F;=H;b?B1>Q`Fsu3I`0A7zh2nrp8=0@{ndtDt1sg^*6QjjJ@ola=+@6NOw!da z=nrJE|!1c57as8%E8L7y)7^7?OqEicVe$atn7>zBzzXfFbTGgo8(vD z;FrD`{GI69H|THYJ#ovQp2L46+lMOm+NXbSMZ7W5{}{(L2L3?P=PTH61^6xwlY1|g z_mho&1B>z<;9rnP5>If>*tQS+t{bcUV|*-MUG4WVcudO~zv}zSroU^UhrzKH>hC{K zzp3i)ev|3Rtc9%qOAD%dVavG=|MNWTvk3fwMz?-DDJ$yN@890NuKKs=kFMpFrwo&O zCx;Kcue!IX_q4Uv(`}Yd%zxKbwE&q+T*Z%jyX3uMGtNwGeVaE^udgG^mJY8sh zvOPk-9q;XQ&VW}wsTTNY@G4~A*s$eogcRkuS7TjcuV&|)Yk!$+`Z&Jtg&yPEy(9ID zy(9N>wu56Y=U4Bn_1nxn(=Y#M?XO!aEd9swqW`!y7suMXzkiAMCI0^k^D^+Wo4wte zTEN^Gdo}(2+r2IRbr?1!J$b0kzf40VIloPD#V{>t_f*yQ{cXPe2lk4#Ly34p`Yv#$ zZ%gz)Vy|%__zTT`;*|-1-(8i?Feyp;nnbrhJKm%Jo`8QCS+=oBY<&WE?yh^6U5O;~ zYx~IWc+=NKydho!y@!b}#1UT@k0?Ll5wY?DPhyN!;1S9(S>AHaQQmh$$0g0mKbG?U z4)V3fJDPm)frQ6Ead5PUbnO}ZbGSD3uP@HBt{CRNhwHK6bA2_$JrDOUMXU=Gy|^PJf)@7*F-+w*#U}U+FJ5 z+rfRF(4n7CyM+DhZ_)ok->ApYR*%T9e0c)P_aT+}E|Y#&-?_OJ;Jh%w@%n~u=6V&! zSq_uWR;M`=CYI#*I)QCse2LeSH?Z@|Q8@Gb;&_kwrOW(z0{n}}Fgc$d=N$8?^YKY= z%*WCnN_68h>mTvGcxxXvf6$Hhw}2zQ>R&I2cS7IAVR|}^vXt{rP%o49(;iX3ANsy( zuVaNuoQ6Tyaea5IkMZAnaKwMEzjlCQ{iXk10gmCVb^<9xwqcC2l__v=Q&Kuo8lbuT%Ty4ygpI>VGh$x z2si(|*v(}8ru-WUVtux#+WP*6eU>%N^%om9K3&Xpj!(Pbh)<7E_&E5J%^rXMv?~AB zhL6&rDY8RYR?z6Ma7p()PNV!}L`!RqDN)^54O`!7r9bGQL&c91m;%z<&Y# z5ct!J_>ZLiQ=CJ8$HRT#JRS~#V?11Fy`ZgM%3*SSd6IMZKg(MVj`FOZIO^Yz|8!%G z_x=GtY*Ih%6Z$#6VCls8>R*4jzTO2r*4N@Q2|t+gm&s?Z);FHLNZc!{71U>eRaJ2#X!ZsY~frtaphfz)8OzgzRPki zd8ZTqIdJeh-)X-*->HB2Tj#qK;F#|o_+V|{e`xalCs=!Yio^7{pTM5b%1eXb{%8C5 zLs#DOT76ufb-}SdTRTIX0Clh}9PeVt)pO|Z|x}UcP9P_!) zs?UPEh1%)3o^$QpThD86IiRAG5nVJX+2ZP@jc_V_OiyPmQ?eXU{FUmLg% z{%h{3^ZoHgcm1}W>pw~S;^23DC_l!>U;TWwPx+vR``Yh7vX|q3iu;E-OwuhY?595w zhd;TP|M%Jd68PiUzxxe~!7)C>s}gp;m;i6$F#Y}yRQtca!p`4-HSOmb?%!{7{<6Fi zjqZH4k?TBvW!$=1C*o|_%epS&?7$J94?}mo6ZsDI#pBA6XNbe~iR+MzBj04ZAM$pC z$H>#=@cEeQ158o+n$M%@_Qh_ToV$i-G9TMm44+YVhUP8=b8v<>bvXy`DNhY>bl@xL z2^ek-;D81sIo3CUW2}p(`tbIIhZyVQa2K}{`|0?il_`?^AU1tUUYw$*Raj7N! zJ=lJ}n>?n=Xk`1_zb@#XYSPykXW};#2eylXUHo>EfX4o7L|yhsplC zg)?R+zxiHo;Pk&~e`{LQkSFWyeRm5&Dfj&n5_cr}~Hl+M`_M>m^g6*>= zvF)3&_Rjct!WUcHU2Ob*H~H0HolQQw(7q!Dbvl3l;_4s%uwm_`KRMj+E@ID(T)(K{ z-v)2z`Ue}f{N-F{|7CgEf0;j*uPvhd? z75&Xv-2R6fC`ta^Tm`@N9|vdsv{&e7{my}N{T~KL{mtJ6=ln~+IsX}O&VLr1^IQL% zACt_=zoRez&XnJ^v-)ZSlWXVooPVL9PRF&g_@0FKB;3C?wmuWkvpyTaQ6I<0X7FJS zlk?%xftmTx`C$dVE9QsiF|mzUGJ}~}J-d!9f>9$v< z?*a$i^0$L?`L=g1{|Gpje-s?$+kT6{vG#Utz7Cw%=IayPwfQtS{QG_HsN?G;PvRzN z!P~2S{*g8^xmTzCW9=rk{(+rumV(v6WP2Xs9QMBLy~N$fdisFWIzQdVb?|>T_FqfB zTz~5q^}kSiFM}TC+kPke@}EliKXzx07p;@2|5@!<8vP@c+z~V6I)VSC`K|vj@^bsz ze{%bGuvf1CAUNv(-Q@pH_Pfwpe4O$lo|kU>XS_S%{_)%Y?A~`>hS67>ne4B|qtRb~ zowc&{r<;`IyBc({Uc7X)(Vv~@D-zxMcfr|ywtu#tZJX`qcnkZvc4o+w^(&{&&ATf8 zEHS)k5^VmH1yyYQ18=&fmj6JbKXHQoPW|54@O9vG;CPm~?Gtrh_h$zygZ^`p%da(l z@xFxZ-)F$tpB)DuZv5wP^gFqZ`ZzJJ0>`t2r-4VoztZ^qPT(xpA8zu5r!71VvR*y_UdFY@I`C=mIj%iU zw)J$Te;@b1m%}t?kQY5WIIdR=cIVCO&YRPnGsk(XGce?E9OM;Hew1R0)&dU|J;+LD z?~bC`^+)^MiFlw|DW-hl07GIlcVr|!6O`|kJAa|pF}UyZ-B=+-`4OoG}tisEe-o#rS|y}hiMM{)-Qez@&9yr{qJG; zTy81)hrPvz5F&*=Z+g9+>FwPp788^MR+F}b!m58hN^sqZk?xxS0RQQy1hJmcUc>^YdUr}}Ow zsIG;z$4JA!Px<3qzrJDjRn_-%4ZE+pk?RjM{MqN!x#_<*tbVpn=%+oq(88V7qn;tz-S~Y4rD9SN*}shK)s*ac!MVzPGCV zrdxTwmpTNF_lkXvzY?6E<7>aDzx`Qy^k?_3wt~ONVbWjj6#jS*%Jy}9<@VhN9-$1=AsTj!^W$25jkWZbu{ZU*|E7-T9po_`Z}_^# z|7UNg?eXnK_c_2OuH!j?@0p3e+4x<{odr`&N$O|+&H6n89%=ON#viPMH|Jjuj{LiB ztM>l}<(S@xAyJ|9S+VlBan9pqJ2=LR`t1W}`L<8U*FO(~Bet@Amx80ce9v?YoZmA& z1djJieeZHD^`#q^q&~{a`sDHJcN}EtJy%x0UEr+WZgA*#p7Ch@-%tIy3(o!7{+s)= z{xABo&*`jhjJFHvXa2C4{OT9{>Zd+gzdhj4PkAfBxxSmhS>6tCmUkaG%R2-Pd6u`L zk3OF0mbU?%%bNn{@}|L2o@*WL5%Zh)B<&S=1@_$v{`*0gNdJ@Ae;fG!Y4{fCU2Okf zn*Dv2u>rm)@6*WRAHObSZuUq3)0{)T_0d=6`mO?x$V_s6Sjsui4@v*D@mE?Rh#Z|7)R&dnE`4^MUnt!#&T^y!S_p zCA}27^1jgU>%jZC{_TWqpTO=tErb@|4fvkIVsN~t;9i&g=~nWXq;KRL^!~jl{i(Lf z{`3$y{OOxMS>L0Z+uB!uJBI9_i`OOG|K6-?&vnR)wdW5{)OaQ451->MCciRFdv{lS zcPp=7p8ZjIxj(A^VKb9_&U=h=e$H!u3wxWt3q9vw36A{I_w>>4O?2nKhrpr#uTlRl z_~u3YM>+&v#`y;t_PxPTaDK0F44mI9obIFB{y{fhSOw1Ug89S0IzK2s=7(RPGwcPQ zGb07twbR=>cm-^7HKCAnTW%~ja@mCvp1 z8EbW)ZE3$R7w#ILpW-^?`K(L)`9^;O`OkB`Y^dHSzhQ&^@f$X)G4>>+1(#I1@!MuD z!@q8Uz7+i5(9`7Jq3zR}r}N8zjbO}FlKx_ft2GsN=3ZOv|Dxuv#G9aHytfY@?ZehL z^DjXEz}kB?^su+@$ywiD;4q2pzZvTf1DpR8IP$yqaSj}NA0H z?enXBUf;0%lU!&1Eq(mkz`_5-u`2(I&A!Il%b`d7{bJ~=z$Y91Dc4o`2OAy-?}h$A z!*6(Iou6Jz9Zc5G_KEt5NBgkzW6+J~4}l||zZmw}1t#m+b9=+#zb`~z3xCj!|Hb!k znELT;G_LxGJ>9#M|FFy?# zC(EA#XZgFpS^ji^}p)%iP~#rs~@F!t@jW77UB!P!1JKI(t(*Z9KnB3|(~ zb9R7-6-IjJ%j^1eCo)Y3FsAnU4+)=7_$d8l5w!g>ll<+DA@Ij4EYG&&zqRG}Jx0gg z-!{7Qzx^lA|C_<_EK2#y;mz_#!CAh#XZiZZEZ_QO`8&b?68Y;cB7b+Ee9O=Jt8dnS zDLCs-lg!e8Jvi&X7ySRF{@N?c&-%NbvwYlINzSLzuT1zr!t!@$zu@=Xo-N#p{nhgB zPs0Aj(j(ybjV0gPG~UhcZH|K@{|`go0%jU0>Cqpr@xoZcqXfS8pYLth_|fs4r1Is@R4u0{_oEdC9DGq+$m9jk^Y~_i^$RAjL>iYk1qMuB3`CT6czj$lH`iEii zX8*7l9RAUGVgoql%l>DP`Umy14NUsyeVj)My7+#m`8UCv^G~My(zo@|wu06 z@$o*q6k4Y5O!$ctb?hRxq_2OnuD`T_>AHy;i^a1n*AI^Mh`%qk9(mfQ>iX)<B`y2vC`zl}iW%=4K%RdFq@&~a`majk0@{JF({LOvx z<<0W9_Q~H1&hqV#S-$oQ`MAD(|7e@ zZ5DL<&_-}>gH7OQWBbf*aJKm#@Cb)Vy7tO+eOl0cUTO^g;mTh6>{70CdCS1LymjC# zZvq_hEKmJ1J^PrSVKTWF`b``r*PLC>({n`iw-pw0jy0!y)Dz&l(b41{z2(QAh-<_N zaIEQE^PLBOnZx87?+oY97j*I8qVSo>L3r~s5&LxH|H@GH33oMl?#Z0!qn`u^-De;x z!0}9E@3X7EFKhYzw$1*qq18`*%gg-7z?uJS;y;2s*JvMT_S0UY;A}5r_^_A#X%d{< z*Eu-1?+)-5$}l;{+y@@1usq{B0SZ*rK*8b8Ss zZ{vROFMKZcqi(wkckM6QGxrz!Yc6jZ9Qn1!YUF2o7=wg8q@U@dJEsR-d!h0ydv(EK zFZ;_FIQq-omsTJ3!l!VPTH?<4|29on~ZrDaXws7^}+gs&_|!VDq+_s z>%b8MyT;xRj`FnUFgW)=%MW|rGhX*>9)izwV7$uz{f4#QYVK$ItpSJqGSw->~G&zjHt|1`JWxD~vQ%lIARyTSIy_g8XRb1%eJ_cW~h1K^N92V;yv zV_25tn$hxt|4Goh;Exn)r}N_!zLM)tC%iRb^jglb z2W^Zgf9yfJ$Lty-)=;H?;V-g2i@~9f-ccYJq%@8>YNr?QW;HZw^-$T{fRTl-~u z%X^r^WPR*Exjwdc)Wpu<-{pDW^KFneAw~y9wj%U)=?<_di?;-FAhe`YBpR@cfIOMwr zG6p7WEy;HpR&(`)hNX{l9dyf6pIn~$M0vK)FgV&rygOm_nFeQl_JTtnC9O+H)uPFgzyrFdhOwSYg4&V}X@70gn96N7^&y3!ial zk2Nj7@~uynuYU@8mZv`tto+U3kT2c$>w~U5+at?62oAdZ{qiUJXtu~Wu-5`HiIgOv9Njo)usC%DevvhD-Nx2(pO=fDwPij^PO zZ(olke)lk3Z^xdOdt#2q*dtp_$6f`0EREeGbo{X_Dd}y{wfEN=?%zZFEBfDk(B9de zk$nh$_4{7RFqvQfo%4@D3;E9P+u)1&$ak=|LI3Y1cl9raxX%9N5pejI2S#f=zp3$y z&r@FD^3K=X+4=W_BftJ`oV@#6e*K;G&;D)+_|{e*-wB)m$2)=UiCh1?XMHa?@5!of zj8F61UXkBD5c_Z56B$m}JrwaLI85@d%$HI=eJk-zisw$ zKG>e<{d?#OZ>#p-*Z7@Z9FH--_`T2uaC|SMKH|_vduZ=$kCWiAhwXnJoZDadBOE5_ z#=Du`1qWUE!|-MKi@_mZd#?az|2GB>|K)zvI5_WDtpwl2VY2-7oOAgT;QyWSm&2dS zSAH&EdAWS;m&@M<&gI))QU0&~Sgn7K&%4kgKK~2omY?a?Kj>E@_!#)Q*1o>ec^^35 z=~Vv7zWk?Be(m1{XZtS!hyAtRI5_KX{X>6$%WESz-l>s)vX9^X8~pwqBJJ^U4wL*0 ziCOZOfrI~I<59Z0W&L-6v;Nv6>wgFw@-P1=V8ic*u78XCp8@NS?r!blet_d4;(;f_uD$a&CED}#jsIq@*KnNWFv)NI zg8w6*s`2HU6aO^y;J=n@$8VNr`v?7EyhWyiDWtw~^XPomo?wNcCdYt%8 zo0xpZeK+UW4|aZC0`EZ>dHZw^-4-UF`A+bwfzsXq) z-0zYEU!wP()16P)w!0*|!(&cDN4-;nUi zgk2x50Nck*4~*99O%;}JAf~vE{L&roL3e-YBsli7T>rX0jQCpqCE(z9zj_h;#@r_N zr!7DBr^Vw58*lFe=Xm=FILF%=oBvG8FFxOgwRe3Nag2uB8S28PUL60 z*bn^G#_v0si@=C2>EDyz{`sd3`+R3NIG)cOhi-p~-==bWxgL)3HJAO*E_t8AJ=0a- zRh(me>-bp;j`5>CSAxTyFNc3GSe;B0;6t3B-mv542)N5(l75bJrauhsa+u~$)c8F9 zPMY!hG44ma{zT}Tkj?Fqt^?cG@;e362z<#5$ z9(sJE;{NC)IPZ_D&tVRe`y0xO{SEiK#d*Jb0rGSHN1#W3u7R4 zzl`(83gTM_aF^?t&t1RU_w)MQ{t@%J^&LWf)K`AnFZ1sOXMWo=_}!oBf}{V6osR!YWESAj2m3QCf+r+p{vX}qx;oZHj!HB#9-{+9b9t{vm1 zFD$Kd>9-iiCiR=*9QuiO^x^4*&A%F1IsZ~{&OgzIHz(}xDVSpX`Hq+gw^x$yh^g;b z!@eWd<(g(G$@d9%aW&n>hyG+6IQ)svdk=x{rc9IX6WHFde$hW20*8O{w+8P8zl6i2 zKH{v;DR9>3G&uBG^ufBH`C{&w)YtY2y7KHFS>8EtmiKVt|Hr8sPuK@dmwl*?m$?l; z1*|`IjF_GSc0BxJg`Jh)vWFX9d3j|B;ve+xMBJAS6XdHn1Gk2Lv?*Ms0ZUhf0Pc$Gd5 zZ>FyX2i^U@{ovUD^P9yZ;8@S@0iOonk4%&LoaLPL=|CGP)JnHLneNyQy6d|xyhj_q z`z0&6KHRYDxh~fS8`d6&xDI=`o-=RoyPi7^&g;3;;D}dUFAfjPtQT)>__Oesw9gpl zY#;Rr`}ob$Ch!+HOyb>~17B!=U4Qf2g`3~q`1u-NsG~{yIli)e9A6`qT;@~fi-;%H zPkpj}YrvtO@|2b3Z3Txs=L_`>|1ZDooB6e8=0B78-47oF$NJ0pOnLShlk?dQ&Urqw z{5+qHQ@=bvje+z0GzAX*c46$Z;6EHzI=+Veu@2*RE%!`czpAcx-c@1e9C#zwzt-^0 z-8$dqzjdU)BrGk-{Aam}{QnmD_Rm|Xr%8G1z#-55$jxAeX-TELIWu&z{w4UI{>-}G z_{pYk|K9@g_usZ7DSjX5h0m_*p8{8w}p@8eW<0#Aq*_(FUM;8-`TM1$sJth_2f42`y2i8 z>nlCl_gP@;|H?);|9P%+e(fFkUB5bB?rHoNd!ORw*VOiY%V5o5f85CB;g;X^kot!I z{t*1O_uCu4^Vx3jgN^^Up+C&^t&Q$}r22lX(e>Y5^o#!Zp_}m^;8$Z)li2%bBJN>|@ZpGi@O`du*Z7tp+>390 zKClY>`9}W=cs18|H7xxQ@-kgt5On{p`c8OnYy7WTRO9RW8}^$m{o@xKmVX=9!SDMs zqu}6w<=d*gKHco^Z*Q-K{sqc3>0i!q-d|AlFK4*U{^bNX?BV+qi^+R!<9B^#d*%6W z3Y`7TQE;@c>l@oY+ROE^<3HBN#%rs=5wEG=CUDko0-W_*0}lNzq|Y|!kEM_Gc?f#c z=R*5w%g{Icqx0o@@b|R(_m7W$Ye_0_*deFvUd$NTO^cmCSIb z&m=h8$N6ETlFR<}>Nzv=-yRF7GTj%JcpE<=`)Kn4G_saE|%Q{lzix zQHdny!*R}eKD0l_eCYf8<_~}5{-X7d{_Xza3h{)aLC^Rzwd+nI`>S@pX1<|KOJwYxF6%q_E`^(_R*f0XqG*sjC+m+=0&1vcz`#1guhyME+iQdJYL0A4JaF%a;8}fah;}G~NWSFea5pb^WQE;yB z$v*n2ME5s0?2q|3H+F#YZ*FV@hd(u5(7q8bIR704hrjyWTWUP>m(8Dwecv#!?YA3z zm^`N9FA{b=K0J43JuY3_1i$UQntajT?x%MK7!UB6jIW2lc|YCuiTu*Ff2MB&2VHq< z(I?AW56=86lfKG3-bc56LZ0){GRhD8eG_NKzs~=_PnrDNHtRUY-?ovy0~~bQcQ-h< z?;dcpulwcu!Fj*@066cL9|6byrTt6$k)JOYntH3M%I=dprn`|RX8>SO%04;2HzW{oq0P^M3FmaFl2H-GnXg zAUMh^{uKWk_;+t7-b26Fz+-ZKrvA60gQ*|8K9l~rLap<$_QQ-NU5Ku|g6{VV>%sZ` zrwMTE7yJFg7Vz_^v&rutwsO`cCjIMP&f#C(zgPe0|Nh?Z3Gf3PrYBC+{ew3agkJ+c zjI8`TbDX+|e?Ia_<_F3?%srFeSa0OoGEDB@Phh{;zZY*y*!(9hnaS_-i529t4NcDP z_RpB#{a$h#IKG!O-m!i;-q`~V`x!4tk9fiT)zjeEU-dVQjJHm5&*X0!IUd8ldKK^Y$$Irckm0|jsbv^U?hQAE9y>4�q_p+n;S0k7x)D= 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

GnPM;eFG?NIb&UDIR}w$>10>_x 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

`{X?K};po7F@M}d$c&(@?d2v_I$ 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 D4$U^6 literal 0 HcmV?d00001 From 4d4ea1d51c2f02c9493827addee82b04e4169fc2 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 17 May 2024 11:35:42 +0400 Subject: [PATCH 071/352] refactor: unify package addition and vulnerability scanning (#6579) Signed-off-by: knqyf263 --- pkg/scanner/langpkg/scan.go | 71 +++++++++++++++++++------------------ pkg/scanner/local/scan.go | 66 ++++++++++------------------------ pkg/scanner/ospkg/scan.go | 50 ++++++++++++-------------- 3 files changed, 76 insertions(+), 111 deletions(-) diff --git a/pkg/scanner/langpkg/scan.go b/pkg/scanner/langpkg/scan.go index 2606727d56e8..6fb394d31336 100644 --- a/pkg/scanner/langpkg/scan.go +++ b/pkg/scanner/langpkg/scan.go @@ -24,7 +24,6 @@ var ( ) type Scanner interface { - Packages(target types.ScanTarget, options types.ScanOptions) types.Results Scan(ctx context.Context, target types.ScanTarget, options types.ScanOptions) (types.Results, error) } @@ -34,24 +33,7 @@ func NewScanner() Scanner { return &scanner{} } -func (s *scanner) Packages(target types.ScanTarget, _ types.ScanOptions) types.Results { - var results types.Results - for _, app := range target.Applications { - if len(app.Packages) == 0 { - continue - } - - results = append(results, types.Result{ - Target: targetName(app.Type, app.FilePath), - Class: types.ClassLangPkg, - Type: app.Type, - Packages: app.Packages, - }) - } - return results -} - -func (s *scanner) Scan(ctx context.Context, target types.ScanTarget, _ types.ScanOptions) (types.Results, error) { +func (s *scanner) Scan(ctx context.Context, target types.ScanTarget, opts types.ScanOptions) (types.Results, error) { apps := target.Applications log.Info("Number of language-specific files", log.Int("num", len(apps))) if len(apps) == 0 { @@ -66,27 +48,29 @@ func (s *scanner) Scan(ctx context.Context, target types.ScanTarget, _ types.Sca } ctx = log.WithContextPrefix(ctx, string(app.Type)) + result := types.Result{ + Target: targetName(app.Type, app.FilePath), + Class: types.ClassLangPkg, + Type: app.Type, + } - // Prevent the same log messages from being displayed many times for the same type. - if _, ok := printedTypes[app.Type]; !ok { - log.InfoContext(ctx, "Detecting vulnerabilities...") - printedTypes[app.Type] = struct{}{} + if opts.ListAllPackages { + sort.Sort(app.Packages) + result.Packages = app.Packages } - log.DebugContext(ctx, "Scanning packages from the file", log.String("file_path", app.FilePath)) - vulns, err := library.Detect(ctx, app.Type, app.Packages) - if err != nil { - return nil, xerrors.Errorf("failed vulnerability detection of packages: %w", err) - } else if len(vulns) == 0 { - continue + if opts.Scanners.Enabled(types.VulnerabilityScanner) { + var err error + result.Vulnerabilities, err = s.scanVulnerabilities(ctx, app, printedTypes) + if err != nil { + return nil, err + } } - results = append(results, types.Result{ - Target: targetName(app.Type, app.FilePath), - Vulnerabilities: vulns, - Class: types.ClassLangPkg, - Type: app.Type, - }) + if len(result.Packages) == 0 && len(result.Vulnerabilities) == 0 { + continue + } + results = append(results, result) } sort.Slice(results, func(i, j int) bool { return results[i].Target < results[j].Target @@ -94,6 +78,23 @@ func (s *scanner) Scan(ctx context.Context, target types.ScanTarget, _ types.Sca return results, nil } +func (s *scanner) scanVulnerabilities(ctx context.Context, app ftypes.Application, printedTypes map[ftypes.LangType]struct{}) ( + []types.DetectedVulnerability, error) { + + // Prevent the same log messages from being displayed many times for the same type. + if _, ok := printedTypes[app.Type]; !ok { + log.InfoContext(ctx, "Detecting vulnerabilities...") + printedTypes[app.Type] = struct{}{} + } + + log.DebugContext(ctx, "Scanning packages for vulnerabilities", log.String("file_path", app.FilePath)) + vulns, err := library.Detect(ctx, app.Type, app.Packages) + if err != nil { + return nil, xerrors.Errorf("failed vulnerability detection of libraries: %w", err) + } + return vulns, err +} + func targetName(appType ftypes.LangType, filePath string) string { if t, ok := PkgTargets[appType]; ok && filePath == "" { // When the file path is empty, we will overwrite it with the pre-defined value. diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 4b1591e0a52f..99146de0d55b 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -14,6 +14,7 @@ import ( "golang.org/x/xerrors" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + ospkgDetector "github.com/aquasecurity/trivy/pkg/detector/ospkg" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/applier" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -105,39 +106,19 @@ func (s Scanner) Scan(ctx context.Context, targetName, artifactKey string, blobK } func (s Scanner) ScanTarget(ctx context.Context, target types.ScanTarget, options types.ScanOptions) (types.Results, ftypes.OS, error) { - var eosl bool - var results, pkgResults types.Results - var err error + var results types.Results // By default, we need to remove dev dependencies from the result // IncludeDevDeps option allows you not to remove them excludeDevDeps(target.Applications, options.IncludeDevDeps) - // Fill OS packages and language-specific packages - if options.ListAllPackages { - if res := s.osPkgScanner.Packages(target, options); len(res.Packages) != 0 { - pkgResults = append(pkgResults, res) - } - pkgResults = append(pkgResults, s.langPkgScanner.Packages(target, options)...) - } - - // Scan packages for vulnerabilities - if options.Scanners.Enabled(types.VulnerabilityScanner) { - var vulnResults types.Results - vulnResults, eosl, err = s.scanVulnerabilities(ctx, target, options) - if err != nil { - return nil, ftypes.OS{}, xerrors.Errorf("failed to detect vulnerabilities: %w", err) - } - target.OS.Eosl = eosl - - // Merge package results into vulnerability results - mergedResults := s.fillPkgsInVulns(pkgResults, vulnResults) - - results = append(results, mergedResults...) - } else { - // If vulnerability scanning is not enabled, it just adds package results. - results = append(results, pkgResults...) + // Add packages if needed and scan packages for vulnerabilities + vulnResults, eosl, err := s.scanVulnerabilities(ctx, target, options) + if err != nil { + return nil, ftypes.OS{}, xerrors.Errorf("failed to detect vulnerabilities: %w", err) } + target.OS.Eosl = eosl + results = append(results, vulnResults...) // Store misconfigurations results = append(results, s.misconfsToResults(target.Misconfigurations, options)...) @@ -172,17 +153,24 @@ func (s Scanner) ScanTarget(ctx context.Context, target types.ScanTarget, option func (s Scanner) scanVulnerabilities(ctx context.Context, target types.ScanTarget, options types.ScanOptions) ( types.Results, bool, error) { + if !options.ListAllPackages && !options.Scanners.Enabled(types.VulnerabilityScanner) { + return nil, false, nil + } + var eosl bool var results types.Results if slices.Contains(options.VulnType, types.VulnTypeOS) { vuln, detectedEOSL, err := s.osPkgScanner.Scan(ctx, target, options) - if err != nil { + switch { + case errors.Is(err, ospkgDetector.ErrUnsupportedOS): + // do nothing + case err != nil: return nil, false, xerrors.Errorf("unable to scan OS packages: %w", err) - } else if vuln.Target != "" { + case vuln.Target != "": results = append(results, vuln) + eosl = detectedEOSL } - eosl = detectedEOSL } if slices.Contains(options.VulnType, types.VulnTypeLibrary) { @@ -196,24 +184,6 @@ func (s Scanner) scanVulnerabilities(ctx context.Context, target types.ScanTarge return results, eosl, nil } -func (s Scanner) fillPkgsInVulns(pkgResults, vulnResults types.Results) types.Results { - var results types.Results - if len(pkgResults) == 0 { // '--list-all-pkgs' == false or packages not found - return vulnResults - } - for _, result := range pkgResults { - if r, found := lo.Find(vulnResults, func(r types.Result) bool { - return r.Class == result.Class && r.Target == result.Target && r.Type == result.Type - }); found { - r.Packages = result.Packages - results = append(results, r) - } else { // when package result has no vulnerabilities we still need to add it to result(for 'list-all-pkgs') - results = append(results, result) - } - } - return results -} - func (s Scanner) misconfsToResults(misconfs []ftypes.Misconfiguration, options types.ScanOptions) types.Results { if !ShouldScanMisconfigOrRbac(options.Scanners) && !options.ImageConfigScanners.Enabled(types.MisconfigScanner) { diff --git a/pkg/scanner/ospkg/scan.go b/pkg/scanner/ospkg/scan.go index 8edfc1b1d786..fee369e369e4 100644 --- a/pkg/scanner/ospkg/scan.go +++ b/pkg/scanner/ospkg/scan.go @@ -2,7 +2,6 @@ package ospkg import ( "context" - "errors" "fmt" "sort" "time" @@ -15,7 +14,6 @@ import ( ) type Scanner interface { - Packages(target types.ScanTarget, options types.ScanOptions) types.Result Scan(ctx context.Context, target types.ScanTarget, options types.ScanOptions) (types.Result, bool, error) } @@ -25,21 +23,7 @@ func NewScanner() Scanner { return &scanner{} } -func (s *scanner) Packages(target types.ScanTarget, _ types.ScanOptions) types.Result { - if len(target.Packages) == 0 || !target.OS.Detected() { - return types.Result{} - } - - sort.Sort(target.Packages) - return types.Result{ - Target: fmt.Sprintf("%s (%s %s)", target.Name, target.OS.Family, target.OS.Name), - Class: types.ClassOSPkg, - Type: target.OS.Family, - Packages: target.Packages, - } -} - -func (s *scanner) Scan(ctx context.Context, target types.ScanTarget, _ types.ScanOptions) (types.Result, bool, error) { +func (s *scanner) Scan(ctx context.Context, target types.ScanTarget, opts types.ScanOptions) (types.Result, bool, error) { if !target.OS.Detected() { log.Debug("Detected OS: unknown") return types.Result{}, false, nil @@ -52,19 +36,29 @@ func (s *scanner) Scan(ctx context.Context, target types.ScanTarget, _ types.Sca target.OS.Name += "-ESM" } + result := types.Result{ + Target: fmt.Sprintf("%s (%s %s)", target.Name, target.OS.Family, target.OS.Name), + Class: types.ClassOSPkg, + Type: target.OS.Family, + } + + if opts.ListAllPackages { + sort.Sort(target.Packages) + result.Packages = target.Packages + } + + if !opts.Scanners.Enabled(types.VulnerabilityScanner) { + // Return packages only + return result, false, nil + } + vulns, eosl, err := ospkgDetector.Detect(ctx, "", target.OS.Family, target.OS.Name, target.Repository, time.Time{}, target.Packages) - if errors.Is(err, ospkgDetector.ErrUnsupportedOS) { - return types.Result{}, false, nil - } else if err != nil { - return types.Result{}, false, xerrors.Errorf("failed vulnerability detection of OS packages: %w", err) + if err != nil { + // Return a result for those who want to override the error handling. + return result, false, xerrors.Errorf("failed vulnerability detection of OS packages: %w", err) } + result.Vulnerabilities = vulns - artifactDetail := fmt.Sprintf("%s (%s %s)", target.Name, target.OS.Family, target.OS.Name) - return types.Result{ - Target: artifactDetail, - Vulnerabilities: vulns, - Class: types.ClassOSPkg, - Type: target.OS.Family, - }, eosl, nil + return result, eosl, nil } From c96f2a5b3de820da37e14594dd537c3b0949ae9c Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 17 May 2024 13:43:56 +0600 Subject: [PATCH 072/352] fix(go): add only non-empty root modules for `gobinaries` (#6710) --- pkg/dependency/parser/golang/binary/parse.go | 23 +++++++++++-------- .../parser/golang/binary/parse_test.go | 5 ---- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/pkg/dependency/parser/golang/binary/parse.go b/pkg/dependency/parser/golang/binary/parse.go index a50397db11b2..4c53858c69e9 100644 --- a/pkg/dependency/parser/golang/binary/parse.go +++ b/pkg/dependency/parser/golang/binary/parse.go @@ -58,8 +58,17 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc ldflags := p.ldFlags(info.Settings) pkgs := make(ftypes.Packages, 0, len(info.Deps)+2) - pkgs = append(pkgs, []ftypes.Package{ - { + pkgs = append(pkgs, ftypes.Package{ + // Add the Go version used to build this binary. + Name: "stdlib", + Version: stdlibVersion, + Relationship: ftypes.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages. + }) + + // There are times when gobinaries don't contain Main information. + // e.g. `Go` binaries (e.g. `go`, `gofmt`, etc.) + if info.Main.Path != "" { + pkgs = append(pkgs, ftypes.Package{ // Add main module Name: info.Main.Path, // Only binaries installed with `go install` contain semver version of the main module. @@ -69,14 +78,8 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc // See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477. Version: cmp.Or(p.checkVersion(info.Main.Path, info.Main.Version), p.ParseLDFlags(info.Main.Path, ldflags)), Relationship: ftypes.RelationshipRoot, - }, - { - // Add the Go version used to build this binary. - Name: "stdlib", - Version: stdlibVersion, - Relationship: ftypes.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages. - }, - }...) + }) + } for _, dep := range info.Deps { // binaries with old go version may incorrectly add module in Deps diff --git a/pkg/dependency/parser/golang/binary/parse_test.go b/pkg/dependency/parser/golang/binary/parse_test.go index 8b84c8dbbaf4..13cc1e682f9c 100644 --- a/pkg/dependency/parser/golang/binary/parse_test.go +++ b/pkg/dependency/parser/golang/binary/parse_test.go @@ -118,11 +118,6 @@ func TestParse(t *testing.T) { name: "goexperiment", inputFile: "testdata/goexperiment", want: []ftypes.Package{ - { - Name: "", - Version: "", - Relationship: ftypes.RelationshipRoot, - }, { Name: "stdlib", Version: "1.22.1", From afb4f9dc4730671ba004e1734fa66422c4c86dad Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 17 May 2024 13:55:24 +0600 Subject: [PATCH 073/352] fix(go): include only `.version`|`.ver` (no prefixes) ldflags for `gobinaries` (#6705) --- pkg/dependency/parser/golang/binary/parse.go | 2 +- pkg/dependency/parser/golang/binary/parse_test.go | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/dependency/parser/golang/binary/parse.go b/pkg/dependency/parser/golang/binary/parse.go index 4c53858c69e9..0354668c33cc 100644 --- a/pkg/dependency/parser/golang/binary/parse.go +++ b/pkg/dependency/parser/golang/binary/parse.go @@ -166,7 +166,7 @@ func (p *Parser) ParseLDFlags(name string, flags []string) string { func isValidXKey(key string) bool { key = strings.ToLower(key) // The check for a 'ver' prefix enables the parser to pick up Trivy's own version value that's set. - return strings.HasSuffix(key, "version") || strings.HasSuffix(key, "ver") + return strings.HasSuffix(key, ".version") || strings.HasSuffix(key, ".ver") } func isValidSemVer(ver string) bool { diff --git a/pkg/dependency/parser/golang/binary/parse_test.go b/pkg/dependency/parser/golang/binary/parse_test.go index 13cc1e682f9c..95e620d412bd 100644 --- a/pkg/dependency/parser/golang/binary/parse_test.go +++ b/pkg/dependency/parser/golang/binary/parse_test.go @@ -238,6 +238,18 @@ func TestParser_ParseLDFlags(t *testing.T) { }, want: "0.50.1", }, + { + name: "with version with extra prefix", + args: args{ + name: "github.com/argoproj/argo-cd/v2", + flags: []string{ + "-s", + "-w", + "-X='github.com/argoproj/argo-cd/v2/common.kubectlVersion=v0.26.11'", + }, + }, + want: "", + }, { name: "with no flags", args: args{ From 903bd69abd2e8a1a2b7010b36146508a4622b159 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 17 May 2024 10:05:47 +0200 Subject: [PATCH 074/352] ci(deps): update golangci-lint-action and enable testifylint linter on "integration/*" (#6706) Signed-off-by: Matthieu MOREL --- .github/workflows/test.yaml | 5 +- .golangci.yaml | 5 +- .../analyzer/buildinfo/dockerfile_test.go | 2 +- .../language/nodejs/license/license_test.go | 2 +- pkg/flag/kubernetes_flags_test.go | 2 +- pkg/iac/adapters/arm/storage/adapt_test.go | 4 +- .../adapters/terraform/aws/s3/adapt_test.go | 2 +- .../adapters/terraform/aws/s3/bucket_test.go | 34 ++++++------- pkg/iac/rego/scanner_test.go | 44 ++++++++-------- pkg/iac/rules/register_test.go | 10 ++-- .../scanners/dockerfile/parser/parser_test.go | 4 +- pkg/iac/scanners/helm/test/scanner_test.go | 6 +-- pkg/iac/scanners/kubernetes/scanner_test.go | 4 +- pkg/iac/scanners/terraform/block_test.go | 12 ++--- pkg/iac/scanners/terraform/count_test.go | 2 +- .../terraform/parser/load_vars_test.go | 4 +- .../scanners/terraform/parser/parser_test.go | 50 +++++++++---------- pkg/k8s/report/summary_test.go | 2 +- pkg/licensing/expression/parser_test.go | 2 +- pkg/misconf/scanner_test.go | 2 +- pkg/module/module_test.go | 2 +- pkg/scanner/post/post_scan_test.go | 4 +- pkg/utils/fsutils/fs_test.go | 2 +- 23 files changed, 101 insertions(+), 105 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2717221220a2..3ce0e8991436 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -43,11 +43,10 @@ jobs: - name: Lint id: lint - uses: golangci/golangci-lint-action@v4.0.0 + uses: golangci/golangci-lint-action@v6.0.1 with: version: v1.57 - args: --timeout=30m --out-format=line-number - skip-cache: true # https://github.com/golangci/golangci-lint-action/issues/244#issuecomment-1052197778 + args: --verbose --out-format=line-number if: matrix.operating-system == 'ubuntu-latest' - name: Check if linter failed diff --git a/.golangci.yaml b/.golangci.yaml index 92c4d6d3d164..7895c84580aa 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -69,10 +69,7 @@ linters-settings: testifylint: enable-all: true disable: - - bool-compare - - expected-actual - float-compare - - len - require-error linters: @@ -98,11 +95,11 @@ linters: run: go: '1.22' + timeout: 30m issues: exclude-files: - ".*_mock.go$" - - "integration/*" - "examples/*" exclude-dirs: - "pkg/iac/scanners/terraform/parser/funcs" # copies of Terraform functions diff --git a/pkg/fanal/analyzer/buildinfo/dockerfile_test.go b/pkg/fanal/analyzer/buildinfo/dockerfile_test.go index 2cecfdaa5388..600425d6c930 100644 --- a/pkg/fanal/analyzer/buildinfo/dockerfile_test.go +++ b/pkg/fanal/analyzer/buildinfo/dockerfile_test.go @@ -59,7 +59,7 @@ func Test_dockerfileAnalyzer_Analyze(t *testing.T) { if tt.wantErr != "" { require.Error(t, err) - assert.Equal(t, err.Error(), tt.wantErr) + assert.Equal(t, tt.wantErr, err.Error()) return } assert.NoError(t, err) diff --git a/pkg/fanal/analyzer/language/nodejs/license/license_test.go b/pkg/fanal/analyzer/language/nodejs/license/license_test.go index 745d24cb4044..b4febec85d94 100644 --- a/pkg/fanal/analyzer/language/nodejs/license/license_test.go +++ b/pkg/fanal/analyzer/language/nodejs/license/license_test.go @@ -91,7 +91,7 @@ func Test_IsLicenseRefToFile(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ok, licenseFileName := license.IsLicenseRefToFile(tt.input) - assert.Equal(t, ok, tt.wantOk) + assert.Equal(t, tt.wantOk, ok) assert.Equal(t, tt.wantFileName, licenseFileName) }) } diff --git a/pkg/flag/kubernetes_flags_test.go b/pkg/flag/kubernetes_flags_test.go index 50c0b4f793bf..c5e45850609d 100644 --- a/pkg/flag/kubernetes_flags_test.go +++ b/pkg/flag/kubernetes_flags_test.go @@ -45,7 +45,7 @@ func TestOptionToToleration(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got, err := optionToTolerations(tt.tolerationsOptions) assert.NoError(t, err) - assert.Equal(t, got, tt.want) + assert.Equal(t, tt.want, got) }) } } diff --git a/pkg/iac/adapters/arm/storage/adapt_test.go b/pkg/iac/adapters/arm/storage/adapt_test.go index 31d95e814d6e..9d8d2f0f1cbb 100644 --- a/pkg/iac/adapters/arm/storage/adapt_test.go +++ b/pkg/iac/adapters/arm/storage/adapt_test.go @@ -28,7 +28,7 @@ func Test_AdaptStorageDefaults(t *testing.T) { account := output.Accounts[0] assert.Equal(t, "TLS1_0", account.MinimumTLSVersion.Value()) - assert.Equal(t, false, account.EnforceHTTPS.Value()) + assert.False(t, account.EnforceHTTPS.Value()) } @@ -53,6 +53,6 @@ func Test_AdaptStorage(t *testing.T) { account := output.Accounts[0] assert.Equal(t, "TLS1_2", account.MinimumTLSVersion.Value()) - assert.Equal(t, true, account.EnforceHTTPS.Value()) + assert.True(t, account.EnforceHTTPS.Value()) } diff --git a/pkg/iac/adapters/terraform/aws/s3/adapt_test.go b/pkg/iac/adapters/terraform/aws/s3/adapt_test.go index 65394abd3ea7..b17a00391b34 100644 --- a/pkg/iac/adapters/terraform/aws/s3/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/s3/adapt_test.go @@ -56,7 +56,7 @@ resource "aws_s3_bucket_public_access_block" "example_access_block"{ modules := tftestutil.CreateModulesFromSource(t, tC.source, ".tf") s3Ctx := Adapt(modules) - assert.Equal(t, tC.expectedBuckets, len(s3Ctx.Buckets)) + assert.Len(t, s3Ctx.Buckets, tC.expectedBuckets) for _, bucket := range s3Ctx.Buckets { if tC.hasPublicAccess { diff --git a/pkg/iac/adapters/terraform/aws/s3/bucket_test.go b/pkg/iac/adapters/terraform/aws/s3/bucket_test.go index 069d0b39c86d..84c0ddc60ecd 100644 --- a/pkg/iac/adapters/terraform/aws/s3/bucket_test.go +++ b/pkg/iac/adapters/terraform/aws/s3/bucket_test.go @@ -21,7 +21,7 @@ resource "aws_s3_bucket" "bucket1" { s3 := Adapt(modules) - assert.Equal(t, 1, len(s3.Buckets)) + assert.Len(t, s3.Buckets, 1) } @@ -38,7 +38,7 @@ resource "aws_s3_bucket" "example" { s3 := Adapt(modules) - assert.Equal(t, 1, len(s3.Buckets)) + assert.Len(t, s3.Buckets, 1) assert.Equal(t, "authenticated-read", s3.Buckets[0].ACL.Value()) } @@ -58,7 +58,7 @@ resource "aws_s3_bucket_acl" "example" { s3 := Adapt(modules) - assert.Equal(t, 1, len(s3.Buckets)) + assert.Len(t, s3.Buckets, 1) assert.Equal(t, "authenticated-read", s3.Buckets[0].ACL.Value()) } @@ -80,7 +80,7 @@ resource "aws_s3_bucket" "example" { s3 := Adapt(modules) - assert.Equal(t, 1, len(s3.Buckets)) + assert.Len(t, s3.Buckets, 1) assert.True(t, s3.Buckets[0].Logging.Enabled.Value()) } @@ -110,7 +110,7 @@ resource "aws_s3_bucket_logging" "example" { s3 := Adapt(modules) - assert.Equal(t, 2, len(s3.Buckets)) + assert.Len(t, s3.Buckets, 2) for _, bucket := range s3.Buckets { switch bucket.Name.Value() { case "yournamehere": @@ -135,7 +135,7 @@ resource "aws_s3_bucket" "example" { s3 := Adapt(modules) - assert.Equal(t, 1, len(s3.Buckets)) + assert.Len(t, s3.Buckets, 1) assert.True(t, s3.Buckets[0].Versioning.Enabled.Value()) } @@ -157,7 +157,7 @@ resource "aws_s3_bucket_versioning" "example" { s3 := Adapt(modules) - assert.Equal(t, 1, len(s3.Buckets)) + assert.Len(t, s3.Buckets, 1) assert.True(t, s3.Buckets[0].Versioning.Enabled.Value()) } @@ -174,7 +174,7 @@ resource "aws_s3_bucket" "example" { s3 := Adapt(modules) - assert.Equal(t, 1, len(s3.Buckets)) + assert.Len(t, s3.Buckets, 1) assert.True(t, s3.Buckets[0].Versioning.Enabled.Value()) } @@ -194,7 +194,7 @@ resource "aws_s3_bucket_object_lock_configuration" "example" { s3 := Adapt(modules) - assert.Equal(t, 1, len(s3.Buckets)) + assert.Len(t, s3.Buckets, 1) assert.True(t, s3.Buckets[0].Versioning.Enabled.Value()) } @@ -220,7 +220,7 @@ resource "aws_s3_bucket_versioning" "example" { s3 := Adapt(modules) - assert.Equal(t, 1, len(s3.Buckets)) + assert.Len(t, s3.Buckets, 1) assert.True(t, s3.Buckets[0].Versioning.Enabled.Value()) } @@ -245,7 +245,7 @@ func Test_BucketGetEncryption(t *testing.T) { s3 := Adapt(modules) - assert.Equal(t, 1, len(s3.Buckets)) + assert.Len(t, s3.Buckets, 1) assert.True(t, s3.Buckets[0].Encryption.Enabled.Value()) } @@ -273,7 +273,7 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "example" { s3 := Adapt(modules) - assert.Equal(t, 1, len(s3.Buckets)) + assert.Len(t, s3.Buckets, 1) assert.True(t, s3.Buckets[0].Encryption.Enabled.Value()) } @@ -312,19 +312,19 @@ data "aws_iam_policy_document" "allow_access_from_another_account" { s3 := Adapt(modules) - require.Equal(t, 1, len(s3.Buckets)) - require.Equal(t, 1, len(s3.Buckets[0].BucketPolicies)) + require.Len(t, s3.Buckets, 1) + require.Len(t, s3.Buckets[0].BucketPolicies, 1) policy := s3.Buckets[0].BucketPolicies[0] statements, _ := policy.Document.Parsed.Statements() - require.Equal(t, 1, len(statements)) + require.Len(t, statements, 1) principals, _ := statements[0].Principals() actions, _ := statements[0].Actions() awsPrincipals, _ := principals.AWS() - require.Equal(t, 1, len(awsPrincipals)) - require.Equal(t, 2, len(actions)) + require.Len(t, awsPrincipals, 1) + require.Len(t, actions, 2) } diff --git a/pkg/iac/rego/scanner_test.go b/pkg/iac/rego/scanner_test.go index 83f3a2b1f959..ef0878822acb 100644 --- a/pkg/iac/rego/scanner_test.go +++ b/pkg/iac/rego/scanner_test.go @@ -58,7 +58,7 @@ deny { }) require.NoError(t, err) - require.Equal(t, 1, len(results.GetFailed())) + require.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) @@ -93,7 +93,7 @@ deny { }) require.NoError(t, err) - require.Equal(t, 1, len(results.GetFailed())) + require.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) @@ -127,7 +127,7 @@ warn { }) require.NoError(t, err) - require.Equal(t, 1, len(results.GetFailed())) + require.Len(t, results.GetFailed(), 1) require.Empty(t, results.GetPassed()) require.Empty(t, results.GetIgnored()) @@ -160,7 +160,7 @@ deny { require.NoError(t, err) assert.Empty(t, results.GetFailed()) - require.Equal(t, 1, len(results.GetPassed())) + require.Len(t, results.GetPassed(), 1) assert.Empty(t, results.GetIgnored()) assert.Equal(t, "/evil.lol", results.GetPassed()[0].Metadata().Range().GetFilename()) @@ -204,7 +204,7 @@ exception[ns] { assert.Empty(t, results.GetFailed()) assert.Empty(t, results.GetPassed()) - assert.Equal(t, 1, len(results.GetIgnored())) + assert.Len(t, results.GetIgnored(), 1) } @@ -250,9 +250,9 @@ exception[ns] { }) require.NoError(t, err) - assert.Equal(t, 1, len(results.GetFailed())) + assert.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) - assert.Equal(t, 1, len(results.GetIgnored())) + assert.Len(t, results.GetIgnored(), 1) } @@ -289,7 +289,7 @@ exception[rules] { assert.Empty(t, results.GetFailed()) assert.Empty(t, results.GetPassed()) - assert.Equal(t, 1, len(results.GetIgnored())) + assert.Len(t, results.GetIgnored(), 1) } func Test_RegoScanning_Rule_Exception_WithoutMatch(t *testing.T) { @@ -323,7 +323,7 @@ exception[rules] { }) require.NoError(t, err) - assert.Equal(t, 1, len(results.GetFailed())) + assert.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) } @@ -357,7 +357,7 @@ deny_evil { }) require.NoError(t, err) - assert.Equal(t, 1, len(results.GetFailed())) + assert.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) } @@ -388,7 +388,7 @@ deny[msg] { }) require.NoError(t, err) - require.Equal(t, 1, len(results.GetFailed())) + require.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) @@ -426,7 +426,7 @@ deny[res] { }) require.NoError(t, err) - require.Equal(t, 1, len(results.GetFailed())) + require.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) @@ -468,7 +468,7 @@ deny[res] { }) require.NoError(t, err) - require.Equal(t, 1, len(results.GetFailed())) + require.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) @@ -522,7 +522,7 @@ deny[res] { }) require.NoError(t, err) - require.Equal(t, 1, len(results.GetFailed())) + require.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) @@ -571,7 +571,7 @@ deny { }) require.NoError(t, err) - assert.Equal(t, 1, len(results.GetFailed())) + assert.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) } @@ -636,7 +636,7 @@ deny { }) require.NoError(t, err) - assert.Equal(t, 1, len(results.GetFailed())) + assert.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) @@ -671,7 +671,7 @@ deny { }) require.NoError(t, err) - assert.Equal(t, 1, len(results.GetFailed())) + assert.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) @@ -705,7 +705,7 @@ deny { }) require.NoError(t, err) - assert.Equal(t, 1, len(results.GetFailed())) + assert.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) @@ -742,7 +742,7 @@ deny { }, }) require.NoError(t, err) - assert.Equal(t, results[0].Rule().Summary, "i am dynamic") + assert.Equal(t, "i am dynamic", results[0].Rule().Summary) } func Test_staticMetadata(t *testing.T) { @@ -775,7 +775,7 @@ deny { }, }) require.NoError(t, err) - assert.Equal(t, results[0].Rule().Summary, "i am static") + assert.Equal(t, "i am static", results[0].Rule().Summary) } func Test_annotationMetadata(t *testing.T) { @@ -933,7 +933,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{}) require.NoError(t, err) - assert.Equal(t, 1, len(results.GetFailed())) + assert.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) } @@ -973,7 +973,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{}) require.NoError(t, err) - assert.Equal(t, 1, len(results.GetFailed())) + assert.Len(t, results.GetFailed(), 1) assert.Empty(t, results.GetPassed()) assert.Empty(t, results.GetIgnored()) } diff --git a/pkg/iac/rules/register_test.go b/pkg/iac/rules/register_test.go index e44acd3d94d2..5f2ec0874535 100644 --- a/pkg/iac/rules/register_test.go +++ b/pkg/iac/rules/register_test.go @@ -14,7 +14,7 @@ func Test_Reset(t *testing.T) { Reset() rule := scan.Rule{} _ = Register(rule) - assert.Equal(t, 1, len(GetFrameworkRules())) + assert.Len(t, GetFrameworkRules(), 1) Reset() assert.Empty(t, GetFrameworkRules()) } @@ -106,10 +106,10 @@ func Test_Deregistration(t *testing.T) { registrationB := Register(scan.Rule{ AVDID: "B", }) - assert.Equal(t, 2, len(GetFrameworkRules())) + assert.Len(t, GetFrameworkRules(), 2) Deregister(registrationA) actual := GetFrameworkRules() - require.Equal(t, 1, len(actual)) + require.Len(t, actual, 1) assert.Equal(t, "B", actual[0].GetRule().AVDID) Deregister(registrationB) assert.Empty(t, GetFrameworkRules()) @@ -129,10 +129,10 @@ func Test_DeregistrationMultipleFrameworks(t *testing.T) { framework.Default: nil, }, }) - assert.Equal(t, 2, len(GetFrameworkRules())) + assert.Len(t, GetFrameworkRules(), 2) Deregister(registrationA) actual := GetFrameworkRules() - require.Equal(t, 1, len(actual)) + require.Len(t, actual, 1) assert.Equal(t, "B", actual[0].GetRule().AVDID) Deregister(registrationB) assert.Empty(t, GetFrameworkRules()) diff --git a/pkg/iac/scanners/dockerfile/parser/parser_test.go b/pkg/iac/scanners/dockerfile/parser/parser_test.go index 04a45ea4695d..764cb3a0e091 100644 --- a/pkg/iac/scanners/dockerfile/parser/parser_test.go +++ b/pkg/iac/scanners/dockerfile/parser/parser_test.go @@ -18,13 +18,13 @@ CMD python /app/app.py df, err := New().parse("Dockerfile", strings.NewReader(input)) require.NoError(t, err) - assert.Equal(t, 1, len(df.Stages)) + assert.Len(t, df.Stages, 1) require.Len(t, df.Stages, 1) assert.Equal(t, "ubuntu:18.04", df.Stages[0].Name) commands := df.Stages[0].Commands - assert.Equal(t, 4, len(commands)) + assert.Len(t, commands, 4) // FROM ubuntu:18.04 assert.Equal(t, "from", commands[0].Cmd) diff --git a/pkg/iac/scanners/helm/test/scanner_test.go b/pkg/iac/scanners/helm/test/scanner_test.go index fdcb0d3af813..a7228601b24d 100644 --- a/pkg/iac/scanners/helm/test/scanner_test.go +++ b/pkg/iac/scanners/helm/test/scanner_test.go @@ -51,7 +51,7 @@ func Test_helm_scanner_with_archive(t *testing.T) { require.NotNil(t, results) failed := results.GetFailed() - assert.Equal(t, 13, len(failed)) + assert.Len(t, failed, 13) visited := make(map[string]bool) var errorCodes []string @@ -135,7 +135,7 @@ func Test_helm_scanner_with_dir(t *testing.T) { require.NotNil(t, results) failed := results.GetFailed() - assert.Equal(t, 14, len(failed)) + assert.Len(t, failed, 14) visited := make(map[string]bool) var errorCodes []string @@ -227,7 +227,7 @@ deny[res] { require.NotNil(t, results) failed := results.GetFailed() - assert.Equal(t, 15, len(failed)) + assert.Len(t, failed, 15) visited := make(map[string]bool) var errorCodes []string diff --git a/pkg/iac/scanners/kubernetes/scanner_test.go b/pkg/iac/scanners/kubernetes/scanner_test.go index bf8ea32461eb..a5f3aaf90938 100644 --- a/pkg/iac/scanners/kubernetes/scanner_test.go +++ b/pkg/iac/scanners/kubernetes/scanner_test.go @@ -424,7 +424,7 @@ spec: `)) require.NoError(t, err) - assert.Equal(t, 1, len(results.GetFailed())) + assert.Len(t, results.GetFailed(), 1) } func Test_FileScanJSON(t *testing.T) { @@ -479,7 +479,7 @@ deny[msg] { `)) require.NoError(t, err) - assert.Equal(t, 1, len(results.GetFailed())) + assert.Len(t, results.GetFailed(), 1) } func Test_FileScanWithMetadata(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/block_test.go b/pkg/iac/scanners/terraform/block_test.go index e65f224bafae..8b86d023bf89 100644 --- a/pkg/iac/scanners/terraform/block_test.go +++ b/pkg/iac/scanners/terraform/block_test.go @@ -49,8 +49,8 @@ resource "aws_s3_bucket" "my-bucket" { modules := createModulesFromSource(t, test.source, ".tf") for _, module := range modules { for _, block := range module.GetBlocks() { - assert.Equal(t, block.HasChild(test.expectedAttribute), true) - assert.Equal(t, !block.HasChild(test.expectedAttribute), false) + assert.True(t, block.HasChild(test.expectedAttribute)) + assert.True(t, block.HasChild(test.expectedAttribute)) } } }) @@ -89,8 +89,8 @@ resource "aws_s3_bucket" "my-bucket" { modules := createModulesFromSource(t, test.source, ".tf") for _, module := range modules { for _, block := range module.GetBlocks() { - assert.Equal(t, block.HasChild(test.expectedAttribute), false) - assert.Equal(t, !block.HasChild(test.expectedAttribute), true) + assert.False(t, block.HasChild(test.expectedAttribute)) + assert.False(t, block.HasChild(test.expectedAttribute)) } } }) @@ -129,8 +129,8 @@ resource "aws_s3_bucket" "my-bucket" { modules := createModulesFromSource(t, test.source, ".tf") for _, module := range modules { for _, block := range module.GetBlocks() { - assert.Equal(t, block.MissingChild(test.expectedAttribute), true) - assert.Equal(t, !block.HasChild(test.expectedAttribute), true) + assert.True(t, block.MissingChild(test.expectedAttribute)) + assert.False(t, block.HasChild(test.expectedAttribute)) } } }) diff --git a/pkg/iac/scanners/terraform/count_test.go b/pkg/iac/scanners/terraform/count_test.go index 2ce46c4388cc..99e447196fa1 100644 --- a/pkg/iac/scanners/terraform/count_test.go +++ b/pkg/iac/scanners/terraform/count_test.go @@ -181,7 +181,7 @@ variable "things" { } else { exclude = r1.LongID() } - assert.Equal(t, test.expectedResults, len(results.GetFailed())) + assert.Len(t, results.GetFailed(), test.expectedResults) if include != "" { testutil.AssertRuleFound(t, include, results, "false negative found") } diff --git a/pkg/iac/scanners/terraform/parser/load_vars_test.go b/pkg/iac/scanners/terraform/parser/load_vars_test.go index 866e895f353e..a207a5383125 100644 --- a/pkg/iac/scanners/terraform/parser/load_vars_test.go +++ b/pkg/iac/scanners/terraform/parser/load_vars_test.go @@ -39,7 +39,7 @@ func Test_TFVarsFile(t *testing.T) { require.NoError(t, err) assert.Equal(t, "bar", vars["variable"].GetAttr("foo").GetAttr("default").AsString()) assert.Equal(t, "qux", vars["variable"].GetAttr("baz").AsString()) - assert.Equal(t, true, vars["foo2"].True()) - assert.Equal(t, true, vars["foo3"].Equals(cty.NumberIntVal(3)).True()) + assert.True(t, vars["foo2"].True()) + assert.True(t, vars["foo3"].Equals(cty.NumberIntVal(3)).True()) }) } diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index 48181eee63e7..4be1b463d7ce 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -291,7 +291,7 @@ resource "something" "blah" { attr := block.GetAttribute("value") require.NotNil(t, attr) - assert.Equal(t, false, attr.IsResolvable()) + assert.False(t, attr.IsResolvable()) } func Test_UndefinedModuleOutputReferenceInSlice(t *testing.T) { @@ -318,18 +318,18 @@ resource "something" "blah" { attr := block.GetAttribute("value") require.NotNil(t, attr) - assert.Equal(t, true, attr.IsResolvable()) + assert.True(t, attr.IsResolvable()) values := attr.AsStringValueSliceOrEmpty() require.Len(t, values, 3) assert.Equal(t, "first", values[0].Value()) - assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) + assert.True(t, values[0].GetMetadata().IsResolvable()) - assert.Equal(t, false, values[1].GetMetadata().IsResolvable()) + assert.False(t, values[1].GetMetadata().IsResolvable()) assert.Equal(t, "last", values[2].Value()) - assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) + assert.True(t, values[2].GetMetadata().IsResolvable()) } func Test_TemplatedSliceValue(t *testing.T) { @@ -361,19 +361,19 @@ resource "something" "blah" { attr := block.GetAttribute("value") require.NotNil(t, attr) - assert.Equal(t, true, attr.IsResolvable()) + assert.True(t, attr.IsResolvable()) values := attr.AsStringValueSliceOrEmpty() require.Len(t, values, 3) assert.Equal(t, "first", values[0].Value()) - assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) + assert.True(t, values[0].GetMetadata().IsResolvable()) assert.Equal(t, "hello-hello", values[1].Value()) - assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) + assert.True(t, values[1].GetMetadata().IsResolvable()) assert.Equal(t, "last", values[2].Value()) - assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) + assert.True(t, values[2].GetMetadata().IsResolvable()) } func Test_SliceOfVars(t *testing.T) { @@ -409,16 +409,16 @@ resource "something" "blah" { attr := block.GetAttribute("value") require.NotNil(t, attr) - assert.Equal(t, true, attr.IsResolvable()) + assert.True(t, attr.IsResolvable()) values := attr.AsStringValueSliceOrEmpty() require.Len(t, values, 2) assert.Equal(t, "1", values[0].Value()) - assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) + assert.True(t, values[0].GetMetadata().IsResolvable()) assert.Equal(t, "2", values[1].Value()) - assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) + assert.True(t, values[1].GetMetadata().IsResolvable()) } func Test_VarSlice(t *testing.T) { @@ -450,19 +450,19 @@ resource "something" "blah" { attr := block.GetAttribute("value") require.NotNil(t, attr) - assert.Equal(t, true, attr.IsResolvable()) + assert.True(t, attr.IsResolvable()) values := attr.AsStringValueSliceOrEmpty() require.Len(t, values, 3) assert.Equal(t, "a", values[0].Value()) - assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) + assert.True(t, values[0].GetMetadata().IsResolvable()) assert.Equal(t, "b", values[1].Value()) - assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) + assert.True(t, values[1].GetMetadata().IsResolvable()) assert.Equal(t, "c", values[2].Value()) - assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) + assert.True(t, values[2].GetMetadata().IsResolvable()) } func Test_LocalSliceNested(t *testing.T) { @@ -498,19 +498,19 @@ resource "something" "blah" { attr := block.GetAttribute("value") require.NotNil(t, attr) - assert.Equal(t, true, attr.IsResolvable()) + assert.True(t, attr.IsResolvable()) values := attr.AsStringValueSliceOrEmpty() require.Len(t, values, 3) assert.Equal(t, "a", values[0].Value()) - assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) + assert.True(t, values[0].GetMetadata().IsResolvable()) assert.Equal(t, "b", values[1].Value()) - assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) + assert.True(t, values[1].GetMetadata().IsResolvable()) assert.Equal(t, "c", values[2].Value()) - assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) + assert.True(t, values[2].GetMetadata().IsResolvable()) } func Test_FunctionCall(t *testing.T) { @@ -543,19 +543,19 @@ resource "something" "blah" { attr := block.GetAttribute("value") require.NotNil(t, attr) - assert.Equal(t, true, attr.IsResolvable()) + assert.True(t, attr.IsResolvable()) values := attr.AsStringValueSliceOrEmpty() require.Len(t, values, 3) assert.Equal(t, "a", values[0].Value()) - assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) + assert.True(t, values[0].GetMetadata().IsResolvable()) assert.Equal(t, "b", values[1].Value()) - assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) + assert.True(t, values[1].GetMetadata().IsResolvable()) assert.Equal(t, "c", values[2].Value()) - assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) + assert.True(t, values[2].GetMetadata().IsResolvable()) } func Test_NullDefaultValueForVar(t *testing.T) { @@ -802,7 +802,7 @@ policy_rules = { block := blocks[0] assert.Equal(t, "secure-tag-1", block.GetAttribute("name").AsStringValueOrDefault("", block).Value()) - assert.Equal(t, true, block.GetAttribute("enabled").AsBoolValueOrDefault(false, block).Value()) + assert.True(t, block.GetAttribute("enabled").AsBoolValueOrDefault(false, block).Value()) assert.Equal(t, "host() != 'google.com'", block.GetAttribute("session_matcher").AsStringValueOrDefault("", block).Value()) assert.Equal(t, 1001, block.GetAttribute("priority").AsIntValueOrDefault(0, block).Value()) } diff --git a/pkg/k8s/report/summary_test.go b/pkg/k8s/report/summary_test.go index 7a38f3b01993..dd1b6985e159 100644 --- a/pkg/k8s/report/summary_test.go +++ b/pkg/k8s/report/summary_test.go @@ -92,7 +92,7 @@ func TestReport_ColumnHeading(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { column := ColumnHeading(tt.scanners, tt.availableColumns) - if !assert.Equal(t, column, tt.want) { + if !assert.Equal(t, tt.want, column) { t.Error(fmt.Errorf("TestReport_ColumnHeading want %v got %v", tt.want, column)) } }) diff --git a/pkg/licensing/expression/parser_test.go b/pkg/licensing/expression/parser_test.go index a828df902008..522777085b5b 100644 --- a/pkg/licensing/expression/parser_test.go +++ b/pkg/licensing/expression/parser_test.go @@ -144,7 +144,7 @@ func TestParse(t *testing.T) { ret := yyParse(l) err := l.Err() if tt.wantErr != "" { - assert.Equal(t, ret, 1) + assert.Equal(t, 1, ret) assert.ErrorContains(t, err, tt.wantErr) return } diff --git a/pkg/misconf/scanner_test.go b/pkg/misconf/scanner_test.go index 03c76ebba2c7..a3b412765706 100644 --- a/pkg/misconf/scanner_test.go +++ b/pkg/misconf/scanner_test.go @@ -159,7 +159,7 @@ func TestScanner_Scan(t *testing.T) { misconfs, err := s.Scan(context.Background(), fsys) require.NoError(t, err) - require.Equal(t, tt.misconfsExpected, len(misconfs), "wrong number of misconfigurations found") + require.Len(t, misconfs, tt.misconfsExpected, "wrong number of misconfigurations found") if tt.misconfsExpected == 1 { assert.Equal(t, tt.wantFilePath, misconfs[0].FilePath, "filePaths don't equal") assert.Equal(t, tt.wantFileType, misconfs[0].FileType, "fileTypes don't equal") diff --git a/pkg/module/module_test.go b/pkg/module/module_test.go index 581b7ca3ed55..d12ffebaf0c9 100644 --- a/pkg/module/module_test.go +++ b/pkg/module/module_test.go @@ -102,7 +102,7 @@ func TestManager_Register(t *testing.T) { }) require.NoError(t, err) // WASM modules must be generated before running the tests. - require.Equal(t, count, 3, "missing WASM modules, try 'mage test:unit' or 'mage test:generateModules'") + require.Equal(t, 3, count, "missing WASM modules, try 'mage test:unit' or 'mage test:generateModules'") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/scanner/post/post_scan_test.go b/pkg/scanner/post/post_scan_test.go index 981f74bdfc14..81319e4c7ac1 100644 --- a/pkg/scanner/post/post_scan_test.go +++ b/pkg/scanner/post/post_scan_test.go @@ -96,8 +96,8 @@ func TestScan(t *testing.T) { }() results, err := post.Scan(context.Background(), tt.results) - require.Equal(t, err != nil, tt.wantErr) - assert.Equal(t, results, tt.want) + require.Equal(t, tt.wantErr, err != nil) + assert.Equal(t, tt.want, results) }) } } diff --git a/pkg/utils/fsutils/fs_test.go b/pkg/utils/fsutils/fs_test.go index 14d39c2fa5d4..44dc6b3d42c2 100644 --- a/pkg/utils/fsutils/fs_test.go +++ b/pkg/utils/fsutils/fs_test.go @@ -65,7 +65,7 @@ func TestCopyFile(t *testing.T) { _, err := CopyFile(src, dst) if tt.wantErr != "" { require.Error(t, err, tt.name) - assert.Equal(t, err.Error(), tt.wantErr, tt.name) + assert.Equal(t, tt.wantErr, err.Error(), tt.name) } else { assert.NoError(t, err, tt.name) } From a944f0e4c520fee53c41f0854db607a5ca9bccac Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 17 May 2024 12:16:52 +0400 Subject: [PATCH 075/352] chore: enforce golangci-lint version (#6700) Signed-off-by: knqyf263 --- magefiles/magefile.go | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/magefiles/magefile.go b/magefiles/magefile.go index 15b9759459b6..6fa5e643b300 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -2,9 +2,11 @@ package main import ( "context" + "encoding/json" "errors" "fmt" "io/fs" + "log/slog" "os" "os/exec" "path/filepath" @@ -13,6 +15,10 @@ import ( "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" "github.com/magefile/mage/target" + + // Trivy packages should not be imported in Mage (see https://github.com/aquasecurity/trivy/pull/4242), + // but this package doesn't have so many dependencies, and Mage is still fast. + "github.com/aquasecurity/trivy/pkg/log" ) var ( @@ -24,6 +30,10 @@ var ( } ) +func init() { + slog.SetDefault(log.New(log.NewHandler(os.Stderr, nil))) // stdout is suppressed in mage +} + func version() (string, error) { if ver, err := sh.Output("git", "describe", "--tags", "--always"); err != nil { return "", err @@ -60,15 +70,38 @@ func (Tool) Wire() error { } // GolangciLint installs golangci-lint -func (Tool) GolangciLint() error { +func (t Tool) GolangciLint() error { const version = "v1.57.2" - if exists(filepath.Join(GOBIN, "golangci-lint")) { + bin := filepath.Join(GOBIN, "golangci-lint") + if exists(bin) && t.matchGolangciLintVersion(bin, version) { return nil } command := fmt.Sprintf("curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b %s %s", GOBIN, version) return sh.Run("bash", "-c", command) } +func (Tool) matchGolangciLintVersion(bin, version string) bool { + out, err := sh.Output(bin, "version", "--format", "json") + if err != nil { + slog.Error("Unable to get golangci-lint version", slog.Any("err", err)) + return false + } + var output struct { + Version string `json:"Version"` + } + if err = json.Unmarshal([]byte(out), &output); err != nil { + slog.Error("Unable to parse golangci-lint version", slog.Any("err", err)) + return false + } + + version = strings.TrimPrefix(version, "v") + if output.Version != version { + slog.Info("golangci-lint version mismatch", slog.String("expected", version), slog.String("actual", output.Version)) + return false + } + return true +} + // Labeler installs labeler func (Tool) Labeler() error { if exists(filepath.Join(GOBIN, "labeler")) { From d6dc56732babbc9d7f788c280a768d8648aa093d Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 17 May 2024 13:29:19 +0400 Subject: [PATCH 076/352] feat(plugin): specify plugin version (#6683) Signed-off-by: knqyf263 Co-authored-by: DmitriyLewen --- docs/docs/plugin/developer-guide.md | 10 + docs/docs/plugin/user-guide.md | 11 + .../configuration/cli/trivy_plugin_install.md | 13 ++ pkg/commands/app.go | 14 +- pkg/fanal/artifact/repo/git_test.go | 2 +- pkg/plugin/index.go | 1 + pkg/plugin/index_test.go | 2 +- pkg/plugin/manager.go | 62 ++++-- pkg/plugin/manager_test.go | 172 ++++----------- pkg/plugin/manager_unix_test.go | 203 ++++++++++++++++++ pkg/plugin/testdata/plugin/index.yaml | 2 +- pkg/plugin/testdata/test_plugin.git/HEAD | 1 + pkg/plugin/testdata/test_plugin.git/config | 8 + .../testdata/test_plugin.git/description | 1 + .../testdata/test_plugin.git/info/exclude | 6 + .../08/6aefb548a1150b765d1e163a5e542fc80bd660 | Bin 0 -> 147 bytes .../0a/e1413e3807e024dbc7de4129d12bdcae7dea61 | 3 + .../49/0535e047e795a5c95306ce281c9f08cdb35b7c | Bin 0 -> 37 bytes .../92/9b4718db99b64a38b4e8c3ec8e673976821c08 | Bin 0 -> 344 bytes .../a0/82cf7b16998b8f048e7d2bf8207d9525688a9f | Bin 0 -> 90 bytes .../d7/8abde66b1d35bdac65402f0e2cddf3a96cd377 | Bin 0 -> 380 bytes .../dc/135ebfc7f680300c981029184a492bbdfa6db3 | Bin 0 -> 91 bytes .../testdata/test_plugin.git/packed-refs | 4 + .../test_plugin.git/refs/heads/.gitkeep | 0 .../test_plugin.git/refs/tags/.gitkeep | 0 pkg/plugin/testdata/test_plugin/plugin.yaml | 7 +- 26 files changed, 368 insertions(+), 154 deletions(-) create mode 100644 pkg/plugin/manager_unix_test.go create mode 100644 pkg/plugin/testdata/test_plugin.git/HEAD create mode 100644 pkg/plugin/testdata/test_plugin.git/config create mode 100644 pkg/plugin/testdata/test_plugin.git/description create mode 100644 pkg/plugin/testdata/test_plugin.git/info/exclude create mode 100644 pkg/plugin/testdata/test_plugin.git/objects/08/6aefb548a1150b765d1e163a5e542fc80bd660 create mode 100644 pkg/plugin/testdata/test_plugin.git/objects/0a/e1413e3807e024dbc7de4129d12bdcae7dea61 create mode 100644 pkg/plugin/testdata/test_plugin.git/objects/49/0535e047e795a5c95306ce281c9f08cdb35b7c create mode 100644 pkg/plugin/testdata/test_plugin.git/objects/92/9b4718db99b64a38b4e8c3ec8e673976821c08 create mode 100644 pkg/plugin/testdata/test_plugin.git/objects/a0/82cf7b16998b8f048e7d2bf8207d9525688a9f create mode 100644 pkg/plugin/testdata/test_plugin.git/objects/d7/8abde66b1d35bdac65402f0e2cddf3a96cd377 create mode 100644 pkg/plugin/testdata/test_plugin.git/objects/dc/135ebfc7f680300c981029184a492bbdfa6db3 create mode 100644 pkg/plugin/testdata/test_plugin.git/packed-refs create mode 100644 pkg/plugin/testdata/test_plugin.git/refs/heads/.gitkeep create mode 100644 pkg/plugin/testdata/test_plugin.git/refs/tags/.gitkeep diff --git a/docs/docs/plugin/developer-guide.md b/docs/docs/plugin/developer-guide.md index 5080bab9ede9..bf77edee00b0 100644 --- a/docs/docs/plugin/developer-guide.md +++ b/docs/docs/plugin/developer-guide.md @@ -130,6 +130,16 @@ The following rules will apply in deciding which platform to select: After determining platform, Trivy will download the execution file from `uri` and store it in the plugin cache. When the plugin is called via Trivy CLI, `bin` command will be executed. +#### Tagging plugin repositories +If you are hosting your plugin in a Git repository, it is strongly recommended to tag your releases with a version number. +By tagging your releases, Trivy can install specific versions of your plugin. + +```bash +$ trivy plugin install referrer@v0.3.0 +``` + +When tagging versions, you must follow [the Semantic Versioning][semver] and prefix the tag with `v`, like `v1.2.3`. + #### Plugin arguments/flags The plugin is responsible for handling flags and arguments. Any arguments are passed to the plugin from the `trivy` command. diff --git a/docs/docs/plugin/user-guide.md b/docs/docs/plugin/user-guide.md index b216f3d63c85..f26f741f7e6e 100644 --- a/docs/docs/plugin/user-guide.md +++ b/docs/docs/plugin/user-guide.md @@ -40,6 +40,8 @@ $ trivy plugin install referrer This command will download the plugin and install it in the plugin cache. + + Trivy adheres to the XDG specification, so the location depends on whether XDG_DATA_HOME is set. Trivy will now search XDG_DATA_HOME for the location of the Trivy plugins cache. The preference order is as follows: @@ -56,6 +58,15 @@ $ trivy plugin install github.com/aquasecurity/trivy-plugin-kubectl $ trivy plugin install myplugin.tar.gz ``` +If the plugin's Git repository is [properly tagged](./developer-guide.md#tagging-plugin-repositories), you can specify the version to install like this: + +```bash +$ trivy plugin install referrer@v0.3.0 +``` + +!!! note + The leading `v` in the version is required. Also, the version must follow the [Semantic Versioning](https://semver.org/). + Under the hood Trivy leverages [go-getter][go-getter] to download plugins. This means the following protocols are supported for downloading plugins: diff --git a/docs/docs/references/configuration/cli/trivy_plugin_install.md b/docs/docs/references/configuration/cli/trivy_plugin_install.md index dbd5f21797b8..ec3afd77a680 100644 --- a/docs/docs/references/configuration/cli/trivy_plugin_install.md +++ b/docs/docs/references/configuration/cli/trivy_plugin_install.md @@ -6,6 +6,19 @@ Install a plugin trivy plugin install NAME | URL | FILE_PATH ``` +### Examples + +``` + # Install a plugin from the plugin index + $ trivy plugin install referrer + + # Specify the version of the plugin to install + $ trivy plugin install referrer@v0.3.0 + + # Install a plugin from a URL + $ trivy plugin install github.com/aquasecurity/trivy-plugin-referrer +``` + ### Options ``` diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 6ae687276f2a..cac211fa1484 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -724,9 +724,17 @@ func NewPluginCommand() *cobra.Command { } cmd.AddCommand( &cobra.Command{ - Use: "install NAME | URL | FILE_PATH", - Aliases: []string{"i"}, - Short: "Install a plugin", + Use: "install NAME | URL | FILE_PATH", + Aliases: []string{"i"}, + Short: "Install a plugin", + Example: ` # Install a plugin from the plugin index + $ trivy plugin install referrer + + # Specify the version of the plugin to install + $ trivy plugin install referrer@v0.3.0 + + # Install a plugin from a URL + $ trivy plugin install github.com/aquasecurity/trivy-plugin-referrer`, SilenceErrors: true, SilenceUsage: true, DisableFlagsInUseLine: true, diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go index e5ea1ad3a230..06dab92fb318 100644 --- a/pkg/fanal/artifact/repo/git_test.go +++ b/pkg/fanal/artifact/repo/git_test.go @@ -4,7 +4,6 @@ package repo import ( "context" - "github.com/aquasecurity/trivy/pkg/fanal/walker" "net/http/httptest" "testing" @@ -16,6 +15,7 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/fanal/walker" ) func setupGitServer() (*httptest.Server, error) { diff --git a/pkg/plugin/index.go b/pkg/plugin/index.go index c825c16e67af..980d4ef1e41f 100644 --- a/pkg/plugin/index.go +++ b/pkg/plugin/index.go @@ -24,6 +24,7 @@ type Index struct { Version int `yaml:"version"` Plugins []struct { Name string `yaml:"name"` + Version string `yaml:"version"` Maintainer string `yaml:"maintainer"` Summary string `yaml:"summary"` Repository string `yaml:"repository"` diff --git a/pkg/plugin/index_test.go b/pkg/plugin/index_test.go index d918a9dac7ef..028c46a10ca4 100644 --- a/pkg/plugin/index_test.go +++ b/pkg/plugin/index_test.go @@ -51,7 +51,7 @@ func TestManager_Search(t *testing.T) { want: `NAME DESCRIPTION MAINTAINER OUTPUT foo A foo plugin aquasecurity ✓ bar A bar plugin aquasecurity -test A test plugin aquasecurity +test_plugin A test plugin aquasecurity `, }, { diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 8f79d744bfb0..741d245d6b62 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -13,6 +13,7 @@ import ( "golang.org/x/xerrors" "gopkg.in/yaml.v3" + "github.com/aquasecurity/go-version/pkg/semver" "github.com/aquasecurity/trivy/pkg/downloader" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" @@ -30,14 +31,20 @@ var ( type ManagerOption func(indexer *Manager) func WithWriter(w io.Writer) ManagerOption { - return func(indexer *Manager) { - indexer.w = w + return func(manager *Manager) { + manager.w = w + } +} + +func WithLogger(logger *log.Logger) ManagerOption { + return func(manager *Manager) { + manager.logger = logger } } func WithIndexURL(indexURL string) ManagerOption { - return func(indexer *Manager) { - indexer.indexURL = indexURL + return func(manager *Manager) { + manager.indexURL = indexURL } } @@ -88,17 +95,18 @@ func Update(ctx context.Context) error { return defaultManager( func Search(ctx context.Context, keyword string) error { return defaultManager().Search(ctx, keyword) } // Install installs a plugin -func (m *Manager) Install(ctx context.Context, name string, opts Options) (Plugin, error) { - src := m.tryIndex(ctx, name) +func (m *Manager) Install(ctx context.Context, arg string, opts Options) (Plugin, error) { + input := m.parseArg(ctx, arg) + input.name = m.tryIndex(ctx, input.name) // If the plugin is already installed, it skips installing the plugin. - if p, installed := m.isInstalled(ctx, src); installed { + if p, installed := m.isInstalled(ctx, input.name, input.version); installed { m.logger.InfoContext(ctx, "The plugin is already installed", log.String("name", p.Name)) return p, nil } - m.logger.InfoContext(ctx, "Installing the plugin...", log.String("src", src)) - return m.install(ctx, src, opts) + m.logger.InfoContext(ctx, "Installing the plugin...", log.String("src", input.name)) + return m.install(ctx, input.String(), opts) } func (m *Manager) install(ctx context.Context, src string, opts Options) (Plugin, error) { @@ -129,7 +137,8 @@ func (m *Manager) install(ctx context.Context, src string, opts Options) (Plugin return Plugin{}, xerrors.Errorf("yaml encode error: %w", err) } - m.logger.InfoContext(ctx, "Plugin successfully installed", log.String("name", plugin.Name)) + m.logger.InfoContext(ctx, "Plugin successfully installed", + log.String("name", plugin.Name), log.String("version", plugin.Version)) return plugin, nil } @@ -340,16 +349,45 @@ func (m *Manager) loadMetadata(dir string) (Plugin, error) { return plugin, nil } -func (m *Manager) isInstalled(ctx context.Context, url string) (Plugin, bool) { +func (m *Manager) isInstalled(ctx context.Context, url, version string) (Plugin, bool) { installedPlugins, err := m.LoadAll(ctx) if err != nil { return Plugin{}, false } for _, plugin := range installedPlugins { - if plugin.Repository == url { + if plugin.Repository == url && (version == "" || plugin.Version == version) { return plugin, true } } return Plugin{}, false } + +// Input represents the user-specified Input. +type Input struct { + name string + version string +} + +func (i *Input) String() string { + if i.version != "" { + // cf. https://github.com/hashicorp/go-getter/blob/268c11cae8cf0d9374783e06572679796abe9ce9/README.md#git-git + return i.name + "?ref=v" + i.version + } + return i.name +} + +func (m *Manager) parseArg(ctx context.Context, arg string) Input { + before, after, found := strings.Cut(arg, "@v") + if !found { + return Input{name: arg} + } else if _, err := semver.Parse(after); err != nil { + m.logger.DebugContext(ctx, "Unable to identify the plugin version", log.String("name", arg), log.Err(err)) + return Input{name: arg} + } + // cf. https://github.com/hashicorp/go-getter/blob/268c11cae8cf0d9374783e06572679796abe9ce9/README.md#git-git + return Input{ + name: before, + version: after, + } +} diff --git a/pkg/plugin/manager_test.go b/pkg/plugin/manager_test.go index 958a320512d5..19b80ee01c0b 100644 --- a/pkg/plugin/manager_test.go +++ b/pkg/plugin/manager_test.go @@ -1,16 +1,15 @@ +//go:build unix + package plugin_test import ( - "archive/zip" "bytes" "context" + "fmt" "github.com/aquasecurity/trivy/pkg/clock" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" v1 "github.com/google/go-containerregistry/pkg/v1" "log/slog" - "net/http" - "net/http/httptest" "os" "path/filepath" "runtime" @@ -24,11 +23,40 @@ import ( "github.com/aquasecurity/trivy/pkg/plugin" ) +func setupInstalledPlugin(t *testing.T, homeDir string, p plugin.Plugin) { + pluginDir := filepath.Join(homeDir, ".trivy", "plugins", p.Name) + + // Create the test plugin directory + err := os.MkdirAll(pluginDir, os.ModePerm) + require.NoError(t, err) + + // write the plugin name + pluginMetadata := fmt.Sprintf(`name: "%s" +repository: "%s" +version: "%s" +usage: test +description: test +platforms: + - selector: + os: linux + arch: amd64 + uri: ./test.sh + bin: ./test.sh +installed: + platform: + os: linux + arch: amd64`, p.Name, p.Repository, p.Version) + + err = os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginMetadata), os.ModePerm) + require.NoError(t, err) +} + func TestManager_Run(t *testing.T) { if runtime.GOOS == "windows" { // the test.sh script can't be run on windows so skipping t.Skip("Test satisfied adequately by Linux tests") } + type fields struct { Name string Repository string @@ -183,110 +211,6 @@ func TestManager_Run(t *testing.T) { } } -func TestManager_Install(t *testing.T) { - if runtime.GOOS == "windows" { - // the test.sh script can't be run on windows so skipping - t.Skip("Test satisfied adequately by Linux tests") - } - wantPlugin := plugin.Plugin{ - Name: "test_plugin", - Repository: "github.com/aquasecurity/trivy-plugin-test", - Version: "0.1.0", - Summary: "test", - Description: "test", - Platforms: []plugin.Platform{ - { - Selector: &plugin.Selector{ - OS: "linux", - Arch: "amd64", - }, - URI: "./test.sh", - Bin: "./test.sh", - }, - }, - Installed: plugin.Installed{ - Platform: plugin.Selector{ - OS: "linux", - Arch: "amd64", - }, - }, - } - - tests := []struct { - name string - pluginName string - want plugin.Plugin - wantFile string - wantErr string - }{ - { - name: "http", - want: wantPlugin, - wantFile: ".trivy/plugins/test_plugin/test.sh", - }, - { - name: "local path", - pluginName: "testdata/test_plugin", - want: wantPlugin, - wantFile: ".trivy/plugins/test_plugin/test.sh", - }, - { - name: "index", - pluginName: "test", - want: wantPlugin, - wantFile: ".trivy/plugins/test_plugin/test.sh", - }, - { - name: "plugin not found", - pluginName: "testdata/not_found", - wantErr: "no such file or directory", - }, - { - name: "no plugin.yaml", - pluginName: "testdata/no_yaml", - wantErr: "file open error", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // The test plugin will be installed here - dst := t.TempDir() - t.Setenv("XDG_DATA_HOME", dst) - - // For plugin index - fsutils.SetCacheDir("testdata") - - if tt.pluginName == "" { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - zr := zip.NewWriter(w) - require.NoError(t, zr.AddFS(os.DirFS("testdata/test_plugin"))) - require.NoError(t, zr.Close()) - })) - t.Cleanup(ts.Close) - tt.pluginName = ts.URL + "/test_plugin.zip" - } - - got, err := plugin.NewManager().Install(context.Background(), tt.pluginName, plugin.Options{ - Platform: ftypes.Platform{ - Platform: &v1.Platform{ - Architecture: "amd64", - OS: "linux", - }, - }, - }) - if tt.wantErr != "" { - require.ErrorContains(t, err, tt.wantErr) - return - } - assert.NoError(t, err) - - assert.EqualExportedValues(t, tt.want, got) - assert.FileExists(t, filepath.Join(dst, tt.wantFile)) - }) - } -} - func TestManager_Uninstall(t *testing.T) { ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) pluginName := "test_plugin" @@ -423,42 +347,28 @@ func TestManager_Upgrade(t *testing.T) { t.Skip("Test satisfied adequately by Linux tests") } pluginName := "test_plugin" + pluginVersion := "0.0.5" tempDir := t.TempDir() - pluginDir := filepath.Join(tempDir, ".trivy", "plugins", pluginName) - t.Setenv("XDG_DATA_HOME", tempDir) - - // Create the test plugin directory - err := os.MkdirAll(pluginDir, os.ModePerm) - require.NoError(t, err) - - // write the plugin name - pluginMetadata := `name: "test_plugin" -repository: testdata/test_plugin -version: "0.0.5" -usage: test -description: A simple test plugin -installed: - platform: - os: linux - arch: amd64` - - err = os.WriteFile(filepath.Join(pluginDir, "plugin.yaml"), []byte(pluginMetadata), os.ModePerm) - require.NoError(t, err) + setupInstalledPlugin(t, tempDir, plugin.Plugin{ + Name: pluginName, + Version: pluginVersion, + Repository: "testdata/test_plugin", + }) ctx := context.Background() m := plugin.NewManager() // verify initial version - verifyVersion(t, ctx, m, pluginName, "0.0.5") + verifyVersion(t, ctx, m, pluginName, pluginVersion) // Upgrade the existing plugin - err = m.Upgrade(ctx, nil) + err := m.Upgrade(ctx, nil) require.NoError(t, err) // verify plugin updated - verifyVersion(t, ctx, m, pluginName, "0.1.0") + verifyVersion(t, ctx, m, pluginName, "0.2.0") } func verifyVersion(t *testing.T, ctx context.Context, m *plugin.Manager, pluginName, expectedVersion string) { diff --git a/pkg/plugin/manager_unix_test.go b/pkg/plugin/manager_unix_test.go new file mode 100644 index 000000000000..52f4b0d4e2c3 --- /dev/null +++ b/pkg/plugin/manager_unix_test.go @@ -0,0 +1,203 @@ +//go:build unix + +package plugin_test + +import ( + "archive/zip" + "bytes" + "context" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + "time" + + "github.com/google/go-containerregistry/pkg/v1" + "github.com/sosedoff/gitkit" // Not work on Windows + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/clock" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/plugin" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +func setupGitServer() (*httptest.Server, error) { + service := gitkit.New(gitkit.Config{ + Dir: "./testdata", + AutoCreate: false, + }) + + if err := service.Setup(); err != nil { + return nil, err + } + + ts := httptest.NewServer(service) + + return ts, nil +} + +func TestManager_Install(t *testing.T) { + gs, err := setupGitServer() + require.NoError(t, err) + t.Cleanup(gs.Close) + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + zr := zip.NewWriter(w) + require.NoError(t, zr.AddFS(os.DirFS("testdata/test_plugin"))) + require.NoError(t, zr.Close()) + })) + t.Cleanup(ts.Close) + + wantPlugin := plugin.Plugin{ + Name: "test_plugin", + Repository: "testdata/test_plugin", + Version: "0.2.0", + Summary: "test", + Description: "test", + Platforms: []plugin.Platform{ + { + Selector: &plugin.Selector{ + OS: "linux", + Arch: "amd64", + }, + URI: "./test.sh", + Bin: "./test.sh", + }, + }, + Installed: plugin.Installed{ + Platform: plugin.Selector{ + OS: "linux", + Arch: "amd64", + }, + }, + } + wantPluginWithVersion := wantPlugin + wantPluginWithVersion.Version = "0.1.0" + + wantLogs := `2021-08-25T12:20:30Z INFO Installing the plugin... src="%s" +2021-08-25T12:20:30Z INFO Plugin successfully installed name="test_plugin" version="%s" +` + + tests := []struct { + name string + pluginName string + installed *plugin.Plugin + want plugin.Plugin + wantFile string + wantLogs string + wantErr string + }{ + { + name: "http", + pluginName: ts.URL + "/test_plugin.zip", + want: wantPlugin, + wantFile: ".trivy/plugins/test_plugin/test.sh", + wantLogs: fmt.Sprintf(wantLogs, ts.URL+"/test_plugin.zip", "0.2.0"), + }, + { + name: "local path", + pluginName: "testdata/test_plugin", + want: wantPlugin, + wantFile: ".trivy/plugins/test_plugin/test.sh", + wantLogs: fmt.Sprintf(wantLogs, "testdata/test_plugin", "0.2.0"), + }, + { + name: "git", + pluginName: "git::" + gs.URL + "/test_plugin.git", + want: wantPlugin, + wantFile: ".trivy/plugins/test_plugin/test.sh", + wantLogs: fmt.Sprintf(wantLogs, "git::"+gs.URL+"/test_plugin.git", "0.2.0"), + }, + { + name: "with version", + pluginName: "git::" + gs.URL + "/test_plugin.git@v0.1.0", + want: wantPluginWithVersion, + wantFile: ".trivy/plugins/test_plugin/test.sh", + wantLogs: fmt.Sprintf(wantLogs, "git::"+gs.URL+"/test_plugin.git", "0.1.0"), + }, + { + name: "via index", + pluginName: "test_plugin", + want: wantPlugin, + wantFile: ".trivy/plugins/test_plugin/test.sh", + wantLogs: fmt.Sprintf(wantLogs, "testdata/test_plugin", "0.2.0"), + }, + { + name: "installed", + pluginName: "test_plugin", + installed: &plugin.Plugin{ + Name: "test_plugin", + Repository: "testdata/test_plugin", + Version: "0.2.0", + }, + want: wantPlugin, + wantLogs: "2021-08-25T12:20:30Z INFO The plugin is already installed name=\"test_plugin\"\n", + }, + { + name: "different version installed", + pluginName: "test_plugin@v0.2.0", + installed: &plugin.Plugin{ + Name: "test_plugin", + Repository: "testdata/test_plugin", + Version: "0.1.0", + }, + want: wantPlugin, + wantLogs: fmt.Sprintf(wantLogs, "testdata/test_plugin", "0.2.0"), + }, + { + name: "plugin not found", + pluginName: "testdata/not_found", + wantErr: "no such file or directory", + }, + { + name: "no plugin.yaml", + pluginName: "testdata/no_yaml", + wantErr: "file open error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // The test plugin will be installed here + dst := t.TempDir() + t.Setenv("XDG_DATA_HOME", dst) + + // For plugin index + fsutils.SetCacheDir("testdata") + + if tt.installed != nil { + setupInstalledPlugin(t, dst, *tt.installed) + } + + var gotLogs bytes.Buffer + logger := log.New(log.NewHandler(&gotLogs, nil)) + + ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) + + got, err := plugin.NewManager(plugin.WithLogger(logger)).Install(ctx, tt.pluginName, plugin.Options{ + Platform: ftypes.Platform{ + Platform: &v1.Platform{ + Architecture: "amd64", + OS: "linux", + }, + }, + }) + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + assert.NoError(t, err) + + assert.EqualExportedValues(t, tt.want, got) + if tt.wantFile != "" { + assert.FileExists(t, filepath.Join(dst, tt.wantFile)) + } + assert.Equal(t, tt.wantLogs, gotLogs.String()) + }) + } +} diff --git a/pkg/plugin/testdata/plugin/index.yaml b/pkg/plugin/testdata/plugin/index.yaml index 17fbb1987939..b37b86e176a0 100644 --- a/pkg/plugin/testdata/plugin/index.yaml +++ b/pkg/plugin/testdata/plugin/index.yaml @@ -9,7 +9,7 @@ plugins: maintainer: aquasecurity summary: A bar plugin repository: github.com/aquasecurity/trivy-plugin-bar - - name: test + - name: test_plugin maintainer: aquasecurity summary: A test plugin repository: testdata/test_plugin \ No newline at end of file diff --git a/pkg/plugin/testdata/test_plugin.git/HEAD b/pkg/plugin/testdata/test_plugin.git/HEAD new file mode 100644 index 000000000000..b870d82622c1 --- /dev/null +++ b/pkg/plugin/testdata/test_plugin.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/pkg/plugin/testdata/test_plugin.git/config b/pkg/plugin/testdata/test_plugin.git/config new file mode 100644 index 000000000000..9512a718938b --- /dev/null +++ b/pkg/plugin/testdata/test_plugin.git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true +[remote "origin"] + url = /Users/teppei/src/github.com/aquasecurity/trivy/pkg/plugin/testdata/test_plugin diff --git a/pkg/plugin/testdata/test_plugin.git/description b/pkg/plugin/testdata/test_plugin.git/description new file mode 100644 index 000000000000..498b267a8c78 --- /dev/null +++ b/pkg/plugin/testdata/test_plugin.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/pkg/plugin/testdata/test_plugin.git/info/exclude b/pkg/plugin/testdata/test_plugin.git/info/exclude new file mode 100644 index 000000000000..a5196d1be8fb --- /dev/null +++ b/pkg/plugin/testdata/test_plugin.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/pkg/plugin/testdata/test_plugin.git/objects/08/6aefb548a1150b765d1e163a5e542fc80bd660 b/pkg/plugin/testdata/test_plugin.git/objects/08/6aefb548a1150b765d1e163a5e542fc80bd660 new file mode 100644 index 0000000000000000000000000000000000000000..e7d696256b86d9c3323ac0740015d918dae20479 GIT binary patch literal 147 zcmV;E0Brww0Zooe4#F@DL|Nw)R`-O05K?m)}bpc2I(oI`pUXp0py9{vdRpd=i8&5in`$3aPP4!^isNtVPhcckT1 zw3u%pIWY=g4?>ofdcUbQK@3>-<@wQ=mdyaV^>Xe`No+v(7qo2Mnz`2htT&%uLTVfd BMbrQQ literal 0 HcmV?d00001 diff --git a/pkg/plugin/testdata/test_plugin.git/objects/0a/e1413e3807e024dbc7de4129d12bdcae7dea61 b/pkg/plugin/testdata/test_plugin.git/objects/0a/e1413e3807e024dbc7de4129d12bdcae7dea61 new file mode 100644 index 000000000000..b16c882bc30f --- /dev/null +++ b/pkg/plugin/testdata/test_plugin.git/objects/0a/e1413e3807e024dbc7de4129d12bdcae7dea61 @@ -0,0 +1,3 @@ +xM +0 S[7a}ɺ +#iE޵(s,>-0!b C!)A1$$~h  AI ZI,\:r*{,A8'oMhd^ݩ [Bi \ No newline at end of file diff --git a/pkg/plugin/testdata/test_plugin.git/objects/49/0535e047e795a5c95306ce281c9f08cdb35b7c b/pkg/plugin/testdata/test_plugin.git/objects/49/0535e047e795a5c95306ce281c9f08cdb35b7c new file mode 100644 index 0000000000000000000000000000000000000000..964cbf82f49bbc0825b9a3b0ce88f1ab88927e94 GIT binary patch literal 37 tcmbGR=C3>B9+OaM$w5k~+3 literal 0 HcmV?d00001 diff --git a/pkg/plugin/testdata/test_plugin.git/objects/92/9b4718db99b64a38b4e8c3ec8e673976821c08 b/pkg/plugin/testdata/test_plugin.git/objects/92/9b4718db99b64a38b4e8c3ec8e673976821c08 new file mode 100644 index 0000000000000000000000000000000000000000..ba6cfbad6f8c1f126ac73ae05fca5053271761db GIT binary patch literal 344 zcmV-e0jK_W0hN$TZ-PJ+g}a_#F?&;;VL&ixo1#!4h4BFt!wyCm1r!nLgI{0Jbk|K! za+7nv4oJIIf`-f{TcYpNR- zg|2Ht*D|Q;`78a-0rWAod{mffuVpuy(>t3HTH3)^?qoi8yu4?8lsR@wKDEr`m+hg> zCh3y#RMAWh;A#C)In)UyZ*h$Vh}{!fC={rVJ`}c*p?BA;Zk>8Pn-SVZGhBq4g{h}Y z0QPjLYn4r;g>g!Pfhs$*twD<0Jhs~?3#!%munJUDR7&Z1sa4fa$*pXH+%j(SIX#Ex q^)&`?vz$+hQ8sEAVK6i>Ff%bxD99;I&&<=SOw7$;;K+Kv)nlP3cUi2Qm{nYe w{t51D2|#7$rltxdsl_FF#Tg8qtfmj#pHE$SGMMe0hRl49vzw!906R(?UD|Xgng9R* literal 0 HcmV?d00001 diff --git a/pkg/plugin/testdata/test_plugin.git/objects/d7/8abde66b1d35bdac65402f0e2cddf3a96cd377 b/pkg/plugin/testdata/test_plugin.git/objects/d7/8abde66b1d35bdac65402f0e2cddf3a96cd377 new file mode 100644 index 0000000000000000000000000000000000000000..78b94e8996dd4a8bb2cdcb6c052a4c599e3a7813 GIT binary patch literal 380 zcmV-?0fYW{0hN$TZ<|0Ag}a_#G5e_YJb27VZB=8aO>L&Nfi`6~49pk|h7h8}{`G-G z-F4F=UFqCUI#*Xmb!}UX(8lb;IFu!nh!LgT96Ow|oM~jS(k)1?89{ErZDKo&Nv=UJ zhw?H)PIzv)m=!taxnoJna;s#DmWq{*OS$VXf)!#wK8>etfalA@RTGE8Pk-H4)5@w@ zsIL72v5Rd+DP|PjAqyce(doUh{O_RY&0}TY$5~#m2?dPfEsVpBSS9H(c;f)3ScEBR zwyRl(Y5Zt*dqqXJ|NZM6)hUjW^UTb$j`laY^Xsj7K3yJbt`Ev+=5Gyv!~M61NJN>P z{CtsjNWCTej5KJDzAL-QO)y;xr`1blwx;4H*SN|25Q`+R0i5aKyC~wsJId24wf_`- z!lWguq3HLPcR!APt$neJ&25DXS(c2h} acGpAn8NAmK2>)MY0JIAVK6i>Ff%bxD99;I&&<=SOw7$;;Ckq2XTknJ<@WJ=j+z&> x@2snRl?YU3ZfdGfl3HA%SDeA%$!hw*{rS|TCxh9}X~@jyIJ-Hz1^}iS9{935F311? literal 0 HcmV?d00001 diff --git a/pkg/plugin/testdata/test_plugin.git/packed-refs b/pkg/plugin/testdata/test_plugin.git/packed-refs new file mode 100644 index 000000000000..00a4f957a23f --- /dev/null +++ b/pkg/plugin/testdata/test_plugin.git/packed-refs @@ -0,0 +1,4 @@ +# pack-refs with: peeled fully-peeled sorted +d78abde66b1d35bdac65402f0e2cddf3a96cd377 refs/heads/main +929b4718db99b64a38b4e8c3ec8e673976821c08 refs/tags/v0.1.0 +d78abde66b1d35bdac65402f0e2cddf3a96cd377 refs/tags/v0.2.0 diff --git a/pkg/plugin/testdata/test_plugin.git/refs/heads/.gitkeep b/pkg/plugin/testdata/test_plugin.git/refs/heads/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/plugin/testdata/test_plugin.git/refs/tags/.gitkeep b/pkg/plugin/testdata/test_plugin.git/refs/tags/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/pkg/plugin/testdata/test_plugin/plugin.yaml b/pkg/plugin/testdata/test_plugin/plugin.yaml index 272c8d5760a7..086aefb548a1 100644 --- a/pkg/plugin/testdata/test_plugin/plugin.yaml +++ b/pkg/plugin/testdata/test_plugin/plugin.yaml @@ -1,6 +1,6 @@ name: "test_plugin" -repository: github.com/aquasecurity/trivy-plugin-test -version: "0.1.0" +repository: testdata/test_plugin +version: "0.2.0" summary: test description: test platforms: @@ -9,6 +9,3 @@ platforms: arch: amd64 uri: ./test.sh bin: ./test.sh -# for testing -_goos: linux -_goarch: amd64 \ No newline at end of file From 39a746c77837f873e87b81be40676818030f44c5 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Sat, 18 May 2024 03:53:59 +0700 Subject: [PATCH 077/352] fix(misconf): don't shift ignore rule related to code (#6708) --- pkg/iac/ignore/parse.go | 42 +++++++++++++++++++++-------------- pkg/iac/ignore/rule.go | 16 +++++++++----- pkg/iac/ignore/rule_test.go | 44 +++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 21 deletions(-) diff --git a/pkg/iac/ignore/parse.go b/pkg/iac/ignore/parse.go index bb2f13603263..889848907548 100644 --- a/pkg/iac/ignore/parse.go +++ b/pkg/iac/ignore/parse.go @@ -5,6 +5,8 @@ import ( "strings" "time" + "github.com/samber/lo" + "github.com/aquasecurity/trivy/pkg/iac/types" "github.com/aquasecurity/trivy/pkg/log" ) @@ -36,23 +38,34 @@ func Parse(src, path string, parsers ...RuleSectionParser) Rules { func parseLine(line string, rng types.Range, parsers []RuleSectionParser) []Rule { var rules []Rule - sections := strings.Split(strings.TrimSpace(line), " ") - for _, section := range sections { - section := strings.TrimSpace(section) - section = strings.TrimLeftFunc(section, func(r rune) bool { + parts := strings.Split(strings.TrimSpace(line), " ") + parts = lo.FilterMap(parts, func(part string, _ int) (string, bool) { + part = strings.TrimSpace(part) + part = strings.TrimLeftFunc(part, func(r rune) bool { return r == '#' || r == '/' || r == '*' }) - section, exists := hasIgnoreRulePrefix(section) + return part, part != "" + }) + + for i, part := range parts { + part, exists := hasIgnoreRulePrefix(part) if !exists { continue } - rule, err := parseComment(section, rng, parsers) + sections, err := parseRuleSections(part, rng, parsers) if err != nil { log.Debug("Failed to parse rule", log.String("range", rng.String()), log.Err(err)) continue } + + rule := Rule{ + rng: rng, + isStartLine: i == 0 || (len(rules) > 0 && rules[0].isStartLine), + sections: sections, + } + rules = append(rules, rule) } @@ -72,11 +85,8 @@ func hasIgnoreRulePrefix(s string) (string, bool) { return "", false } -func parseComment(input string, rng types.Range, parsers []RuleSectionParser) (Rule, error) { - rule := Rule{ - rng: rng, - sections: make(map[string]any), - } +func parseRuleSections(input string, rng types.Range, parsers []RuleSectionParser) (map[string]any, error) { + sections := make(map[string]any) parsers = append(parsers, &expiryDateParser{ rng: rng, @@ -93,7 +103,7 @@ func parseComment(input string, rng types.Range, parsers []RuleSectionParser) (R StringMatchParser{SectionKey: "id"}, } if idParser.Parse(val) { - rule.sections[idParser.Key()] = idParser.Param() + sections[idParser.Key()] = idParser.Param() } } @@ -103,16 +113,16 @@ func parseComment(input string, rng types.Range, parsers []RuleSectionParser) (R } if parser.Parse(val) { - rule.sections[parser.Key()] = parser.Param() + sections[parser.Key()] = parser.Param() } } } - if _, exists := rule.sections["id"]; !exists { - return Rule{}, errors.New("rule section with the `ignore` key is required") + if _, exists := sections["id"]; !exists { + return nil, errors.New("rule section with the `ignore` key is required") } - return rule, nil + return sections, nil } type StringMatchParser struct { diff --git a/pkg/iac/ignore/rule.go b/pkg/iac/ignore/rule.go index 61057ce75f87..86e24e8dd120 100644 --- a/pkg/iac/ignore/rule.go +++ b/pkg/iac/ignore/rule.go @@ -30,11 +30,16 @@ func (r Rules) shift() { ) for i := len(r) - 1; i > 0; i-- { - currentIgnore, nextIgnore := r[i], r[i-1] + currentRule, prevRule := r[i], r[i-1] + + if !prevRule.isStartLine { + continue + } + if currentRange == nil { - currentRange = ¤tIgnore.rng + currentRange = ¤tRule.rng } - if nextIgnore.rng.GetStartLine()+1+offset == currentIgnore.rng.GetStartLine() { + if prevRule.rng.GetStartLine()+1+offset == currentRule.rng.GetStartLine() { r[i-1].rng = *currentRange offset++ } else { @@ -46,8 +51,9 @@ func (r Rules) shift() { // Rule represents a rule for ignoring vulnerabilities. type Rule struct { - rng types.Range - sections map[string]any + rng types.Range + isStartLine bool + sections map[string]any } func (r Rule) ignore(m types.Metadata, ids []string, ignorers map[string]Ignorer) bool { diff --git a/pkg/iac/ignore/rule_test.go b/pkg/iac/ignore/rule_test.go index da89ca2a3595..29c38bf76b5d 100644 --- a/pkg/iac/ignore/rule_test.go +++ b/pkg/iac/ignore/rule_test.go @@ -190,6 +190,50 @@ func TestRules_Ignore(t *testing.T) { }, shouldIgnore: false, }, + { + name: "multiple ignore rules on the same line", + src: `test #trivy:ignore:rule-1 +test #trivy:ignore:rule-2 + `, + args: args{ + metadata: metadataWithLine(filename, 1), + ids: []string{"rule-1"}, + }, + shouldIgnore: true, + }, + { + name: "multiple ignore rules on the same line", + src: `# trivy:ignore:rule-1 +# trivy:ignore:rule-2 +test #trivy:ignore:rule-3 +`, + args: args{ + metadata: metadataWithLine(filename, 3), + ids: []string{"rule-1"}, + }, + shouldIgnore: true, + }, + { + name: "multiple ignore rules on the same line", + src: `# trivy:ignore:rule-1 # trivy:ignore:rule-2 +# trivy:ignore:rule-3 +test #trivy:ignore:rule-4 +`, + args: args{ + metadata: metadataWithLine(filename, 3), + ids: []string{"rule-2"}, + }, + shouldIgnore: true, + }, + { + name: "multiple ids", + src: `# trivy:ignore:rule-1`, + args: args{ + metadata: metadataWithLine(filename, 1), + ids: []string{"rule-1", "rule-2"}, + }, + shouldIgnore: true, + }, } for _, tt := range tests { From eca51500c3534d9ca5bb14f60dc15c7908254487 Mon Sep 17 00:00:00 2001 From: chenk Date: Sun, 19 May 2024 19:03:00 +0300 Subject: [PATCH 078/352] chore: auto-bump golang patch versions (#6711) Signed-off-by: chenk --- .github/workflows/auto-update-labels.yaml | 6 ++++-- .github/workflows/reusable-release.yaml | 3 ++- .github/workflows/test.yaml | 15 ++++++++------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/auto-update-labels.yaml b/.github/workflows/auto-update-labels.yaml index f10febe7c070..5080e5d0a28c 100644 --- a/.github/workflows/auto-update-labels.yaml +++ b/.github/workflows/auto-update-labels.yaml @@ -5,7 +5,8 @@ on: - 'misc/triage/labels.yaml' branches: - main - +env: + GO_VERSION: '1.22' jobs: deploy: name: Auto-update labels @@ -17,7 +18,8 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: go.mod + # cf. https://github.com/aquasecurity/trivy/pull/6711 + go-version: ${{ env.GO_VERSION }} - name: Install aqua tools uses: aquaproj/aqua-installer@v3.0.0 diff --git a/.github/workflows/reusable-release.yaml b/.github/workflows/reusable-release.yaml index 8f5d26a52922..39ca65809f06 100644 --- a/.github/workflows/reusable-release.yaml +++ b/.github/workflows/reusable-release.yaml @@ -14,6 +14,7 @@ on: env: GH_USER: "aqua-bot" + GO_VERSION: '1.22' jobs: release: @@ -76,7 +77,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version-file: go.mod + go-version: ${{ env.GO_VERSION }} cache: false # Disable cache to avoid free space issues during `Post Setup Go` step. - name: Generate SBOM diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3ce0e8991436..b42b2b517a38 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -7,6 +7,8 @@ on: - 'mkdocs.yml' - 'LICENSE' merge_group: +env: + GO_VERSION: '1.22' jobs: test: name: Test @@ -30,8 +32,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: go.mod - + go-version: ${{ env.GO_VERSION }} - name: go mod tidy run: | go mod tidy @@ -83,7 +84,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: go.mod + go-version: ${{ env.GO_VERSION }} - name: Install tools uses: aquaproj/aqua-installer@v3.0.0 @@ -112,7 +113,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: go.mod + go-version: ${{ env.GO_VERSION }} - name: Install tools uses: aquaproj/aqua-installer@v3.0.0 @@ -132,7 +133,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: go.mod + go-version: ${{ env.GO_VERSION }} - name: Install tools uses: aquaproj/aqua-installer@v3.0.0 @@ -163,7 +164,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: go.mod + go-version: ${{ env.GO_VERSION }} - name: Install tools uses: aquaproj/aqua-installer@v3.0.0 with: @@ -197,7 +198,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: go.mod + go-version: ${{ env.GO_VERSION }} - name: Determine GoReleaser ID id: goreleaser_id From e7f14f729de259551203f313e57d2d9d3aa2ff87 Mon Sep 17 00:00:00 2001 From: Luke Young <91491244+lyoung-confluent@users.noreply.github.com> Date: Sun, 19 May 2024 20:10:09 -0700 Subject: [PATCH 079/352] Merge pull request from GHSA-xcq4-m2r3-cmrj * Update azure.go * Update ecr.go * Update google.go * Update ecr_test.go * Update azure_test.go * Update google_test.go --- pkg/fanal/image/registry/azure/azure.go | 2 +- pkg/fanal/image/registry/azure/azure_test.go | 5 +++++ pkg/fanal/image/registry/ecr/ecr.go | 5 +++-- pkg/fanal/image/registry/ecr/ecr_test.go | 8 ++++++++ pkg/fanal/image/registry/google/google.go | 7 ++++--- pkg/fanal/image/registry/google/google_test.go | 4 ++++ 6 files changed, 25 insertions(+), 6 deletions(-) diff --git a/pkg/fanal/image/registry/azure/azure.go b/pkg/fanal/image/registry/azure/azure.go index 491fce4e27c3..fe348eaac9f5 100644 --- a/pkg/fanal/image/registry/azure/azure.go +++ b/pkg/fanal/image/registry/azure/azure.go @@ -20,7 +20,7 @@ type Registry struct { } const ( - azureURL = "azurecr.io" + azureURL = ".azurecr.io" scope = "https://management.azure.com/.default" scheme = "https" ) diff --git a/pkg/fanal/image/registry/azure/azure_test.go b/pkg/fanal/image/registry/azure/azure_test.go index ae823b82a65a..dc4d03fc078e 100644 --- a/pkg/fanal/image/registry/azure/azure_test.go +++ b/pkg/fanal/image/registry/azure/azure_test.go @@ -19,6 +19,11 @@ func TestRegistry_CheckOptions(t *testing.T) { name: "happy path", domain: "test.azurecr.io", }, + { + name: "invalidURL", + domain: "not-azurecr.io", + wantErr: "Azure registry: invalid url pattern", + }, { name: "invalidURL", domain: "alpine:3.9", diff --git a/pkg/fanal/image/registry/ecr/ecr.go b/pkg/fanal/image/registry/ecr/ecr.go index e675ed47afaf..90dc4ef69f09 100644 --- a/pkg/fanal/image/registry/ecr/ecr.go +++ b/pkg/fanal/image/registry/ecr/ecr.go @@ -14,7 +14,8 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" ) -const ecrURL = "amazonaws.com" +const ecrURLSuffix = ".amazonaws.com" +const ecrURLPartial = ".dkr.ecr" type ecrAPI interface { GetAuthorizationToken(ctx context.Context, params *ecr.GetAuthorizationTokenInput, optFns ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) @@ -37,7 +38,7 @@ func getSession(option types.RegistryOptions) (aws.Config, error) { } func (e *ECR) CheckOptions(domain string, option types.RegistryOptions) error { - if !strings.HasSuffix(domain, ecrURL) { + if !strings.HasSuffix(domain, ecrURLSuffix) && !strings.Contains(domain, ecrURLPartial) { return xerrors.Errorf("ECR : %w", types.InvalidURLPattern) } diff --git a/pkg/fanal/image/registry/ecr/ecr_test.go b/pkg/fanal/image/registry/ecr/ecr_test.go index 63ae1858114c..f968cf1d850d 100644 --- a/pkg/fanal/image/registry/ecr/ecr_test.go +++ b/pkg/fanal/image/registry/ecr/ecr_test.go @@ -21,6 +21,14 @@ func TestCheckOptions(t *testing.T) { domain: "alpine:3.9", wantErr: types.InvalidURLPattern, }, + "InvalidDomain": { + domain: "xxx.ecr.ap-northeast-1.not-amazonaws.com", + wantErr: types.InvalidURLPattern, + }, + "InvalidSubdomain": { + domain: "xxx.s3.ap-northeast-1.amazonaws.com", + wantErr: types.InvalidURLPattern, + }, "NoOption": { domain: "xxx.ecr.ap-northeast-1.amazonaws.com", }, diff --git a/pkg/fanal/image/registry/google/google.go b/pkg/fanal/image/registry/google/google.go index f4e7a7414260..fe52c85f7493 100644 --- a/pkg/fanal/image/registry/google/google.go +++ b/pkg/fanal/image/registry/google/google.go @@ -18,13 +18,14 @@ type Registry struct { } // Google container registry -const gcrURL = "gcr.io" +const gcrURLDomain = "gcr.io" +const gcrURLSuffix = ".gcr.io" // Google artifact registry -const garURL = "docker.pkg.dev" +const garURLSuffix = "-docker.pkg.dev" func (g *Registry) CheckOptions(domain string, option types.RegistryOptions) error { - if !strings.HasSuffix(domain, gcrURL) && !strings.HasSuffix(domain, garURL) { + if domain != gcrURLDomain && !strings.HasSuffix(domain, gcrURLSuffix) && !strings.HasSuffix(domain, garURLSuffix) { return xerrors.Errorf("Google registry: %w", types.InvalidURLPattern) } g.domain = domain diff --git a/pkg/fanal/image/registry/google/google_test.go b/pkg/fanal/image/registry/google/google_test.go index 62a2b1f57627..2e4ba64d663e 100644 --- a/pkg/fanal/image/registry/google/google_test.go +++ b/pkg/fanal/image/registry/google/google_test.go @@ -21,6 +21,10 @@ func TestCheckOptions(t *testing.T) { domain: "alpine:3.9", wantErr: types.InvalidURLPattern, }, + "InvalidDomain": { + domain: "not-gcr.io", + wantErr: types.InvalidURLPattern, + }, "NoOption": { domain: "gcr.io", gcr: &Registry{domain: "gcr.io"}, From ff32deb7bf9163c06963f557228260b3b8c161ed Mon Sep 17 00:00:00 2001 From: chenk Date: Mon, 20 May 2024 08:09:27 +0300 Subject: [PATCH 080/352] fix: node-collector high and critical cves (#6707) Signed-off-by: chenk Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- .../configuration/cli/trivy_kubernetes.md | 2 +- go.mod | 24 +++++----- go.sum | 48 +++++++++---------- pkg/fanal/image/registry/ecr/ecr.go | 2 +- pkg/flag/kubernetes_flags.go | 2 +- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index cdc50f9f5451..edd2efe6c5de 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -72,7 +72,7 @@ trivy kubernetes [flags] [CONTEXT] --list-all-pkgs enabling the option will output all packages regardless of vulnerability --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --no-progress suppress progress bar - --node-collector-imageref string indicate the image reference for the node-collector scan job (default "ghcr.io/aquasecurity/node-collector:0.0.9") + --node-collector-imageref string indicate the image reference for the node-collector scan job (default "ghcr.io/aquasecurity/node-collector:0.2.1") --node-collector-namespace string specify the namespace in which the node-collector job should be deployed (default "trivy-temp") --offline-scan do not issue API requests to identify dependencies -o, --output string output file name diff --git a/go.mod b/go.mod index d25d339b8a1d..5d28534b19d0 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/aquasecurity/trivy -go 1.22 +go 1.22.0 -toolchain go1.22.0 +toolchain go1.22.2 require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible @@ -30,7 +30,7 @@ require ( github.com/aquasecurity/trivy-checks v0.10.5-0.20240514040354-93bcb2f8c233 github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 - github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240507080745-f6c5fb0a3f3f + github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7 github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2/config v1.27.11 github.com/aws/aws-sdk-go-v2/credentials v1.17.11 @@ -129,7 +129,7 @@ require ( google.golang.org/protobuf v1.34.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.14.2 - k8s.io/api v0.29.3 + k8s.io/api v0.30.0 k8s.io/utils v0.0.0-20231127182322-b307cd553661 modernc.org/sqlite v1.29.7 sigs.k8s.io/yaml v1.4.0 @@ -171,7 +171,7 @@ require ( github.com/antchfx/xpath v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.51.25 // indirect + github.com/aws/aws-sdk-go v1.53.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect @@ -407,14 +407,14 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.29.0 // indirect - k8s.io/apimachinery v0.29.3 // indirect + k8s.io/apimachinery v0.30.0 // indirect k8s.io/apiserver v0.29.0 // indirect - k8s.io/cli-runtime v0.29.3 // indirect - k8s.io/client-go v0.29.3 // indirect - k8s.io/component-base v0.29.3 // indirect - k8s.io/klog/v2 v2.120.0 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/kubectl v0.29.3 // indirect + k8s.io/cli-runtime v0.30.0 // indirect + k8s.io/client-go v0.30.0 // indirect + k8s.io/component-base v0.30.0 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/kubectl v0.30.0 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect modernc.org/libc v1.49.3 // indirect modernc.org/mathutil v1.6.0 // indirect diff --git a/go.sum b/go.sum index d62a5fb3c10b..55e43b57a536 100644 --- a/go.sum +++ b/go.sum @@ -781,8 +781,8 @@ github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTU github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240507080745-f6c5fb0a3f3f h1:IJkhrSrlpemDZ+tPLKlJeuuK64yFcLqpTdQa4v173zA= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240507080745-f6c5fb0a3f3f/go.mod h1:5uAM0CbAlVBTWc4yKCDHtl7zCwZMMYfL7erBnP3gwkI= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7 h1:bLmh/xuC/7abvt9S/xnODTQRu8fW6BhFHS6Cmbn0RNU= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7/go.mod h1:HSpAJE8Y5Cjjg0Aw/0lqd3vMihN/FxBEj/f/7yDi/Uc= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -796,8 +796,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.51.25 h1:DjTT8mtmsachhV6yrXR8+yhnG6120dazr720nopRsls= -github.com/aws/aws-sdk-go v1.51.25/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.53.0 h1:MMo1x1ggPPxDfHMXJnQudTbGXYlD4UigUAud1DJxPVo= +github.com/aws/aws-sdk-go v1.53.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= @@ -1853,15 +1853,15 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= -github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= +github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= github.com/open-policy-agent/opa v0.64.1 h1:n8IJTYlFWzqiOYx+JiawbErVxiqAyXohovcZxYbskxQ= github.com/open-policy-agent/opa v0.64.1/go.mod h1:j4VeLorVpKipnkQ2TDjWshEuV3cvP/rHzQhYaraUXZY= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -3089,32 +3089,32 @@ honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw= -k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= +k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= +k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= -k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= +k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= +k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.29.0 h1:Y1xEMjJkP+BIi0GSEv1BBrf1jLU9UPfAnnGGbbDdp7o= k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM= -k8s.io/cli-runtime v0.29.3 h1:r68rephmmytoywkw2MyJ+CxjpasJDQY7AGc3XY2iv1k= -k8s.io/cli-runtime v0.29.3/go.mod h1:aqVUsk86/RhaGJwDhHXH0jcdqBrgdF3bZWk4Z9D4mkM= +k8s.io/cli-runtime v0.30.0 h1:0vn6/XhOvn1RJ2KJOC6IRR2CGqrpT6QQF4+8pYpWQ48= +k8s.io/cli-runtime v0.30.0/go.mod h1:vATpDMATVTMA79sZ0YUCzlMelf6rUjoBzlp+RnoM+cg= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg= -k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0= +k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= +k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/component-base v0.29.3 h1:Oq9/nddUxlnrCuuR2K/jp6aflVvc0uDvxMzAWxnGzAo= -k8s.io/component-base v0.29.3/go.mod h1:Yuj33XXjuOk2BAaHsIGHhCKZQAgYKhqIxIjIr2UXYio= +k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o= +k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= @@ -3122,13 +3122,13 @@ k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.120.0 h1:z+q5mfovBj1fKFxiRzsa2DsJLPIVMk/KFL81LMOfK+8= -k8s.io/klog/v2 v2.120.0/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/kubectl v0.29.3 h1:RuwyyIU42MAISRIePaa8Q7A3U74Q9P4MoJbDFz9o3us= -k8s.io/kubectl v0.29.3/go.mod h1:yCxfY1dbwgVdEt2zkJ6d5NNLOhhWgTyrqACIoFhpdd4= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kubectl v0.30.0 h1:xbPvzagbJ6RNYVMVuiHArC1grrV5vSmmIcSZuCdzRyk= +k8s.io/kubectl v0.30.0/go.mod h1:zgolRw2MQXLPwmic2l/+iHs239L49fhSeICuMhQQXTI= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= diff --git a/pkg/fanal/image/registry/ecr/ecr.go b/pkg/fanal/image/registry/ecr/ecr.go index 90dc4ef69f09..261030d1a584 100644 --- a/pkg/fanal/image/registry/ecr/ecr.go +++ b/pkg/fanal/image/registry/ecr/ecr.go @@ -38,7 +38,7 @@ func getSession(option types.RegistryOptions) (aws.Config, error) { } func (e *ECR) CheckOptions(domain string, option types.RegistryOptions) error { - if !strings.HasSuffix(domain, ecrURLSuffix) && !strings.Contains(domain, ecrURLPartial) { + if !strings.HasSuffix(domain, ecrURLSuffix) && !strings.Contains(domain, ecrURLPartial) { return xerrors.Errorf("ECR : %w", types.InvalidURLPattern) } diff --git a/pkg/flag/kubernetes_flags.go b/pkg/flag/kubernetes_flags.go index b88d14cb8b9e..2683fa07b13a 100644 --- a/pkg/flag/kubernetes_flags.go +++ b/pkg/flag/kubernetes_flags.go @@ -39,7 +39,7 @@ var ( NodeCollectorImageRef = Flag[string]{ Name: "node-collector-imageref", ConfigName: "kubernetes.node-collector.imageref", - Default: "ghcr.io/aquasecurity/node-collector:0.0.9", + Default: "ghcr.io/aquasecurity/node-collector:0.2.1", Usage: "indicate the image reference for the node-collector scan job", } ExcludeOwned = Flag[bool]{ From 65b8a40d0daa9bba904ae8b4667a7d12a8017750 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Mon, 20 May 2024 10:35:34 +0400 Subject: [PATCH 081/352] chore(deps): bump golangci-lint to v1.58.2 (#6719) Signed-off-by: knqyf263 --- .github/workflows/test.yaml | 2 +- internal/testutil/gzip.go | 6 +++--- magefiles/magefile.go | 2 +- pkg/cloud/aws/scanner/scanner.go | 2 +- pkg/dependency/parser/c/conan/parse.go | 2 +- pkg/iac/scanners/azure/functions/max.go | 8 ++++---- pkg/iac/scanners/azure/functions/min.go | 8 ++++---- .../scanners/kubernetes/parser/manifest_node.go | 16 ++++++++-------- pkg/plugin/index.go | 2 +- 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b42b2b517a38..59f27efc1bd5 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -46,7 +46,7 @@ jobs: id: lint uses: golangci/golangci-lint-action@v6.0.1 with: - version: v1.57 + version: v1.58 args: --verbose --out-format=line-number if: matrix.operating-system == 'ubuntu-latest' diff --git a/internal/testutil/gzip.go b/internal/testutil/gzip.go index 6f417e326ab6..68e97395cb3a 100644 --- a/internal/testutil/gzip.go +++ b/internal/testutil/gzip.go @@ -11,7 +11,7 @@ import ( ) const ( - max = int64(10) << 30 // 10GB + maxSize = int64(10) << 30 // 10GB blockSize = 4096 ) @@ -27,7 +27,7 @@ func DecompressGzip(t *testing.T, src, dst string) { gr, err := gzip.NewReader(f) require.NoError(t, err) - _, err = io.CopyN(w, gr, max) + _, err = io.CopyN(w, gr, maxSize) require.ErrorIs(t, err, io.EOF) } @@ -69,7 +69,7 @@ func DecompressSparseGzip(t *testing.T, src, dst string) { require.NoError(t, err) } written += int64(wn) - if written > max { + if written > maxSize { require.Fail(t, "written size exceeds max") } } diff --git a/magefiles/magefile.go b/magefiles/magefile.go index 6fa5e643b300..2df15c4ed6b0 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -71,7 +71,7 @@ func (Tool) Wire() error { // GolangciLint installs golangci-lint func (t Tool) GolangciLint() error { - const version = "v1.57.2" + const version = "v1.58.2" bin := filepath.Join(GOBIN, "golangci-lint") if exists(bin) && t.matchGolangciLintVersion(bin, version) { return nil diff --git a/pkg/cloud/aws/scanner/scanner.go b/pkg/cloud/aws/scanner/scanner.go index fcbabe0d8558..6ea5f919f24d 100644 --- a/pkg/cloud/aws/scanner/scanner.go +++ b/pkg/cloud/aws/scanner/scanner.go @@ -99,7 +99,7 @@ func (s *AWSScanner) Scan(ctx context.Context, option flag.Options) (scan.Result dataFS, dataPaths, err := misconf.CreateDataFS(option.RegoOptions.DataPaths) if err != nil { - s.logger.Error("Could not load config data", err) + s.logger.Error("Could not load config data", log.Err(err)) } scannerOpts = append(scannerOpts, options.ScannerWithDataDirs(dataPaths...), diff --git a/pkg/dependency/parser/c/conan/parse.go b/pkg/dependency/parser/c/conan/parse.go index 4528da67b778..7661131ec8b9 100644 --- a/pkg/dependency/parser/c/conan/parse.go +++ b/pkg/dependency/parser/c/conan/parse.go @@ -107,7 +107,7 @@ func (p *Parser) parseV2(lock LockFile) ([]ftypes.Package, []ftypes.Dependency, for _, req := range lock.Requires { pkg, err := toPackage(req.Dependency, req.StartLine, req.EndLine) if err != nil { - p.logger.Debug("Creating package entry from requirement failed", err) + p.logger.Debug("Creating package entry from requirement failed", log.Err(err)) continue } diff --git a/pkg/iac/scanners/azure/functions/max.go b/pkg/iac/scanners/azure/functions/max.go index eb0338a4f894..b28ddb436ced 100644 --- a/pkg/iac/scanners/azure/functions/max.go +++ b/pkg/iac/scanners/azure/functions/max.go @@ -21,12 +21,12 @@ func maxInt(args []int) int { return 0 } - max := args[0] + maxN := args[0] for i := 1; i < len(args); i++ { - if args[i] > max { - max = args[i] + if args[i] > maxN { + maxN = args[i] } } - return max + return maxN } diff --git a/pkg/iac/scanners/azure/functions/min.go b/pkg/iac/scanners/azure/functions/min.go index 5147c3bb2769..5c0d63332275 100644 --- a/pkg/iac/scanners/azure/functions/min.go +++ b/pkg/iac/scanners/azure/functions/min.go @@ -21,12 +21,12 @@ func minInt(args []int) int { return 0 } - min := args[0] + minN := args[0] for i := 1; i < len(args); i++ { - if args[i] < min { - min = args[i] + if args[i] < minN { + minN = args[i] } } - return min + return minN } diff --git a/pkg/iac/scanners/kubernetes/parser/manifest_node.go b/pkg/iac/scanners/kubernetes/parser/manifest_node.go index 1f82ca1e3680..17111e70b1e5 100644 --- a/pkg/iac/scanners/kubernetes/parser/manifest_node.go +++ b/pkg/iac/scanners/kubernetes/parser/manifest_node.go @@ -98,19 +98,19 @@ func (r *ManifestNode) UnmarshalYAML(node *yaml.Node) error { func (r *ManifestNode) handleSliceTag(node *yaml.Node) error { var nodes []ManifestNode - max := node.Line + maxLine := node.Line for _, contentNode := range node.Content { newNode := new(ManifestNode) newNode.Path = r.Path if err := contentNode.Decode(newNode); err != nil { return err } - if newNode.EndLine > max { - max = newNode.EndLine + if newNode.EndLine > maxLine { + maxLine = newNode.EndLine } nodes = append(nodes, *newNode) } - r.EndLine = max + r.EndLine = maxLine r.Value = nodes return nil } @@ -118,7 +118,7 @@ func (r *ManifestNode) handleSliceTag(node *yaml.Node) error { func (r *ManifestNode) handleMapTag(node *yaml.Node) error { output := make(map[string]ManifestNode) var key string - max := node.Line + maxLine := node.Line for i, contentNode := range node.Content { if i == 0 || i%2 == 0 { key = contentNode.Value @@ -129,12 +129,12 @@ func (r *ManifestNode) handleMapTag(node *yaml.Node) error { return err } output[key] = *newNode - if newNode.EndLine > max { - max = newNode.EndLine + if newNode.EndLine > maxLine { + maxLine = newNode.EndLine } } } - r.EndLine = max + r.EndLine = maxLine r.Value = output return nil } diff --git a/pkg/plugin/index.go b/pkg/plugin/index.go index 980d4ef1e41f..d0df12e2036f 100644 --- a/pkg/plugin/index.go +++ b/pkg/plugin/index.go @@ -83,7 +83,7 @@ func (m *Manager) tryIndex(ctx context.Context, name string) string { m.logger.WarnContext(ctx, "The plugin index is not found. Please run 'trivy plugin update' to download the index.") return name } else if err != nil { - m.logger.ErrorContext(ctx, "Unable to load the plugin index: %w", err) + m.logger.ErrorContext(ctx, "Unable to load the plugin index", log.Err(err)) return name } From bbaf5952bc8059c537401bd4364e019eecd16c9a Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 20 May 2024 09:12:43 +0200 Subject: [PATCH 082/352] ci(deps): enable `require-error` rule from `testifylint` linter (#6718) Signed-off-by: Matthieu MOREL --- .golangci.yaml | 3 +- integration/aws_cloud_test.go | 2 +- integration/client_server_test.go | 8 ++--- integration/docker_engine_test.go | 6 ++-- integration/integration_test.go | 2 +- integration/repo_test.go | 5 ++- integration/standalone_tar_test.go | 6 ++-- pkg/cache/remote_test.go | 6 ++-- pkg/cloud/aws/commands/run_test.go | 4 +-- pkg/db/db_test.go | 4 +-- .../parser/conda/environment/parse_test.go | 2 +- .../parser/conda/meta/parse_test.go | 2 +- .../parser/golang/binary/parse_test.go | 2 +- .../parser/gradle/lockfile/parse_test.go | 3 +- pkg/dependency/parser/hex/mix/parse_test.go | 3 +- .../parser/nuget/config/parse_test.go | 2 +- .../parser/nuget/packagesprops/parse_test.go | 2 +- .../parser/python/poetry/parse_test.go | 2 +- .../parser/rust/binary/parse_test.go | 2 +- .../parser/swift/swift/parse_test.go | 10 +++--- pkg/detector/library/driver_test.go | 2 +- pkg/detector/ospkg/alma/alma_test.go | 5 +-- pkg/detector/ospkg/alpine/alpine_test.go | 2 +- pkg/detector/ospkg/amazon/amazon_test.go | 5 +-- .../ospkg/chainguard/chainguard_test.go | 2 +- pkg/detector/ospkg/debian/debian_test.go | 5 +-- pkg/detector/ospkg/mariner/mariner_test.go | 2 +- pkg/detector/ospkg/oracle/oracle_test.go | 5 +-- pkg/detector/ospkg/photon/photon_test.go | 5 +-- pkg/detector/ospkg/rocky/rocky_test.go | 5 +-- pkg/detector/ospkg/suse/suse_test.go | 5 +-- pkg/detector/ospkg/ubuntu/ubuntu_test.go | 5 +-- pkg/detector/ospkg/wolfi/wolfi_test.go | 2 +- .../analyzer/buildinfo/dockerfile_test.go | 2 +- .../imgconf/dockerfile/dockerfile_test.go | 2 +- .../analyzer/imgconf/secret/secret_test.go | 2 +- .../analyzer/language/conda/meta/meta_test.go | 2 +- .../language/dart/pub/pubspec_test.go | 2 +- .../language/dotnet/deps/deps_test.go | 2 +- .../language/dotnet/nuget/nuget_test.go | 2 +- .../packagesprops/packagesprops_test.go | 2 +- .../analyzer/language/elixir/mix/mix_test.go | 4 +-- .../language/golang/binary/binary_test.go | 2 +- .../analyzer/language/golang/mod/mod_test.go | 4 +-- .../language/java/gradle/lockfile_test.go | 2 +- .../analyzer/language/java/jar/jar_test.go | 11 +++--- .../analyzer/language/java/pom/pom_test.go | 2 +- .../analyzer/language/julia/pkg/pkg_test.go | 2 +- .../analyzer/language/nodejs/npm/npm_test.go | 2 +- .../analyzer/language/nodejs/pkg/pkg_test.go | 2 +- .../language/nodejs/pnpm/pnpm_test.go | 2 +- .../language/nodejs/yarn/yarn_test.go | 2 +- .../language/php/composer/composer_test.go | 2 +- .../python/packaging/packaging_test.go | 2 +- .../analyzer/language/python/pip/pip_test.go | 2 +- .../language/python/poetry/poetry_test.go | 2 +- .../language/ruby/gemspec/gemspec_test.go | 2 +- .../language/rust/binary/binary_test.go | 2 +- .../language/rust/cargo/cargo_test.go | 2 +- .../swift/cocoapods/cocoapods_test.go | 2 +- .../language/swift/swift/swift_test.go | 2 +- pkg/fanal/analyzer/os/alpine/alpine_test.go | 5 +-- pkg/fanal/analyzer/os/release/release_test.go | 4 +-- pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go | 11 +++--- pkg/fanal/analyzer/pkg/rpm/rpm_test.go | 4 +-- pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go | 5 +-- pkg/fanal/analyzer/repo/apk/apk_test.go | 5 +-- pkg/fanal/artifact/repo/git_test.go | 2 +- pkg/fanal/cache/key_test.go | 5 +-- pkg/fanal/cache/redis_test.go | 12 +++---- pkg/fanal/image/daemon/image_test.go | 4 +-- pkg/fanal/image/daemon/podman_test.go | 4 +-- pkg/fanal/image/image_test.go | 12 +++---- pkg/fanal/image/oci_test.go | 5 +-- pkg/fanal/image/registry/azure/azure_test.go | 3 +- pkg/fanal/test/integration/containerd_test.go | 4 +-- pkg/fanal/walker/fs_test.go | 5 +-- pkg/fanal/walker/tar_test.go | 2 +- pkg/flag/kubernetes_flags_test.go | 3 +- pkg/flag/report_flags_test.go | 6 ++-- pkg/iac/detection/detect_test.go | 5 +-- pkg/iac/rego/load_test.go | 6 ++-- pkg/iac/rego/metadata_test.go | 2 +- pkg/iac/rego/scanner_test.go | 4 +-- .../terraform/executor/executor_test.go | 6 ++-- .../scanners/terraform/parser/parser_test.go | 34 +++++++++---------- .../parser/resolvers/registry_test.go | 3 +- pkg/iac/terraform/reference_test.go | 2 +- pkg/k8s/scanner/scanner_test.go | 7 ++-- pkg/plugin/manager_test.go | 8 ++--- pkg/plugin/manager_unix_test.go | 2 +- pkg/policy/policy_test.go | 4 +-- pkg/purl/purl_test.go | 4 +-- pkg/rekor/client_test.go | 2 +- pkg/remote/remote_test.go | 2 +- pkg/report/github/github_test.go | 5 +-- pkg/report/json_test.go | 5 +-- pkg/report/sarif_test.go | 5 +-- pkg/report/table/table_test.go | 6 ++-- pkg/report/template_test.go | 2 +- pkg/rpc/server/listen_test.go | 2 +- pkg/rpc/server/server_test.go | 8 ++--- pkg/utils/fsutils/fs_test.go | 2 +- 103 files changed, 226 insertions(+), 196 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 7895c84580aa..e22dc0378556 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -70,7 +70,6 @@ linters-settings: enable-all: true disable: - float-compare - - require-error linters: disable-all: true @@ -99,7 +98,7 @@ run: issues: exclude-files: - - ".*_mock.go$" + - "mock_*.go$" - "examples/*" exclude-dirs: - "pkg/iac/scanners/terraform/parser/funcs" # copies of Terraform functions diff --git a/integration/aws_cloud_test.go b/integration/aws_cloud_test.go index 3290d5daeee1..481ce6ca0cf6 100644 --- a/integration/aws_cloud_test.go +++ b/integration/aws_cloud_test.go @@ -71,7 +71,7 @@ func TestAwsCommandRun(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } - assert.NoError(t, err) + require.NoError(t, err) }) } diff --git a/integration/client_server_test.go b/integration/client_server_test.go index f6b25c6cc3ef..8a5b62f05054 100644 --- a/integration/client_server_test.go +++ b/integration/client_server_test.go @@ -5,16 +5,16 @@ package integration import ( "context" "fmt" - "github.com/aquasecurity/trivy/pkg/types" "os" "path/filepath" "strings" "testing" "time" + "github.com/aquasecurity/trivy/pkg/types" + dockercontainer "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" @@ -542,7 +542,7 @@ func setup(t *testing.T, options setupOptions) (string, string) { t.Setenv("XDG_DATA_HOME", cacheDir) port, err := getFreePort() - assert.NoError(t, err) + require.NoError(t, err) addr := fmt.Sprintf("localhost:%d", port) go func() { @@ -554,7 +554,7 @@ func setup(t *testing.T, options setupOptions) (string, string) { ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) err = waitPort(ctx, addr) - assert.NoError(t, err) + require.NoError(t, err) return addr, cacheDir } diff --git a/integration/docker_engine_test.go b/integration/docker_engine_test.go index 35122b51c90a..dd82b5f34b51 100644 --- a/integration/docker_engine_test.go +++ b/integration/docker_engine_test.go @@ -5,15 +5,15 @@ package integration import ( "context" - "github.com/aquasecurity/trivy/pkg/types" "io" "os" "strings" "testing" + "github.com/aquasecurity/trivy/pkg/types" + api "github.com/docker/docker/api/types" "github.com/docker/docker/client" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -298,7 +298,7 @@ func TestDockerEngine(t *testing.T) { if len(tt.ignoreIDs) != 0 { trivyIgnore := ".trivyignore" err = os.WriteFile(trivyIgnore, []byte(strings.Join(tt.ignoreIDs, "\n")), 0444) - assert.NoError(t, err, "failed to write .trivyignore") + require.NoError(t, err, "failed to write .trivyignore") defer os.Remove(trivyIgnore) } osArgs = append(osArgs, tt.input) diff --git a/integration/integration_test.go b/integration/integration_test.go index 80f0d96119fb..aeb91dfe783e 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -289,7 +289,7 @@ func compareSPDXJson(t *testing.T, wantFile, gotFile string) { SPDXVersion, ok := strings.CutPrefix(want.SPDXVersion, "SPDX-") assert.True(t, ok) - assert.NoError(t, spdxlib.ValidateDocument(got)) + require.NoError(t, spdxlib.ValidateDocument(got)) // Validate SPDX output against the JSON schema validateReport(t, fmt.Sprintf(SPDXSchema, SPDXVersion), got) diff --git a/integration/repo_test.go b/integration/repo_test.go index 3bc94416710a..00665f17ab8d 100644 --- a/integration/repo_test.go +++ b/integration/repo_test.go @@ -8,10 +8,9 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" - "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/require" ) // TestRepository tests `trivy repo` with the local code repositories @@ -460,7 +459,7 @@ func TestRepository(t *testing.T) { if len(tt.args.ignoreIDs) != 0 { trivyIgnore := ".trivyignore" err := os.WriteFile(trivyIgnore, []byte(strings.Join(tt.args.ignoreIDs, "\n")), 0444) - assert.NoError(t, err, "failed to write .trivyignore") + require.NoError(t, err, "failed to write .trivyignore") defer os.Remove(trivyIgnore) } diff --git a/integration/standalone_tar_test.go b/integration/standalone_tar_test.go index 67cd869ebf1a..5b9d73c5d9ff 100644 --- a/integration/standalone_tar_test.go +++ b/integration/standalone_tar_test.go @@ -3,13 +3,13 @@ package integration import ( - "github.com/aquasecurity/trivy/pkg/types" "os" "path/filepath" "strings" "testing" - "github.com/stretchr/testify/assert" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/require" ) @@ -384,7 +384,7 @@ func TestTar(t *testing.T) { if len(tt.args.IgnoreIDs) != 0 { trivyIgnore := ".trivyignore" err := os.WriteFile(trivyIgnore, []byte(strings.Join(tt.args.IgnoreIDs, "\n")), 0444) - assert.NoError(t, err, "failed to write .trivyignore") + require.NoError(t, err, "failed to write .trivyignore") defer os.Remove(trivyIgnore) } if tt.args.Input != "" { diff --git a/pkg/cache/remote_test.go b/pkg/cache/remote_test.go index 25ca003910b9..a7a8e27dbaa9 100644 --- a/pkg/cache/remote_test.go +++ b/pkg/cache/remote_test.go @@ -150,7 +150,7 @@ func TestRemoteCache_PutArtifact(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { - assert.NoError(t, err, tt.name) + require.NoError(t, err, tt.name) } }) } @@ -211,7 +211,7 @@ func TestRemoteCache_PutBlob(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { - assert.NoError(t, err, tt.name) + require.NoError(t, err, tt.name) } }) } @@ -339,7 +339,7 @@ func TestRemoteCache_PutArtifactInsecure(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err, tt.name) + require.NoError(t, err, tt.name) }) } } diff --git a/pkg/cloud/aws/commands/run_test.go b/pkg/cloud/aws/commands/run_test.go index 05c88560d1bf..84528ab4ae6d 100644 --- a/pkg/cloud/aws/commands/run_test.go +++ b/pkg/cloud/aws/commands/run_test.go @@ -1275,10 +1275,10 @@ Summary Report for compliance: my-custom-spec err := Run(ctx, test.options) if test.expectErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, test.want, output.String()) }) } diff --git a/pkg/db/db_test.go b/pkg/db/db_test.go index ef15370879a6..e627a684a696 100644 --- a/pkg/db/db_test.go +++ b/pkg/db/db_test.go @@ -161,7 +161,7 @@ func TestClient_NeedsUpdate(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr, tt.name) default: - assert.NoError(t, err, tt.name) + require.NoError(t, err, tt.name) } assert.Equal(t, tt.want, needsUpdate) @@ -232,7 +232,7 @@ func TestClient_Download(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) meta := metadata.NewClient(cacheDir) got, err := meta.Get() diff --git a/pkg/dependency/parser/conda/environment/parse_test.go b/pkg/dependency/parser/conda/environment/parse_test.go index 6127054b2552..6283e8e135d6 100644 --- a/pkg/dependency/parser/conda/environment/parse_test.go +++ b/pkg/dependency/parser/conda/environment/parse_test.go @@ -220,7 +220,7 @@ func TestParse(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/dependency/parser/conda/meta/parse_test.go b/pkg/dependency/parser/conda/meta/parse_test.go index b2a4eafd6b08..8363b57e53bb 100644 --- a/pkg/dependency/parser/conda/meta/parse_test.go +++ b/pkg/dependency/parser/conda/meta/parse_test.go @@ -63,7 +63,7 @@ func TestParse(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/dependency/parser/golang/binary/parse_test.go b/pkg/dependency/parser/golang/binary/parse_test.go index 95e620d412bd..47e5ec0db7c6 100644 --- a/pkg/dependency/parser/golang/binary/parse_test.go +++ b/pkg/dependency/parser/golang/binary/parse_test.go @@ -144,7 +144,7 @@ func TestParse(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/dependency/parser/gradle/lockfile/parse_test.go b/pkg/dependency/parser/gradle/lockfile/parse_test.go index fa73ea81122e..beed1ad675cc 100644 --- a/pkg/dependency/parser/gradle/lockfile/parse_test.go +++ b/pkg/dependency/parser/gradle/lockfile/parse_test.go @@ -7,6 +7,7 @@ import ( ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestParser_Parse(t *testing.T) { @@ -65,7 +66,7 @@ func TestParser_Parse(t *testing.T) { t.Run(tt.name, func(t *testing.T) { parser := NewParser() f, err := os.Open(tt.inputFile) - assert.NoError(t, err) + require.NoError(t, err) pkgs, _, _ := parser.Parse(f) sort.Sort(ftypes.Packages(pkgs)) diff --git a/pkg/dependency/parser/hex/mix/parse_test.go b/pkg/dependency/parser/hex/mix/parse_test.go index a46f35f8d158..d92255614c19 100644 --- a/pkg/dependency/parser/hex/mix/parse_test.go +++ b/pkg/dependency/parser/hex/mix/parse_test.go @@ -7,6 +7,7 @@ import ( ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestParser_Parse(t *testing.T) { @@ -76,7 +77,7 @@ func TestParser_Parse(t *testing.T) { t.Run(tt.name, func(t *testing.T) { parser := NewParser() f, err := os.Open(tt.inputFile) - assert.NoError(t, err) + require.NoError(t, err) pkgs, _, _ := parser.Parse(f) sort.Sort(ftypes.Packages(pkgs)) diff --git a/pkg/dependency/parser/nuget/config/parse_test.go b/pkg/dependency/parser/nuget/config/parse_test.go index 5ea128c2861a..f7e6d16a0e10 100644 --- a/pkg/dependency/parser/nuget/config/parse_test.go +++ b/pkg/dependency/parser/nuget/config/parse_test.go @@ -51,7 +51,7 @@ func TestParse(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.ElementsMatch(t, tt.want, got) }) } diff --git a/pkg/dependency/parser/nuget/packagesprops/parse_test.go b/pkg/dependency/parser/nuget/packagesprops/parse_test.go index a33dfb76611e..16e4e59816d9 100644 --- a/pkg/dependency/parser/nuget/packagesprops/parse_test.go +++ b/pkg/dependency/parser/nuget/packagesprops/parse_test.go @@ -75,7 +75,7 @@ func TestParse(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/dependency/parser/python/poetry/parse_test.go b/pkg/dependency/parser/python/poetry/parse_test.go index 5ce44ddcea8f..7f42975713de 100644 --- a/pkg/dependency/parser/python/poetry/parse_test.go +++ b/pkg/dependency/parser/python/poetry/parse_test.go @@ -122,7 +122,7 @@ func TestParseDependency(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/dependency/parser/rust/binary/parse_test.go b/pkg/dependency/parser/rust/binary/parse_test.go index 88da862c860a..1c1eb1c586f2 100644 --- a/pkg/dependency/parser/rust/binary/parse_test.go +++ b/pkg/dependency/parser/rust/binary/parse_test.go @@ -82,7 +82,7 @@ func TestParse(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) assert.Equal(t, tt.wantDeps, gotDeps) }) diff --git a/pkg/dependency/parser/swift/swift/parse_test.go b/pkg/dependency/parser/swift/swift/parse_test.go index cd90d26fc6e4..1bedadea29ed 100644 --- a/pkg/dependency/parser/swift/swift/parse_test.go +++ b/pkg/dependency/parser/swift/swift/parse_test.go @@ -1,10 +1,12 @@ package swift import ( - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/stretchr/testify/assert" "os" "testing" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestParser_Parse(t *testing.T) { @@ -90,10 +92,10 @@ func TestParser_Parse(t *testing.T) { t.Run(tt.name, func(t *testing.T) { parser := NewParser() f, err := os.Open(tt.inputFile) - assert.NoError(t, err) + require.NoError(t, err) gotPkgs, _, err := parser.Parse(f) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, gotPkgs) }) } diff --git a/pkg/detector/library/driver_test.go b/pkg/detector/library/driver_test.go index b7b94153c6a1..e6722e841e8e 100644 --- a/pkg/detector/library/driver_test.go +++ b/pkg/detector/library/driver_test.go @@ -200,7 +200,7 @@ func TestDriver_Detect(t *testing.T) { } // Compare - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/detector/ospkg/alma/alma_test.go b/pkg/detector/ospkg/alma/alma_test.go index cd7a318d8f10..ea012609bd94 100644 --- a/pkg/detector/ospkg/alma/alma_test.go +++ b/pkg/detector/ospkg/alma/alma_test.go @@ -2,10 +2,11 @@ package alma_test import ( "context" - "github.com/aquasecurity/trivy/pkg/clock" "testing" "time" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" @@ -168,7 +169,7 @@ func TestScanner_Detect(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/detector/ospkg/alpine/alpine_test.go b/pkg/detector/ospkg/alpine/alpine_test.go index eba7258e1392..4556592ad83f 100644 --- a/pkg/detector/ospkg/alpine/alpine_test.go +++ b/pkg/detector/ospkg/alpine/alpine_test.go @@ -261,7 +261,7 @@ func TestScanner_Detect(t *testing.T) { sort.Slice(got, func(i, j int) bool { return got[i].VulnerabilityID < got[j].VulnerabilityID }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/detector/ospkg/amazon/amazon_test.go b/pkg/detector/ospkg/amazon/amazon_test.go index 83100e98c72c..a3f60fb3578e 100644 --- a/pkg/detector/ospkg/amazon/amazon_test.go +++ b/pkg/detector/ospkg/amazon/amazon_test.go @@ -2,10 +2,11 @@ package amazon_test import ( "context" - "github.com/aquasecurity/trivy/pkg/clock" "testing" "time" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" @@ -183,7 +184,7 @@ func TestScanner_Detect(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/detector/ospkg/chainguard/chainguard_test.go b/pkg/detector/ospkg/chainguard/chainguard_test.go index 27758c191636..af6e81afcc84 100644 --- a/pkg/detector/ospkg/chainguard/chainguard_test.go +++ b/pkg/detector/ospkg/chainguard/chainguard_test.go @@ -204,7 +204,7 @@ func TestScanner_Detect(t *testing.T) { sort.Slice(got, func(i, j int) bool { return got[i].VulnerabilityID < got[j].VulnerabilityID }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/detector/ospkg/debian/debian_test.go b/pkg/detector/ospkg/debian/debian_test.go index 2f5c2b3595ab..602afe5d6699 100644 --- a/pkg/detector/ospkg/debian/debian_test.go +++ b/pkg/detector/ospkg/debian/debian_test.go @@ -2,11 +2,12 @@ package debian_test import ( "context" - "github.com/aquasecurity/trivy/pkg/clock" "sort" "testing" "time" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" @@ -125,7 +126,7 @@ func TestScanner_Detect(t *testing.T) { sort.Slice(got, func(i, j int) bool { return got[i].VulnerabilityID < got[j].VulnerabilityID }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/detector/ospkg/mariner/mariner_test.go b/pkg/detector/ospkg/mariner/mariner_test.go index 199ab1dd996a..7c7410f28234 100644 --- a/pkg/detector/ospkg/mariner/mariner_test.go +++ b/pkg/detector/ospkg/mariner/mariner_test.go @@ -143,7 +143,7 @@ func TestScanner_Detect(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/detector/ospkg/oracle/oracle_test.go b/pkg/detector/ospkg/oracle/oracle_test.go index 8dd3dfc8900f..0116effe9325 100644 --- a/pkg/detector/ospkg/oracle/oracle_test.go +++ b/pkg/detector/ospkg/oracle/oracle_test.go @@ -2,10 +2,11 @@ package oracle import ( "context" - "github.com/aquasecurity/trivy/pkg/clock" "testing" "time" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" @@ -256,7 +257,7 @@ func TestScanner_Detect(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } else { - assert.NoError(t, err) + require.NoError(t, err) } assert.Equal(t, tt.want, got) diff --git a/pkg/detector/ospkg/photon/photon_test.go b/pkg/detector/ospkg/photon/photon_test.go index a85957f6c516..6d45edeea1ee 100644 --- a/pkg/detector/ospkg/photon/photon_test.go +++ b/pkg/detector/ospkg/photon/photon_test.go @@ -2,10 +2,11 @@ package photon_test import ( "context" - "github.com/aquasecurity/trivy/pkg/clock" "testing" "time" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" @@ -100,7 +101,7 @@ func TestScanner_Detect(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/detector/ospkg/rocky/rocky_test.go b/pkg/detector/ospkg/rocky/rocky_test.go index 91250aa73776..799eb8bc6fc0 100644 --- a/pkg/detector/ospkg/rocky/rocky_test.go +++ b/pkg/detector/ospkg/rocky/rocky_test.go @@ -2,10 +2,11 @@ package rocky_test import ( "context" - "github.com/aquasecurity/trivy/pkg/clock" "testing" "time" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" @@ -128,7 +129,7 @@ func TestScanner_Detect(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/detector/ospkg/suse/suse_test.go b/pkg/detector/ospkg/suse/suse_test.go index eb3e7d9673b4..793127f1b07d 100644 --- a/pkg/detector/ospkg/suse/suse_test.go +++ b/pkg/detector/ospkg/suse/suse_test.go @@ -2,10 +2,11 @@ package suse_test import ( "context" - "github.com/aquasecurity/trivy/pkg/clock" "testing" "time" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" @@ -103,7 +104,7 @@ func TestScanner_Detect(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/detector/ospkg/ubuntu/ubuntu_test.go b/pkg/detector/ospkg/ubuntu/ubuntu_test.go index a95c48bb24ee..eb2048532dbe 100644 --- a/pkg/detector/ospkg/ubuntu/ubuntu_test.go +++ b/pkg/detector/ospkg/ubuntu/ubuntu_test.go @@ -2,11 +2,12 @@ package ubuntu_test import ( "context" - "github.com/aquasecurity/trivy/pkg/clock" "sort" "testing" "time" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" @@ -188,7 +189,7 @@ func TestScanner_Detect(t *testing.T) { sort.Slice(got, func(i, j int) bool { return got[i].VulnerabilityID < got[j].VulnerabilityID }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/detector/ospkg/wolfi/wolfi_test.go b/pkg/detector/ospkg/wolfi/wolfi_test.go index 2ef5fb664f55..8df2c2ab0870 100644 --- a/pkg/detector/ospkg/wolfi/wolfi_test.go +++ b/pkg/detector/ospkg/wolfi/wolfi_test.go @@ -204,7 +204,7 @@ func TestScanner_Detect(t *testing.T) { sort.Slice(got, func(i, j int) bool { return got[i].VulnerabilityID < got[j].VulnerabilityID }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/buildinfo/dockerfile_test.go b/pkg/fanal/analyzer/buildinfo/dockerfile_test.go index 600425d6c930..93d1168c0025 100644 --- a/pkg/fanal/analyzer/buildinfo/dockerfile_test.go +++ b/pkg/fanal/analyzer/buildinfo/dockerfile_test.go @@ -62,7 +62,7 @@ func Test_dockerfileAnalyzer_Analyze(t *testing.T) { assert.Equal(t, tt.wantErr, err.Error()) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go index 273366f8856e..1ee81016ac8c 100644 --- a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go +++ b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile_test.go @@ -291,7 +291,7 @@ func Test_historyAnalyzer_Analyze(t *testing.T) { require.NoError(t, err) got, err := a.Analyze(context.Background(), tt.input) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } if got != nil && got.Misconfiguration != nil { diff --git a/pkg/fanal/analyzer/imgconf/secret/secret_test.go b/pkg/fanal/analyzer/imgconf/secret/secret_test.go index 8c28fd9c91ce..66f958dcd9cf 100644 --- a/pkg/fanal/analyzer/imgconf/secret/secret_test.go +++ b/pkg/fanal/analyzer/imgconf/secret/secret_test.go @@ -99,7 +99,7 @@ func Test_secretAnalyzer_Analyze(t *testing.T) { Config: tt.config, }) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } require.NoError(t, err) diff --git a/pkg/fanal/analyzer/language/conda/meta/meta_test.go b/pkg/fanal/analyzer/language/conda/meta/meta_test.go index 3cb4f18e4b2b..ef98bdc1e105 100644 --- a/pkg/fanal/analyzer/language/conda/meta/meta_test.go +++ b/pkg/fanal/analyzer/language/conda/meta/meta_test.go @@ -67,7 +67,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go b/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go index b464ddf5cd57..ce961fe589db 100644 --- a/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec_test.go @@ -137,7 +137,7 @@ func Test_pubSpecLockAnalyzer_Analyze(t *testing.T) { FS: os.DirFS(tt.dir), }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go b/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go index 4e8cc0ba212b..c91d4467320e 100644 --- a/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go +++ b/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go @@ -68,7 +68,7 @@ func Test_depsLibraryAnalyzer_Analyze(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go index 07b5023a392d..52e10ad5622d 100644 --- a/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go @@ -201,7 +201,7 @@ func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { FS: os.DirFS(tt.dir), }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops_test.go b/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops_test.go index ee398028de6e..4808f3e89adf 100644 --- a/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops_test.go +++ b/pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops_test.go @@ -91,7 +91,7 @@ func Test_packagesPropsAnalyzer_Analyze(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/elixir/mix/mix_test.go b/pkg/fanal/analyzer/language/elixir/mix/mix_test.go index d375014fd97a..39e44a128db5 100644 --- a/pkg/fanal/analyzer/language/elixir/mix/mix_test.go +++ b/pkg/fanal/analyzer/language/elixir/mix/mix_test.go @@ -54,7 +54,7 @@ func Test_mixLockAnalyzer_Analyze(t *testing.T) { require.NoError(t, err) defer func() { err = f.Close() - assert.NoError(t, err) + require.NoError(t, err) }() a := mixLockAnalyzer{} @@ -63,7 +63,7 @@ func Test_mixLockAnalyzer_Analyze(t *testing.T) { Content: f, }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/golang/binary/binary_test.go b/pkg/fanal/analyzer/language/golang/binary/binary_test.go index 7c2b678ac0a5..70256113cfd3 100644 --- a/pkg/fanal/analyzer/language/golang/binary/binary_test.go +++ b/pkg/fanal/analyzer/language/golang/binary/binary_test.go @@ -78,7 +78,7 @@ func Test_gobinaryLibraryAnalyzer_Analyze(t *testing.T) { Content: f, }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/golang/mod/mod_test.go b/pkg/fanal/analyzer/language/golang/mod/mod_test.go index 02ed84c91ed7..5cf006430aca 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod_test.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod_test.go @@ -231,13 +231,13 @@ func Test_gomodAnalyzer_Analyze(t *testing.T) { got, err := a.PostAnalyze(ctx, analyzer.PostAnalysisInput{ FS: mfs, }) - assert.NoError(t, err) + require.NoError(t, err) if len(got.Applications) > 0 { sort.Sort(got.Applications[0].Packages) sort.Sort(tt.want.Applications[0].Packages) } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go b/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go index e226b542f7d1..50199bcac15d 100644 --- a/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go +++ b/pkg/fanal/analyzer/language/java/gradle/lockfile_test.go @@ -122,7 +122,7 @@ func Test_gradleLockAnalyzer_Analyze(t *testing.T) { FS: os.DirFS(tt.dir), }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/java/jar/jar_test.go b/pkg/fanal/analyzer/language/java/jar/jar_test.go index d1cf6ab1f491..304f5a33f861 100644 --- a/pkg/fanal/analyzer/language/java/jar/jar_test.go +++ b/pkg/fanal/analyzer/language/java/jar/jar_test.go @@ -2,12 +2,13 @@ package jar import ( "context" - "github.com/google/go-containerregistry/pkg/name" - "github.com/stretchr/testify/require" "os" "path/filepath" "testing" + "github.com/google/go-containerregistry/pkg/name" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -141,16 +142,16 @@ func Test_javaLibraryAnalyzer_Analyze(t *testing.T) { mfs := mapfs.New() err = mfs.MkdirAll(filepath.Dir(tt.inputFile), os.ModePerm) - assert.NoError(t, err) + require.NoError(t, err) err = mfs.WriteFile(tt.inputFile, tt.inputFile) - assert.NoError(t, err) + require.NoError(t, err) got, err := a.PostAnalyze(ctx, analyzer.PostAnalysisInput{ FS: mfs, Options: analyzer.AnalysisOptions{FileChecksum: tt.includeChecksum}, }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/java/pom/pom_test.go b/pkg/fanal/analyzer/language/java/pom/pom_test.go index 8a169b929151..b3aaf43a82cf 100644 --- a/pkg/fanal/analyzer/language/java/pom/pom_test.go +++ b/pkg/fanal/analyzer/language/java/pom/pom_test.go @@ -184,7 +184,7 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go b/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go index 8e96e0574d93..d5562ef7e18c 100644 --- a/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go +++ b/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go @@ -202,7 +202,7 @@ func Test_juliaAnalyzer_Analyze(t *testing.T) { FS: os.DirFS(tt.dir), }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go b/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go index 010a6207502d..695fa80e040d 100644 --- a/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go +++ b/pkg/fanal/analyzer/language/nodejs/npm/npm_test.go @@ -239,7 +239,7 @@ func Test_npmLibraryAnalyzer_Analyze(t *testing.T) { FS: os.DirFS(tt.dir), }) - assert.NoError(t, err) + require.NoError(t, err) if len(got.Applications) > 0 { sort.Sort(got.Applications[0].Packages) } diff --git a/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go b/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go index c2528c92f974..62eebb5a5f09 100644 --- a/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go +++ b/pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go @@ -94,7 +94,7 @@ func Test_nodePkgLibraryAnalyzer_Analyze(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go index 130482746a20..4fe9fd75874e 100644 --- a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go @@ -59,7 +59,7 @@ func Test_pnpmPkgLibraryAnalyzer_Analyze(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go index 7aae5d5181ae..201629335b7a 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go @@ -771,7 +771,7 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { FS: os.DirFS(tt.dir), }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/php/composer/composer_test.go b/pkg/fanal/analyzer/language/php/composer/composer_test.go index cc5d6908fc84..ea963d94dcf2 100644 --- a/pkg/fanal/analyzer/language/php/composer/composer_test.go +++ b/pkg/fanal/analyzer/language/php/composer/composer_test.go @@ -163,7 +163,7 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { FS: os.DirFS(tt.dir), }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go index 69eec5eba5f0..c3a89ad0cd19 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go @@ -172,7 +172,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/python/pip/pip_test.go b/pkg/fanal/analyzer/language/python/pip/pip_test.go index fc86c5a4fad8..c97714506a27 100644 --- a/pkg/fanal/analyzer/language/python/pip/pip_test.go +++ b/pkg/fanal/analyzer/language/python/pip/pip_test.go @@ -69,7 +69,7 @@ func Test_pipAnalyzer_Analyze(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/python/poetry/poetry_test.go b/pkg/fanal/analyzer/language/python/poetry/poetry_test.go index b0e38a51f5e0..08de4d92d4ef 100644 --- a/pkg/fanal/analyzer/language/python/poetry/poetry_test.go +++ b/pkg/fanal/analyzer/language/python/poetry/poetry_test.go @@ -192,7 +192,7 @@ func Test_poetryLibraryAnalyzer_Analyze(t *testing.T) { FS: os.DirFS(tt.dir), }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go b/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go index 69aff2a3ed61..76c91476dd02 100644 --- a/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go +++ b/pkg/fanal/analyzer/language/ruby/gemspec/gemspec_test.go @@ -96,7 +96,7 @@ func Test_gemspecLibraryAnalyzer_Analyze(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/rust/binary/binary_test.go b/pkg/fanal/analyzer/language/rust/binary/binary_test.go index 27bfce04eea0..1144fdb84468 100644 --- a/pkg/fanal/analyzer/language/rust/binary/binary_test.go +++ b/pkg/fanal/analyzer/language/rust/binary/binary_test.go @@ -69,7 +69,7 @@ func Test_rustBinaryLibraryAnalyzer_Analyze(t *testing.T) { Content: f, }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go b/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go index 52c5820faa04..e146ccb6c481 100644 --- a/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo_test.go @@ -550,7 +550,7 @@ func Test_cargoAnalyzer_Analyze(t *testing.T) { FS: os.DirFS(tt.dir), }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go b/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go index 5dfc9be18d56..79e2a84078e7 100644 --- a/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go +++ b/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go @@ -91,7 +91,7 @@ func Test_cocoaPodsLockAnalyzer_Analyze(t *testing.T) { } } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/swift/swift/swift_test.go b/pkg/fanal/analyzer/language/swift/swift/swift_test.go index 73066aa69948..244a96e076b3 100644 --- a/pkg/fanal/analyzer/language/swift/swift/swift_test.go +++ b/pkg/fanal/analyzer/language/swift/swift/swift_test.go @@ -83,7 +83,7 @@ func Test_swiftLockAnalyzer_Analyze(t *testing.T) { Content: f, }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/os/alpine/alpine_test.go b/pkg/fanal/analyzer/os/alpine/alpine_test.go index 330695eaf117..437c7ab77688 100644 --- a/pkg/fanal/analyzer/os/alpine/alpine_test.go +++ b/pkg/fanal/analyzer/os/alpine/alpine_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -39,10 +40,10 @@ func TestAlpineReleaseOSAnalyzer_Required(t *testing.T) { res, err := a.Analyze(context.Background(), test.input) if test.wantError != "" { - assert.Error(t, err) + require.Error(t, err) assert.Equal(t, test.wantError, err.Error()) } else { - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, test.wantResult, res) } }) diff --git a/pkg/fanal/analyzer/os/release/release_test.go b/pkg/fanal/analyzer/os/release/release_test.go index 615324c200fd..862f39f4cf17 100644 --- a/pkg/fanal/analyzer/os/release/release_test.go +++ b/pkg/fanal/analyzer/os/release/release_test.go @@ -120,12 +120,12 @@ func Test_osReleaseAnalyzer_Analyze(t *testing.T) { }) if tt.wantErr != "" { - assert.Error(t, err) + require.Error(t, err) assert.Equal(t, tt.wantErr, err.Error()) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, res) }) } diff --git a/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go b/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go index c131b900d899..5acb8441615e 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go +++ b/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -1443,21 +1444,21 @@ func Test_dpkgAnalyzer_Analyze(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { a, err := newDpkgAnalyzer(analyzer.AnalyzerOptions{}) - assert.NoError(t, err) + require.NoError(t, err) ctx := context.Background() mfs := mapfs.New() for testPath, osPath := range tt.testFiles { err = mfs.MkdirAll(filepath.Dir(osPath), os.ModePerm) - assert.NoError(t, err) + require.NoError(t, err) err = mfs.WriteFile(osPath, testPath) - assert.NoError(t, err) + require.NoError(t, err) } got, err := a.PostAnalyze(ctx, analyzer.PostAnalysisInput{ FS: mfs, }) - assert.NoError(t, err) + require.NoError(t, err) // Sort the result for consistency for i := range got.PackageInfos { @@ -1509,7 +1510,7 @@ func Test_dpkgAnalyzer_Required(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { a, err := newDpkgAnalyzer(analyzer.AnalyzerOptions{}) - assert.NoError(t, err) + require.NoError(t, err) got := a.Required(tt.filePath, nil) assert.Equal(t, tt.want, got) }) diff --git a/pkg/fanal/analyzer/pkg/rpm/rpm_test.go b/pkg/fanal/analyzer/pkg/rpm/rpm_test.go index 7c1ec8ca77c8..98cd1a2f3acc 100644 --- a/pkg/fanal/analyzer/pkg/rpm/rpm_test.go +++ b/pkg/fanal/analyzer/pkg/rpm/rpm_test.go @@ -97,9 +97,9 @@ func Test_splitFileName(t *testing.T) { t.Run(tt.name, func(t *testing.T) { gotName, gotVer, gotRel, err := splitFileName(tt.filename) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) } else { - assert.NoError(t, err) + require.NoError(t, err) } assert.Equal(t, tt.wantName, gotName) assert.Equal(t, tt.wantVer, gotVer) diff --git a/pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go b/pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go index ae216d501fc9..82a71cae4ee6 100644 --- a/pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go +++ b/pkg/fanal/analyzer/pkg/rpm/rpmqa_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/types" ) @@ -63,10 +64,10 @@ glibc 2.35-2.cm2 1653816591 1653628955 Microsoft Corporation (none) 10855265 x86 a := rpmqaPkgAnalyzer{} result, err := a.parseRpmqaManifest(strings.NewReader(test.content)) if test.wantErr != "" { - assert.Error(t, err) + require.Error(t, err) assert.Equal(t, test.wantErr, err.Error()) } else { - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, test.wantPkgs, result) } }) diff --git a/pkg/fanal/analyzer/repo/apk/apk_test.go b/pkg/fanal/analyzer/repo/apk/apk_test.go index daf60febe639..26586f7dfc0d 100644 --- a/pkg/fanal/analyzer/repo/apk/apk_test.go +++ b/pkg/fanal/analyzer/repo/apk/apk_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -152,11 +153,11 @@ https://dl-cdn.alpinelinux.org/alpine/v3.10/main got, err := a.Analyze(context.Background(), test.input) if test.wantErr != "" { - assert.Error(t, err) + require.Error(t, err) assert.Equal(t, test.wantErr, err.Error()) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, test.want, got) }) } diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go index 06dab92fb318..e755ebbd0d2c 100644 --- a/pkg/fanal/artifact/repo/git_test.go +++ b/pkg/fanal/artifact/repo/git_test.go @@ -211,7 +211,7 @@ func TestArtifact_Inspect(t *testing.T) { defer cleanup() ref, err := art.Inspect(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, ref) }) } diff --git a/pkg/fanal/cache/key_test.go b/pkg/fanal/cache/key_test.go index f21910b275ae..9e036155f5ea 100644 --- a/pkg/fanal/cache/key_test.go +++ b/pkg/fanal/cache/key_test.go @@ -1,9 +1,10 @@ package cache import ( - "github.com/aquasecurity/trivy/pkg/fanal/walker" "testing" + "github.com/aquasecurity/trivy/pkg/fanal/walker" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -253,7 +254,7 @@ func TestCalcKey(t *testing.T) { assert.ErrorContains(t, err, tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/cache/redis_test.go b/pkg/fanal/cache/redis_test.go index 99bdae01b3d4..335e272890f2 100644 --- a/pkg/fanal/cache/redis_test.go +++ b/pkg/fanal/cache/redis_test.go @@ -77,7 +77,7 @@ func TestRedisCache_PutArtifact(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } else { - assert.NoError(t, err) + require.NoError(t, err) } got, err := s.Get(tt.wantKey) @@ -166,7 +166,7 @@ func TestRedisCache_PutBlob(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } else { - assert.NoError(t, err) + require.NoError(t, err) } got, err := s.Get(tt.wantKey) @@ -251,7 +251,7 @@ func TestRedisCache_GetArtifact(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } else { - assert.NoError(t, err) + require.NoError(t, err) } assert.Equal(t, tt.want, got) @@ -345,7 +345,7 @@ func TestRedisCache_GetBlob(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -456,7 +456,7 @@ func TestRedisCache_MissingBlobs(t *testing.T) { return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.wantMissingArtifact, missingArtifact) assert.Equal(t, tt.wantMissingBlobIDs, missingBlobIDs) }) @@ -556,7 +556,7 @@ func TestRedisCache_DeleteBlobs(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) }) } } diff --git a/pkg/fanal/image/daemon/image_test.go b/pkg/fanal/image/daemon/image_test.go index 330041cfd42d..ce8b5f852596 100644 --- a/pkg/fanal/image/daemon/image_test.go +++ b/pkg/fanal/image/daemon/image_test.go @@ -112,7 +112,7 @@ func Test_image_ConfigNameWithCustomDockerHost(t *testing.T) { Algorithm: "sha256", Hex: "a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", }, conf) - assert.NoError(t, err) + require.NoError(t, err) } func Test_image_ConfigNameWithCustomPodmanHost(t *testing.T) { @@ -152,7 +152,7 @@ func Test_image_ConfigNameWithCustomPodmanHost(t *testing.T) { Algorithm: "sha256", Hex: "a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", }, conf) - assert.NoError(t, err) + require.NoError(t, err) } func Test_image_ConfigFile(t *testing.T) { diff --git a/pkg/fanal/image/daemon/podman_test.go b/pkg/fanal/image/daemon/podman_test.go index 5d56facb8cd2..d1c0e20807b6 100644 --- a/pkg/fanal/image/daemon/podman_test.go +++ b/pkg/fanal/image/daemon/podman_test.go @@ -88,10 +88,10 @@ func TestPodmanImage(t *testing.T) { defer cleanup() if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) confName, err := img.ConfigName() require.NoError(t, err) diff --git a/pkg/fanal/image/image_test.go b/pkg/fanal/image/image_test.go index 78147fb93f0e..283a4ee83ad7 100644 --- a/pkg/fanal/image/image_test.go +++ b/pkg/fanal/image/image_test.go @@ -281,10 +281,10 @@ func TestNewDockerImage(t *testing.T) { defer cleanup() if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) return } - assert.NoError(t, err) + require.NoError(t, err) gotID, err := img.ID() require.NoError(t, err) @@ -399,10 +399,10 @@ func TestNewDockerImageWithPrivateRegistry(t *testing.T) { defer cleanup() if tt.wantErr != "" { - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr, err) } else { - assert.NoError(t, err) + require.NoError(t, err) } }) } @@ -494,7 +494,7 @@ func TestNewArchiveImage(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr, tt.name) return default: - assert.NoError(t, err, tt.name) + require.NoError(t, err, tt.name) } // archive doesn't support RepoTags and RepoDigests @@ -552,7 +552,7 @@ func TestDockerPlatformArguments(t *testing.T) { if tt.wantErr != "" { assert.ErrorContains(t, err, tt.wantErr, err) } else { - assert.NoError(t, err) + require.NoError(t, err) } }) } diff --git a/pkg/fanal/image/oci_test.go b/pkg/fanal/image/oci_test.go index 6279fa1ed5c2..4256a56ae9f8 100644 --- a/pkg/fanal/image/oci_test.go +++ b/pkg/fanal/image/oci_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestTryOCI(t *testing.T) { @@ -61,10 +62,10 @@ func TestTryOCI(t *testing.T) { t.Run(test.name, func(t *testing.T) { _, err := tryOCI(test.ociImagePath) if test.wantErr != "" { - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), test.wantErr, err) } else { - assert.NoError(t, err) + require.NoError(t, err) } }) } diff --git a/pkg/fanal/image/registry/azure/azure_test.go b/pkg/fanal/image/registry/azure/azure_test.go index dc4d03fc078e..c8c48574b61b 100644 --- a/pkg/fanal/image/registry/azure/azure_test.go +++ b/pkg/fanal/image/registry/azure/azure_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/image/registry/azure" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -37,7 +38,7 @@ func TestRegistry_CheckOptions(t *testing.T) { if tt.wantErr != "" { assert.EqualError(t, err, tt.wantErr) } else { - assert.NoError(t, err) + require.NoError(t, err) } }) } diff --git a/pkg/fanal/test/integration/containerd_test.go b/pkg/fanal/test/integration/containerd_test.go index c335159fb851..e0fdd7602e32 100644 --- a/pkg/fanal/test/integration/containerd_test.go +++ b/pkg/fanal/test/integration/containerd_test.go @@ -63,7 +63,7 @@ func setupContainerd(t *testing.T, ctx context.Context, namespace string) *conta return err, true } t.Cleanup(func() { - assert.NoError(t, client.Close()) + require.NoError(t, client.Close()) }) return nil, false }) @@ -105,7 +105,7 @@ func startContainerd(t *testing.T, ctx context.Context, hostPath string) { require.NoError(t, err) t.Cleanup(func() { - assert.NoError(t, containerdC.Terminate(ctx)) + require.NoError(t, containerdC.Terminate(ctx)) }) } diff --git a/pkg/fanal/walker/fs_test.go b/pkg/fanal/walker/fs_test.go index 7af5b52020f8..0f4da5eefe72 100644 --- a/pkg/fanal/walker/fs_test.go +++ b/pkg/fanal/walker/fs_test.go @@ -2,7 +2,6 @@ package walker_test import ( "errors" - "golang.org/x/exp/slices" "io" "os" "path/filepath" @@ -10,6 +9,8 @@ import ( "strings" "testing" + "golang.org/x/exp/slices" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -84,7 +85,7 @@ func TestFS_Walk(t *testing.T) { assert.ErrorContains(t, err, tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) }) } } diff --git a/pkg/fanal/walker/tar_test.go b/pkg/fanal/walker/tar_test.go index 79dc878e0118..4cbdb782fb48 100644 --- a/pkg/fanal/walker/tar_test.go +++ b/pkg/fanal/walker/tar_test.go @@ -84,7 +84,7 @@ func TestLayerTar_Walk(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.wantOpqDirs, gotOpqDirs) assert.Equal(t, tt.wantWhFiles, gotWhFiles) }) diff --git a/pkg/flag/kubernetes_flags_test.go b/pkg/flag/kubernetes_flags_test.go index c5e45850609d..81b9fa94145a 100644 --- a/pkg/flag/kubernetes_flags_test.go +++ b/pkg/flag/kubernetes_flags_test.go @@ -5,6 +5,7 @@ import ( "github.com/samber/lo" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" ) @@ -44,7 +45,7 @@ func TestOptionToToleration(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := optionToTolerations(tt.tolerationsOptions) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go index 37ba3c81b84f..9aa3c0a58d69 100644 --- a/pkg/flag/report_flags_test.go +++ b/pkg/flag/report_flags_test.go @@ -1,9 +1,10 @@ package flag_test import ( - "github.com/aquasecurity/trivy/pkg/log" "testing" + "github.com/aquasecurity/trivy/pkg/log" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/compliance/spec" "github.com/aquasecurity/trivy/pkg/flag" @@ -11,6 +12,7 @@ import ( "github.com/aquasecurity/trivy/pkg/types" "github.com/spf13/viper" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestReportFlagGroup_ToOptions(t *testing.T) { @@ -221,7 +223,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { } got, err := f.ToOptions() - assert.NoError(t, err) + require.NoError(t, err) assert.Equalf(t, tt.want, got, "ToOptions()") // Assert log messages diff --git a/pkg/iac/detection/detect_test.go b/pkg/iac/detection/detect_test.go index ceb7f65f143a..5a0d9f876b16 100644 --- a/pkg/iac/detection/detect_test.go +++ b/pkg/iac/detection/detect_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_Detection(t *testing.T) { @@ -389,7 +390,7 @@ rules: func BenchmarkIsType_SmallFile(b *testing.B) { data, err := os.ReadFile(fmt.Sprintf("./testdata/%s", "small.file")) - assert.NoError(b, err) + require.NoError(b, err) b.ReportAllocs() b.ResetTimer() @@ -400,7 +401,7 @@ func BenchmarkIsType_SmallFile(b *testing.B) { func BenchmarkIsType_BigFile(b *testing.B) { data, err := os.ReadFile(fmt.Sprintf("./testdata/%s", "big.file")) - assert.NoError(b, err) + require.NoError(b, err) b.ReportAllocs() b.ResetTimer() diff --git a/pkg/iac/rego/load_test.go b/pkg/iac/rego/load_test.go index 1658fdefc467..a2aca7abaff5 100644 --- a/pkg/iac/rego/load_test.go +++ b/pkg/iac/rego/load_test.go @@ -97,7 +97,7 @@ deny { }` scanner := rego.NewScanner(types.SourceJSON) err := scanner.LoadPolicies(false, false, fstest.MapFS{}, []string{"."}, []io.Reader{strings.NewReader(check)}) - assert.NoError(t, err) + require.NoError(t, err) }) } @@ -205,7 +205,7 @@ deny { if tt.expectedErr != "" { assert.ErrorContains(t, err, tt.expectedErr) } else { - assert.NoError(t, err) + require.NoError(t, err) } }) } @@ -245,5 +245,5 @@ deny { options.ScannerWithEmbeddedPolicies(false), ) err := scanner.LoadPolicies(false, false, fsys, []string{"."}, nil) - assert.Error(t, err) + require.Error(t, err) } diff --git a/pkg/iac/rego/metadata_test.go b/pkg/iac/rego/metadata_test.go index 423ddc1a20d7..29f990a9b506 100644 --- a/pkg/iac/rego/metadata_test.go +++ b/pkg/iac/rego/metadata_test.go @@ -201,7 +201,7 @@ Resources: for _, tc := range testCases { t.Run(tc.schema, func(t *testing.T) { em, err := NewEngineMetadata(tc.schema, inputSchema) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tc.want, em.GoodExamples[0]) }) } diff --git a/pkg/iac/rego/scanner_test.go b/pkg/iac/rego/scanner_test.go index ef0878822acb..d97ffa675b26 100644 --- a/pkg/iac/rego/scanner_test.go +++ b/pkg/iac/rego/scanner_test.go @@ -871,7 +871,7 @@ deny { }) scanner := NewScanner(types.SourceDockerfile) - assert.NoError( + require.NoError( t, scanner.LoadPolicies(false, false, srcFS, []string{"policies"}, nil), ) @@ -1007,7 +1007,7 @@ deny { scanner.LoadPolicies(false, false, fsys, []string{"checks"}, nil), ) _, err := scanner.ScanInput(context.TODO(), Input{}) - assert.NoError(t, err) + require.NoError(t, err) assert.Contains(t, buf.String(), `Error occurred while applying rule "deny" from check "checks/bad.rego"`) } diff --git a/pkg/iac/scanners/terraform/executor/executor_test.go b/pkg/iac/scanners/terraform/executor/executor_test.go index d33e5d999c38..fa25a133ded8 100644 --- a/pkg/iac/scanners/terraform/executor/executor_test.go +++ b/pkg/iac/scanners/terraform/executor/executor_test.go @@ -54,7 +54,7 @@ resource "problem" "this" { require.NoError(t, err) results, err := New().Execute(modules) - assert.Error(t, err) + require.Error(t, err) assert.Empty(t, results.GetFailed()) } @@ -79,7 +79,7 @@ resource "problem" "this" { modules, _, err := p.EvaluateAll(context.TODO()) require.NoError(t, err) _, err = New().Execute(modules) - assert.Error(t, err) + require.Error(t, err) } func Test_PanicNotInCheckNotIncludePassed(t *testing.T) { @@ -127,5 +127,5 @@ resource "problem" "this" { require.NoError(t, err) _, err = New().Execute(modules) - assert.Error(t, err) + require.Error(t, err) } diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index 4be1b463d7ce..48b8b6765bae 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -174,7 +174,7 @@ output "mod_result" { require.NoError(t, parser.ParseFS(context.TODO(), "code")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) require.Len(t, modules, 2) rootModule := modules[0] @@ -235,7 +235,7 @@ output "mod_result" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(context.TODO(), "code")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) require.Len(t, modules, 2) rootModule := modules[0] childModule := modules[1] @@ -280,7 +280,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(context.TODO(), "code")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -307,7 +307,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(context.TODO(), "code")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -350,7 +350,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(context.TODO(), "code")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -398,7 +398,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(context.TODO(), "code")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -439,7 +439,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(context.TODO(), "code")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -487,7 +487,7 @@ resource "something" "blah" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(context.TODO(), "code")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) require.Len(t, modules, 1) rootModule := modules[0] @@ -635,7 +635,7 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "this2" { parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(context.TODO(), ".")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, modules, 1) rootModule := modules[0] @@ -670,7 +670,7 @@ resource "aws_s3_bucket" "main" { require.NoError(t, parser.ParseFS(context.TODO(), ".")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, modules, 1) rootModule := modules[0] @@ -703,7 +703,7 @@ resource "aws_s3_bucket" "this" { require.NoError(t, parser.ParseFS(context.TODO(), ".")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, modules, 1) rootModule := modules[0] @@ -737,7 +737,7 @@ resource "aws_s3_bucket" "this" { require.NoError(t, parser.ParseFS(context.TODO(), ".")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, modules, 1) rootModule := modules[0] @@ -791,7 +791,7 @@ policy_rules = { require.NoError(t, parser.ParseFS(context.TODO(), ".")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, modules, 1) rootModule := modules[0] @@ -827,7 +827,7 @@ resource "aws_s3_bucket" "this" { require.NoError(t, parser.ParseFS(context.TODO(), ".")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, modules, 1) bucketBlocks := modules.GetResourcesByType("aws_s3_bucket") @@ -859,7 +859,7 @@ data "http" "example" { require.NoError(t, parser.ParseFS(context.TODO(), ".")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, modules, 1) rootModule := modules[0] @@ -896,7 +896,7 @@ data "http" "example" { require.NoError(t, parser.ParseFS(context.TODO(), ".")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, modules, 1) rootModule := modules[0] @@ -1268,7 +1268,7 @@ func TestForEachWithObjectsOfDifferentTypes(t *testing.T) { require.NoError(t, parser.ParseFS(context.TODO(), ".")) modules, _, err := parser.EvaluateAll(context.TODO()) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, modules, 1) } diff --git a/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go b/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go index 1adc8e9d2922..d0e679e3dc03 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/registry_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_getPrivateRegistryTokenFromEnvVars_ErrorsWithNoEnvVarSet(t *testing.T) { @@ -50,7 +51,7 @@ func Test_getPrivateRegistryTokenFromEnvVars_ConvertsSiteNameToEnvVar(t *testing t.Setenv(tt.tokenName, "abcd") token, err := getPrivateRegistryTokenFromEnvVars(tt.siteName) assert.Equal(t, "abcd", token) - assert.NoError(t, err) + require.NoError(t, err) }) } } diff --git a/pkg/iac/terraform/reference_test.go b/pkg/iac/terraform/reference_test.go index 1b6a7b59be73..6ad5bc782655 100644 --- a/pkg/iac/terraform/reference_test.go +++ b/pkg/iac/terraform/reference_test.go @@ -46,7 +46,7 @@ func Test_ReferenceParsing(t *testing.T) { for _, test := range cases { t.Run(test.expected, func(t *testing.T) { ref, err := newReference(test.input, "") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, test.expected, ref.String()) }) } diff --git a/pkg/k8s/scanner/scanner_test.go b/pkg/k8s/scanner/scanner_test.go index 69429a6f4d48..ec6aa50baef0 100644 --- a/pkg/k8s/scanner/scanner_test.go +++ b/pkg/k8s/scanner/scanner_test.go @@ -2,13 +2,14 @@ package scanner import ( "context" + "sort" + "testing" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/uuid" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" - "sort" - "testing" "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" @@ -278,7 +279,7 @@ func TestScanner_Scan(t *testing.T) { uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d") runner, err := cmd.NewRunner(ctx, flagOpts) - assert.NoError(t, err) + require.NoError(t, err) scanner := NewScanner(tt.clusterName, runner, flagOpts) got, err := scanner.Scan(ctx, tt.artifacts) diff --git a/pkg/plugin/manager_test.go b/pkg/plugin/manager_test.go index 19b80ee01c0b..e2f3c5b6e82e 100644 --- a/pkg/plugin/manager_test.go +++ b/pkg/plugin/manager_test.go @@ -206,7 +206,7 @@ func TestManager_Run(t *testing.T) { require.ErrorContains(t, err, tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) }) } } @@ -230,7 +230,7 @@ func TestManager_Uninstall(t *testing.T) { // Uninstall the plugin err = plugin.NewManager().Uninstall(ctx, pluginName) - assert.NoError(t, err) + require.NoError(t, err) assert.NoDirExists(t, pluginDir) }) @@ -240,7 +240,7 @@ func TestManager_Uninstall(t *testing.T) { slog.SetDefault(slog.New(log.NewHandler(buf, &log.Options{Level: log.LevelInfo}))) err := plugin.NewManager().Uninstall(ctx, pluginName) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "2021-08-25T12:20:30Z\tERROR\t[plugin] No such plugin\n", buf.String()) }) } @@ -332,7 +332,7 @@ func TestManager_LoadAll(t *testing.T) { require.ErrorContains(t, err, tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) require.Len(t, got, len(tt.want)) for i := range tt.want { assert.EqualExportedValues(t, tt.want[i], got[i]) diff --git a/pkg/plugin/manager_unix_test.go b/pkg/plugin/manager_unix_test.go index 52f4b0d4e2c3..0fd024ff3246 100644 --- a/pkg/plugin/manager_unix_test.go +++ b/pkg/plugin/manager_unix_test.go @@ -191,7 +191,7 @@ func TestManager_Install(t *testing.T) { require.ErrorContains(t, err, tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.EqualExportedValues(t, tt.want, got) if tt.wantFile != "" { diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go index 2ac5d92399d9..9e741ffddb7c 100644 --- a/pkg/policy/policy_test.go +++ b/pkg/policy/policy_test.go @@ -128,7 +128,7 @@ func TestClient_LoadBuiltinPolicies(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } @@ -373,7 +373,7 @@ func TestClient_DownloadBuiltinPolicies(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) // Assert metadata.json metadata := filepath.Join(tempDir, "policy", "metadata.json") diff --git a/pkg/purl/purl_test.go b/pkg/purl/purl_test.go index 90af61d7b949..e010f6c35636 100644 --- a/pkg/purl/purl_test.go +++ b/pkg/purl/purl_test.go @@ -436,7 +436,7 @@ func TestNewPackageURL(t *testing.T) { assert.Contains(t, err.Error(), tc.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tc.want, packageURL, tc.name) }) } @@ -554,7 +554,7 @@ func TestFromString(t *testing.T) { assert.ErrorContains(t, err, tc.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tc.want, *pkg, tc.name) }) } diff --git a/pkg/rekor/client_test.go b/pkg/rekor/client_test.go index 96a50155dbec..9ea48d657cc5 100644 --- a/pkg/rekor/client_test.go +++ b/pkg/rekor/client_test.go @@ -69,7 +69,7 @@ func TestClient_Search(t *testing.T) { assert.ErrorContains(t, err, tt.wantErr) return } - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/remote/remote_test.go b/pkg/remote/remote_test.go index 63a98ba1fce2..c0fdb3087f31 100644 --- a/pkg/remote/remote_test.go +++ b/pkg/remote/remote_test.go @@ -202,7 +202,7 @@ func TestGet(t *testing.T) { assert.ErrorContains(t, err, tt.wantErr, err) return } - assert.NoError(t, err) + require.NoError(t, err) }) } } diff --git a/pkg/report/github/github_test.go b/pkg/report/github/github_test.go index 655a24a7a3b8..bd05fecf8d13 100644 --- a/pkg/report/github/github_test.go +++ b/pkg/report/github/github_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -245,11 +246,11 @@ func TestWriter_Write(t *testing.T) { inputResults := tt.report err := w.Write(context.Background(), inputResults) - assert.NoError(t, err) + require.NoError(t, err) var got github.DependencySnapshot err = json.Unmarshal(written.Bytes(), &got) - assert.NoError(t, err, "invalid github written") + require.NoError(t, err, "invalid github written") assert.Equal(t, tt.want, got.Manifests, tt.name) }) } diff --git a/pkg/report/json_test.go b/pkg/report/json_test.go index 493fd6d8f641..253af7b7782f 100644 --- a/pkg/report/json_test.go +++ b/pkg/report/json_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" @@ -87,11 +88,11 @@ func TestReportWriter_JSON(t *testing.T) { } err := jw.Write(context.Background(), inputResults) - assert.NoError(t, err) + require.NoError(t, err) var got types.Report err = json.Unmarshal(jsonWritten.Bytes(), &got) - assert.NoError(t, err, "invalid json written") + require.NoError(t, err, "invalid json written") assert.Equal(t, tc.want, got, tc.name) }) diff --git a/pkg/report/sarif_test.go b/pkg/report/sarif_test.go index 31a5f0d8a620..0b4f37e26efe 100644 --- a/pkg/report/sarif_test.go +++ b/pkg/report/sarif_test.go @@ -9,6 +9,7 @@ import ( "github.com/owenrumney/go-sarif/v2/sarif" "github.com/samber/lo" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" @@ -672,11 +673,11 @@ func TestReportWriter_Sarif(t *testing.T) { Output: sarifWritten, } err := w.Write(context.TODO(), tt.input) - assert.NoError(t, err) + require.NoError(t, err) result := &sarif.Report{} err = json.Unmarshal(sarifWritten.Bytes(), result) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.want, result) }) } diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index 6b357a6d08dc..2cec7d98c801 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -2,10 +2,12 @@ package table_test import ( "bytes" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "testing" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/report/table" @@ -85,7 +87,7 @@ Total: 1 (MEDIUM: 0, HIGH: 1) }, } err := writer.Write(nil, types.Report{Results: tc.results}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tc.expectedOutput, tableWritten.String(), tc.name) }) } diff --git a/pkg/report/template_test.go b/pkg/report/template_test.go index 4422e2c0e136..ae1b139c8767 100644 --- a/pkg/report/template_test.go +++ b/pkg/report/template_test.go @@ -183,7 +183,7 @@ func TestReportWriter_Template(t *testing.T) { w, err := report.NewTemplateWriter(&got, tc.template, "dev") require.NoError(t, err) err = w.Write(ctx, inputReport) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tc.expected, got.String()) }) } diff --git a/pkg/rpc/server/listen_test.go b/pkg/rpc/server/listen_test.go index 093aebca0c90..b36abb81ff37 100644 --- a/pkg/rpc/server/listen_test.go +++ b/pkg/rpc/server/listen_test.go @@ -178,7 +178,7 @@ func Test_dbWorker_update(t *testing.T) { mc := metadata.NewClient(cacheDir) got, err := mc.Get() - assert.NoError(t, err, tt.name) + require.NoError(t, err, tt.name) assert.Equal(t, tt.want, got, tt.name) mockDBClient.AssertExpectations(t) diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index ca4a66850faf..07ddf09c9af6 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -178,7 +178,7 @@ func TestScanServer_Scan(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } - assert.NoError(t, err, tt.name) + require.NoError(t, err, tt.name) assert.Equal(t, tt.want, got) }) } @@ -277,7 +277,7 @@ func TestCacheServer_PutArtifact(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { - assert.NoError(t, err, tt.name) + require.NoError(t, err, tt.name) } assert.Equal(t, tt.want, got) @@ -512,7 +512,7 @@ func TestCacheServer_PutBlob(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { - assert.NoError(t, err, tt.name) + require.NoError(t, err, tt.name) } assert.Equal(t, tt.want, got) @@ -577,7 +577,7 @@ func TestCacheServer_MissingBlobs(t *testing.T) { assert.Contains(t, err.Error(), tt.wantErr, tt.name) return } else { - assert.NoError(t, err, tt.name) + require.NoError(t, err, tt.name) } assert.Equal(t, tt.want, got) diff --git a/pkg/utils/fsutils/fs_test.go b/pkg/utils/fsutils/fs_test.go index 44dc6b3d42c2..cef8e7af723d 100644 --- a/pkg/utils/fsutils/fs_test.go +++ b/pkg/utils/fsutils/fs_test.go @@ -67,7 +67,7 @@ func TestCopyFile(t *testing.T) { require.Error(t, err, tt.name) assert.Equal(t, tt.wantErr, err.Error(), tt.name) } else { - assert.NoError(t, err, tt.name) + require.NoError(t, err, tt.name) } }) } From b526e73d883ac7cf269477642d11dfa363f284c3 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Mon, 20 May 2024 14:01:25 +0400 Subject: [PATCH 083/352] chore: respect timeout value in .golangci.yaml (#6724) Signed-off-by: knqyf263 --- magefiles/magefile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/magefiles/magefile.go b/magefiles/magefile.go index 2df15c4ed6b0..3e1efd4481f8 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -314,13 +314,13 @@ type Lint mg.Namespace // Run runs linters func (Lint) Run() error { mg.Deps(Tool{}.GolangciLint) - return sh.RunV("golangci-lint", "run", "--timeout", "5m") + return sh.RunV("golangci-lint", "run") } // Fix auto fixes linters func (Lint) Fix() error { mg.Deps(Tool{}.GolangciLint) - return sh.RunV("golangci-lint", "run", "--timeout", "5m", "--fix") + return sh.RunV("golangci-lint", "run", "--fix") } // Fmt formats Go code and proto files From 2bc54ad2752aba5de4380cb92c13b09c0abefd73 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 20 May 2024 22:55:36 +0600 Subject: [PATCH 084/352] feat(python): add line number support for `requirement.txt` files (#6729) --- docs/docs/coverage/language/python.md | 4 +- integration/testdata/pip.json.golden | 74 +++-- pkg/dependency/parser/python/pip/parse.go | 8 + .../parser/python/pip/parse_testcase.go | 264 ++++++++++++++++-- .../analyzer/language/python/pip/pip_test.go | 18 ++ pkg/fanal/artifact/local/fs_test.go | 24 +- 6 files changed, 344 insertions(+), 48 deletions(-) diff --git a/docs/docs/coverage/language/python.md b/docs/docs/coverage/language/python.md index eaed8f1d4b7d..ce792842b978 100644 --- a/docs/docs/coverage/language/python.md +++ b/docs/docs/coverage/language/python.md @@ -23,9 +23,9 @@ The following table provides an outline of the features Trivy offers. | Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | |-----------------|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| -| pip | requirements.txt | - | Include | - | - | +| pip | requirements.txt | - | Include | - | ✓ | | Pipenv | Pipfile.lock | ✓ | Include | - | ✓ | -| Poetry | poetry.lock | ✓ | Exclude | ✓ | | +| Poetry | poetry.lock | ✓ | Exclude | ✓ | - | | Packaging | Dependency graph | diff --git a/integration/testdata/pip.json.golden b/integration/testdata/pip.json.golden index cc715f05357d..4171d6b91df2 100644 --- a/integration/testdata/pip.json.golden +++ b/integration/testdata/pip.json.golden @@ -25,64 +25,106 @@ "Name": "Flask", "Identifier": { "PURL": "pkg:pypi/flask@2.0.0", - "UID": "301ccf5fd90d6082" + "UID": "8b02ba2c070d72c6" }, "Version": "2.0.0", - "Layer": {} + "Layer": {}, + "Locations": [ + { + "StartLine": 2, + "EndLine": 2 + } + ] }, { "Name": "Jinja2", "Identifier": { "PURL": "pkg:pypi/jinja2@3.0.0", - "UID": "212193e1595e68cc" + "UID": "476df0c1e49c8f99" }, "Version": "3.0.0", - "Layer": {} + "Layer": {}, + "Locations": [ + { + "StartLine": 4, + "EndLine": 4 + } + ] }, { "Name": "Werkzeug", "Identifier": { "PURL": "pkg:pypi/werkzeug@0.11", - "UID": "56b919b561299a48" + "UID": "4163de19df046f49" }, "Version": "0.11", - "Layer": {} + "Layer": {}, + "Locations": [ + { + "StartLine": 6, + "EndLine": 6 + } + ] }, { "Name": "click", "Identifier": { "PURL": "pkg:pypi/click@8.0.0", - "UID": "d58cb56b4e8b1ffd" + "UID": "71e4c8ef31456bf" }, "Version": "8.0.0", - "Layer": {} + "Layer": {}, + "Locations": [ + { + "StartLine": 1, + "EndLine": 1 + } + ] }, { "Name": "itsdangerous", "Identifier": { "PURL": "pkg:pypi/itsdangerous@2.0.0", - "UID": "9bf39d440e409733" + "UID": "389c7cbc34cb6b32" }, "Version": "2.0.0", - "Layer": {} + "Layer": {}, + "Locations": [ + { + "StartLine": 3, + "EndLine": 3 + } + ] }, { "Name": "oauth2-client", "Identifier": { "PURL": "pkg:pypi/oauth2-client@4.0.0", - "UID": "ffc67df5ef686f77" + "UID": "c63f60db796a16ed" }, "Version": "4.0.0", - "Layer": {} + "Layer": {}, + "Locations": [ + { + "StartLine": 7, + "EndLine": 7 + } + ] }, { "Name": "python-gitlab", "Identifier": { "PURL": "pkg:pypi/python-gitlab@2.0.0", - "UID": "f9cbb9736717c4d4" + "UID": "ccad39abab737d13" }, "Version": "2.0.0", - "Layer": {} + "Layer": {}, + "Locations": [ + { + "StartLine": 8, + "EndLine": 8 + } + ] } ], "Vulnerabilities": [ @@ -91,7 +133,7 @@ "PkgName": "Werkzeug", "PkgIdentifier": { "PURL": "pkg:pypi/werkzeug@0.11", - "UID": "56b919b561299a48" + "UID": "4163de19df046f49" }, "InstalledVersion": "0.11", "FixedVersion": "0.15.3", @@ -148,7 +190,7 @@ "PkgName": "Werkzeug", "PkgIdentifier": { "PURL": "pkg:pypi/werkzeug@0.11", - "UID": "56b919b561299a48" + "UID": "4163de19df046f49" }, "InstalledVersion": "0.11", "FixedVersion": "0.11.6", diff --git a/pkg/dependency/parser/python/pip/parse.go b/pkg/dependency/parser/python/pip/parse.go index 00eee4349b0b..a849eb0cbea2 100644 --- a/pkg/dependency/parser/python/pip/parse.go +++ b/pkg/dependency/parser/python/pip/parse.go @@ -37,7 +37,9 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc scanner := bufio.NewScanner(decodedReader) var pkgs []ftypes.Package + var lineNumber int for scanner.Scan() { + lineNumber++ line := scanner.Text() line = strings.ReplaceAll(line, " ", "") line = strings.ReplaceAll(line, `\`, "") @@ -52,6 +54,12 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc pkgs = append(pkgs, ftypes.Package{ Name: s[0], Version: s[1], + Locations: []ftypes.Location{ + { + StartLine: lineNumber, + EndLine: lineNumber, + }, + }, }) } if err := scanner.Err(); err != nil { diff --git a/pkg/dependency/parser/python/pip/parse_testcase.go b/pkg/dependency/parser/python/pip/parse_testcase.go index dc119c3ba054..e8192ee1775d 100644 --- a/pkg/dependency/parser/python/pip/parse_testcase.go +++ b/pkg/dependency/parser/python/pip/parse_testcase.go @@ -4,53 +4,269 @@ import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( requirementsFlask = []ftypes.Package{ - {Name: "click", Version: "8.0.0"}, - {Name: "Flask", Version: "2.0.0"}, - {Name: "itsdangerous", Version: "2.0.0"}, - {Name: "Jinja2", Version: "3.0.0"}, - {Name: "MarkupSafe", Version: "2.0.0"}, - {Name: "Werkzeug", Version: "2.0.0"}, + { + Name: "click", + Version: "8.0.0", + Locations: []ftypes.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, + }, + { + Name: "Flask", + Version: "2.0.0", + Locations: []ftypes.Location{ + { + StartLine: 2, + EndLine: 2, + }, + }, + }, + { + Name: "itsdangerous", + Version: "2.0.0", + Locations: []ftypes.Location{ + { + StartLine: 3, + EndLine: 3, + }, + }, + }, + { + Name: "Jinja2", + Version: "3.0.0", + Locations: []ftypes.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, + { + Name: "MarkupSafe", + Version: "2.0.0", + Locations: []ftypes.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + { + Name: "Werkzeug", + Version: "2.0.0", + Locations: []ftypes.Location{ + { + StartLine: 6, + EndLine: 6, + }, + }, + }, } requirementsComments = []ftypes.Package{ - {Name: "click", Version: "8.0.0"}, - {Name: "Flask", Version: "2.0.0"}, - {Name: "Jinja2", Version: "3.0.0"}, - {Name: "MarkupSafe", Version: "2.0.0"}, + { + Name: "click", + Version: "8.0.0", + Locations: []ftypes.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, + { + Name: "Flask", + Version: "2.0.0", + Locations: []ftypes.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + { + Name: "Jinja2", + Version: "3.0.0", + Locations: []ftypes.Location{ + { + StartLine: 6, + EndLine: 6, + }, + }, + }, + { + Name: "MarkupSafe", + Version: "2.0.0", + Locations: []ftypes.Location{ + { + StartLine: 7, + EndLine: 7, + }, + }, + }, } requirementsSpaces = []ftypes.Package{ - {Name: "click", Version: "8.0.0"}, - {Name: "Flask", Version: "2.0.0"}, - {Name: "itsdangerous", Version: "2.0.0"}, - {Name: "Jinja2", Version: "3.0.0"}, + { + Name: "click", + Version: "8.0.0", + Locations: []ftypes.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, + }, + { + Name: "Flask", + Version: "2.0.0", + Locations: []ftypes.Location{ + { + StartLine: 2, + EndLine: 2, + }, + }, + }, + { + Name: "itsdangerous", + Version: "2.0.0", + Locations: []ftypes.Location{ + { + StartLine: 3, + EndLine: 3, + }, + }, + }, + { + Name: "Jinja2", + Version: "3.0.0", + Locations: []ftypes.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, } requirementsNoVersion = []ftypes.Package{ - {Name: "Flask", Version: "2.0.0"}, + { + Name: "Flask", + Version: "2.0.0", + Locations: []ftypes.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, + }, } requirementsOperator = []ftypes.Package{ - {Name: "Django", Version: "2.3.4"}, - {Name: "SomeProject", Version: "5.4"}, + { + Name: "Django", + Version: "2.3.4", + Locations: []ftypes.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, + { + Name: "SomeProject", + Version: "5.4", + Locations: []ftypes.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, } requirementsHash = []ftypes.Package{ - {Name: "FooProject", Version: "1.2"}, - {Name: "Jinja2", Version: "3.0.0"}, + { + Name: "FooProject", + Version: "1.2", + Locations: []ftypes.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, + }, + { + Name: "Jinja2", + Version: "3.0.0", + Locations: []ftypes.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, } requirementsHyphens = []ftypes.Package{ - {Name: "oauth2-client", Version: "4.0.0"}, - {Name: "python-gitlab", Version: "2.0.0"}, + { + Name: "oauth2-client", + Version: "4.0.0", + Locations: []ftypes.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, + }, + { + Name: "python-gitlab", + Version: "2.0.0", + Locations: []ftypes.Location{ + { + StartLine: 2, + EndLine: 2, + }, + }, + }, } requirementsExtras = []ftypes.Package{ - {Name: "pyjwt", Version: "2.1.0"}, - {Name: "celery", Version: "4.4.7"}, + { + Name: "pyjwt", + Version: "2.1.0", + Locations: []ftypes.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, + }, + { + Name: "celery", + Version: "4.4.7", + Locations: []ftypes.Location{ + { + StartLine: 2, + EndLine: 2, + }, + }, + }, } requirementsUtf16le = []ftypes.Package{ - {Name: "attrs", Version: "20.3.0"}, + { + Name: "attrs", + Version: "20.3.0", + Locations: []ftypes.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, + }, } ) diff --git a/pkg/fanal/analyzer/language/python/pip/pip_test.go b/pkg/fanal/analyzer/language/python/pip/pip_test.go index c97714506a27..62fd13953f9b 100644 --- a/pkg/fanal/analyzer/language/python/pip/pip_test.go +++ b/pkg/fanal/analyzer/language/python/pip/pip_test.go @@ -31,14 +31,32 @@ func Test_pipAnalyzer_Analyze(t *testing.T) { { Name: "click", Version: "8.0.0", + Locations: []types.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, }, { Name: "Flask", Version: "2.0.0", + Locations: []types.Location{ + { + StartLine: 2, + EndLine: 2, + }, + }, }, { Name: "itsdangerous", Version: "2.0.0", + Locations: []types.Location{ + { + StartLine: 3, + EndLine: 3, + }, + }, }, }, }, diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index 3bbb9d2c3b79..707818c3cdf2 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -174,7 +174,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", + BlobID: "sha256:0c41376dbbc0dbf18e9dbd36c5de85627007dcf9357fd98f191864d48dd35537", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -185,6 +185,12 @@ func TestArtifact_Inspect(t *testing.T) { { Name: "Flask", Version: "2.0.0", + Locations: []types.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, }, }, }, @@ -196,9 +202,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "testdata/requirements.txt", Type: artifact.TypeFilesystem, - ID: "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", + ID: "sha256:0c41376dbbc0dbf18e9dbd36c5de85627007dcf9357fd98f191864d48dd35537", BlobIDs: []string{ - "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", + "sha256:0c41376dbbc0dbf18e9dbd36c5de85627007dcf9357fd98f191864d48dd35537", }, }, }, @@ -209,7 +215,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", + BlobID: "sha256:0c41376dbbc0dbf18e9dbd36c5de85627007dcf9357fd98f191864d48dd35537", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -220,6 +226,12 @@ func TestArtifact_Inspect(t *testing.T) { { Name: "Flask", Version: "2.0.0", + Locations: []types.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, }, }, }, @@ -231,9 +243,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "testdata/requirements.txt", Type: artifact.TypeFilesystem, - ID: "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", + ID: "sha256:0c41376dbbc0dbf18e9dbd36c5de85627007dcf9357fd98f191864d48dd35537", BlobIDs: []string{ - "sha256:9e999fcf6fd571e175601fb7cc0da28f0d7960e26eab67dad93152e0bebf21ca", + "sha256:0c41376dbbc0dbf18e9dbd36c5de85627007dcf9357fd98f191864d48dd35537", }, }, }, From 9515695d45e9b5c20890e27e21e3ab45bfd4ce5f Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Tue, 21 May 2024 14:00:51 +0400 Subject: [PATCH 085/352] feat(vex): support non-root components for products in OpenVEX (#6728) Signed-off-by: knqyf263 Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> --- integration/testdata/conda-spdx.json.golden | 12 +- integration/testdata/julia-spdx.json.golden | 24 +- pkg/fanal/applier/applier_test.go | 2 +- pkg/result/filter.go | 2 +- pkg/result/filter_test.go | 56 +++-- pkg/sbom/core/bom.go | 16 +- pkg/sbom/cyclonedx/marshal.go | 2 +- pkg/sbom/io/encode.go | 1 + pkg/vex/csaf.go | 2 +- pkg/vex/openvex.go | 53 ++--- pkg/vex/testdata/openvex-nested.json | 26 +++ pkg/vex/vex.go | 71 +++++- pkg/vex/vex_test.go | 240 ++++++++++++++++---- 13 files changed, 393 insertions(+), 114 deletions(-) create mode 100644 pkg/vex/testdata/openvex-nested.json diff --git a/integration/testdata/conda-spdx.json.golden b/integration/testdata/conda-spdx.json.golden index f8a8778538c0..c14d1bf4e018 100644 --- a/integration/testdata/conda-spdx.json.golden +++ b/integration/testdata/conda-spdx.json.golden @@ -14,7 +14,7 @@ "packages": [ { "name": "openssl", - "SPDXID": "SPDXRef-Package-32b6b37a6fa2e57f", + "SPDXID": "SPDXRef-Package-22a178da112ac20a", "versionInfo": "1.1.1q", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -38,7 +38,7 @@ }, { "name": "pip", - "SPDXID": "SPDXRef-Package-e260029d0b6fd07b", + "SPDXID": "SPDXRef-Package-c22b9ee9a601ba6", "versionInfo": "22.2.2", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -103,21 +103,21 @@ }, { "spdxElementId": "SPDXRef-Filesystem-2e2426fd0f2580ef", - "relatedSpdxElement": "SPDXRef-Package-32b6b37a6fa2e57f", + "relatedSpdxElement": "SPDXRef-Package-22a178da112ac20a", "relationshipType": "CONTAINS" }, { "spdxElementId": "SPDXRef-Filesystem-2e2426fd0f2580ef", - "relatedSpdxElement": "SPDXRef-Package-e260029d0b6fd07b", + "relatedSpdxElement": "SPDXRef-Package-c22b9ee9a601ba6", "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-32b6b37a6fa2e57f", + "spdxElementId": "SPDXRef-Package-22a178da112ac20a", "relatedSpdxElement": "SPDXRef-File-600e5e0110a84891", "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-e260029d0b6fd07b", + "spdxElementId": "SPDXRef-Package-c22b9ee9a601ba6", "relatedSpdxElement": "SPDXRef-File-7eb62e2a3edddc0a", "relationshipType": "CONTAINS" } diff --git a/integration/testdata/julia-spdx.json.golden b/integration/testdata/julia-spdx.json.golden index 483991784365..8ae4ead23a7d 100644 --- a/integration/testdata/julia-spdx.json.golden +++ b/integration/testdata/julia-spdx.json.golden @@ -25,7 +25,7 @@ }, { "name": "A", - "SPDXID": "SPDXRef-Package-2a46714189f3b9de", + "SPDXID": "SPDXRef-Package-7784b00da0cb0cb0", "versionInfo": "1.9.0", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -48,7 +48,7 @@ }, { "name": "B", - "SPDXID": "SPDXRef-Package-4a8e351c4c9b7318", + "SPDXID": "SPDXRef-Package-960543ac5c5f7e10", "versionInfo": "1.9.0", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -60,18 +60,18 @@ { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "pkg:julia/B@1.9.0?uuid=edca9bc6-334e-11e9-3554-9595dbb4349c" + "referenceLocator": "pkg:julia/B@1.9.0?uuid=f41f7b98-334e-11e9-1257-49272045fb24" } ], "attributionTexts": [ - "PkgID: edca9bc6-334e-11e9-3554-9595dbb4349c", + "PkgID: f41f7b98-334e-11e9-1257-49272045fb24", "PkgType: julia" ], "primaryPackagePurpose": "LIBRARY" }, { "name": "B", - "SPDXID": "SPDXRef-Package-d10d5e4a30a43fff", + "SPDXID": "SPDXRef-Package-a4705eb108e4f15c", "versionInfo": "1.9.0", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -83,11 +83,11 @@ { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "pkg:julia/B@1.9.0?uuid=f41f7b98-334e-11e9-1257-49272045fb24" + "referenceLocator": "pkg:julia/B@1.9.0?uuid=edca9bc6-334e-11e9-3554-9595dbb4349c" } ], "attributionTexts": [ - "PkgID: f41f7b98-334e-11e9-1257-49272045fb24", + "PkgID: edca9bc6-334e-11e9-3554-9595dbb4349c", "PkgType: julia" ], "primaryPackagePurpose": "LIBRARY" @@ -106,17 +106,17 @@ "relationships": [ { "spdxElementId": "SPDXRef-Application-18fc3597717a3e56", - "relatedSpdxElement": "SPDXRef-Package-2a46714189f3b9de", + "relatedSpdxElement": "SPDXRef-Package-7784b00da0cb0cb0", "relationshipType": "CONTAINS" }, { "spdxElementId": "SPDXRef-Application-18fc3597717a3e56", - "relatedSpdxElement": "SPDXRef-Package-4a8e351c4c9b7318", + "relatedSpdxElement": "SPDXRef-Package-960543ac5c5f7e10", "relationshipType": "CONTAINS" }, { "spdxElementId": "SPDXRef-Application-18fc3597717a3e56", - "relatedSpdxElement": "SPDXRef-Package-d10d5e4a30a43fff", + "relatedSpdxElement": "SPDXRef-Package-a4705eb108e4f15c", "relationshipType": "CONTAINS" }, { @@ -130,8 +130,8 @@ "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-2a46714189f3b9de", - "relatedSpdxElement": "SPDXRef-Package-d10d5e4a30a43fff", + "spdxElementId": "SPDXRef-Package-7784b00da0cb0cb0", + "relatedSpdxElement": "SPDXRef-Package-960543ac5c5f7e10", "relationshipType": "DEPENDS_ON" } ] diff --git a/pkg/fanal/applier/applier_test.go b/pkg/fanal/applier/applier_test.go index 8ba2b3f1eb95..ac8915f2dad6 100644 --- a/pkg/fanal/applier/applier_test.go +++ b/pkg/fanal/applier/applier_test.go @@ -1,10 +1,10 @@ package applier_test import ( - "github.com/package-url/packageurl-go" "sort" "testing" + "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/result/filter.go b/pkg/result/filter.go index 6e92dbd3de98..17b2b6feb422 100644 --- a/pkg/result/filter.go +++ b/pkg/result/filter.go @@ -89,7 +89,7 @@ func filterByVEX(report types.Report, opt FilterOption) error { return nil } - bom, err := sbomio.NewEncoder(core.Options{}).Encode(report) + bom, err := sbomio.NewEncoder(core.Options{Parents: true}).Encode(report) if err != nil { return xerrors.Errorf("unable to encode the SBOM: %w", err) } diff --git a/pkg/result/filter_test.go b/pkg/result/filter_test.go index 740caa6693d0..71ac8644cd36 100644 --- a/pkg/result/filter_test.go +++ b/pkg/result/filter_test.go @@ -2,6 +2,7 @@ package result_test import ( "context" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/package-url/packageurl-go" "testing" "time" @@ -18,10 +19,12 @@ import ( func TestFilter(t *testing.T) { var ( - vuln1 = types.DetectedVulnerability{ - VulnerabilityID: "CVE-2019-0001", - PkgName: "foo", - PkgIdentifier: ftypes.PkgIdentifier{ + pkg1 = ftypes.Package{ + ID: "foo@1.2.3", + Name: "foo", + Version: "1.2.3", + Identifier: ftypes.PkgIdentifier{ + UID: "01", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "github.com/aquasecurity", @@ -29,25 +32,29 @@ func TestFilter(t *testing.T) { Version: "1.2.3", }, }, - InstalledVersion: "1.2.3", + } + vuln1 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2019-0001", + PkgName: pkg1.Name, + InstalledVersion: pkg1.Version, FixedVersion: "1.2.4", + PkgIdentifier: ftypes.PkgIdentifier{ + UID: pkg1.Identifier.UID, + PURL: pkg1.Identifier.PURL, + }, Vulnerability: dbTypes.Vulnerability{ Severity: dbTypes.SeverityLow.String(), }, } vuln2 = types.DetectedVulnerability{ - VulnerabilityID: "CVE-2019-0002", - PkgName: "foo", + VulnerabilityID: "CVE-2019-0002", + PkgName: pkg1.Name, + InstalledVersion: pkg1.Version, + FixedVersion: "1.2.4", PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeGolang, - Namespace: "github.com/aquasecurity", - Name: "foo", - Version: "4.5.6", - }, + UID: pkg1.Identifier.UID, + PURL: pkg1.Identifier.PURL, }, - InstalledVersion: "1.2.3", - FixedVersion: "1.2.4", Vulnerability: dbTypes.Vulnerability{ Severity: dbTypes.SeverityCritical.String(), }, @@ -243,8 +250,14 @@ func TestFilter(t *testing.T) { name: "filter by VEX", args: args{ report: types.Report{ + ArtifactName: ".", + ArtifactType: artifact.TypeFilesystem, Results: types.Results{ types.Result{ + Target: "gobinary", + Class: types.ClassLangPkg, + Type: ftypes.GoBinary, + Packages: []ftypes.Package{pkg1}, Vulnerabilities: []types.DetectedVulnerability{ vuln1, vuln2, @@ -262,8 +275,14 @@ func TestFilter(t *testing.T) { vexPath: "testdata/openvex.json", }, want: types.Report{ + ArtifactName: ".", + ArtifactType: artifact.TypeFilesystem, Results: types.Results{ types.Result{ + Target: "gobinary", + Class: types.ClassLangPkg, + Type: ftypes.GoBinary, + Packages: []ftypes.Package{pkg1}, Vulnerabilities: []types.DetectedVulnerability{ vuln2, }, @@ -658,7 +677,10 @@ func TestFilter(t *testing.T) { }, }, }, - severities: []dbTypes.Severity{dbTypes.SeverityLow, dbTypes.SeverityHigh}, + severities: []dbTypes.Severity{ + dbTypes.SeverityLow, + dbTypes.SeverityHigh, + }, policyFile: "./testdata/test-ignore-policy-licenses-and-secrets.rego", }, want: types.Report{ @@ -671,7 +693,7 @@ func TestFilter(t *testing.T) { secret1, }, ModifiedFindings: []types.ModifiedFinding{ - { + { Type: types.FindingTypeSecret, Status: types.FindingStatusIgnored, Statement: "Filtered by Rego", diff --git a/pkg/sbom/core/bom.go b/pkg/sbom/core/bom.go index 68ba8dab76d5..1fb3078d0c6b 100644 --- a/pkg/sbom/core/bom.go +++ b/pkg/sbom/core/bom.go @@ -70,6 +70,10 @@ type BOM struct { // This is used to ensure that each package URL is only represented once in the BOM. purls map[string][]uuid.UUID + // parents is a map of parent components to their children + // This field is populated when Options.Parents is set to true. + parents map[uuid.UUID][]uuid.UUID + // opts is a set of options for the BOM. opts Options } @@ -199,7 +203,8 @@ type Vulnerability struct { } type Options struct { - GenerateBOMRef bool + GenerateBOMRef bool // Generate BOMRef for CycloneDX + Parents bool // Hold parent maps } func NewBOM(opts Options) *BOM { @@ -208,6 +213,7 @@ func NewBOM(opts Options) *BOM { relationships: make(map[uuid.UUID][]Relationship), vulnerabilities: make(map[uuid.UUID][]Vulnerability), purls: make(map[string][]uuid.UUID), + parents: make(map[uuid.UUID][]uuid.UUID), opts: opts, } } @@ -257,6 +263,10 @@ func (b *BOM) AddRelationship(parent, child *Component, relationshipType Relatio Type: relationshipType, Dependency: child.id, }) + + if b.opts.Parents { + b.parents[child.id] = append(b.parents[child.id], parent.id) + } } func (b *BOM) AddVulnerabilities(c *Component, vulns []Vulnerability) { @@ -298,8 +308,8 @@ func (b *BOM) Vulnerabilities() map[uuid.UUID][]Vulnerability { return b.vulnerabilities } -func (b *BOM) NumComponents() int { - return len(b.components) + 1 // +1 for the root component +func (b *BOM) Parents() map[uuid.UUID][]uuid.UUID { + return b.parents } // bomRef returns BOMRef for CycloneDX diff --git a/pkg/sbom/cyclonedx/marshal.go b/pkg/sbom/cyclonedx/marshal.go index 5b7241254d25..7f4bb0c3b397 100644 --- a/pkg/sbom/cyclonedx/marshal.go +++ b/pkg/sbom/cyclonedx/marshal.go @@ -60,7 +60,7 @@ func (m *Marshaler) MarshalReport(ctx context.Context, report types.Report) (*cd // Marshal converts the Trivy component to the CycloneDX format func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*cdx.BOM, error) { m.bom = bom - m.componentIDs = make(map[uuid.UUID]string, m.bom.NumComponents()) + m.componentIDs = make(map[uuid.UUID]string, len(m.bom.Components())) cdxBOM := cdx.NewBOM() cdxBOM.SerialNumber = uuid.New().URN() diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index 4b715b023a08..db9052f033dd 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -348,6 +348,7 @@ func (*Encoder) component(result types.Result, pkg ftypes.Package) *core.Compone SrcVersion: utils.FormatSrcVersion(pkg), SrcFile: srcFile, PkgIdentifier: ftypes.PkgIdentifier{ + UID: pkg.Identifier.UID, PURL: pkg.Identifier.PURL, }, Supplier: pkg.Maintainer, diff --git a/pkg/vex/csaf.go b/pkg/vex/csaf.go index 3e43503b042b..8f6ecc9a84cb 100644 --- a/pkg/vex/csaf.go +++ b/pkg/vex/csaf.go @@ -1,7 +1,7 @@ package vex import ( - csaf "github.com/csaf-poc/csaf_distribution/v3/csaf" + "github.com/csaf-poc/csaf_distribution/v3/csaf" "github.com/package-url/packageurl-go" "github.com/samber/lo" diff --git a/pkg/vex/openvex.go b/pkg/vex/openvex.go index ce049777e8cc..36ab7808d559 100644 --- a/pkg/vex/openvex.go +++ b/pkg/vex/openvex.go @@ -2,7 +2,6 @@ package vex import ( openvex "github.com/openvex/go-vex/pkg/vex" - "github.com/samber/lo" "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/types" @@ -19,38 +18,36 @@ func newOpenVEX(vex openvex.VEX) VEX { } func (v *OpenVEX) Filter(result *types.Result, bom *core.BOM) { - result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool { - if vuln.PkgIdentifier.PURL == nil { - return true - } + filterVulnerabilities(result, bom, v.NotAffected) +} - stmts := v.Matches(vuln, bom) - if len(stmts) == 0 { - return true - } +func (v *OpenVEX) NotAffected(vuln types.DetectedVulnerability, product, subComponent *core.Component) (types.ModifiedFinding, bool) { + stmts := v.Matches(vuln, product, subComponent) + if len(stmts) == 0 { + return types.ModifiedFinding{}, false + } - // Take the latest statement for a given vulnerability and product - // as a sequence of statements can be overridden by the newer one. - // cf. https://github.com/openvex/spec/blob/fa5ba0c0afedb008dc5ebad418548cacf16a3ca7/OPENVEX-SPEC.md#the-vex-statement - stmt := stmts[len(stmts)-1] - if stmt.Status == openvex.StatusNotAffected || stmt.Status == openvex.StatusFixed { - result.ModifiedFindings = append(result.ModifiedFindings, - types.NewModifiedFinding(vuln, findingStatus(stmt.Status), string(stmt.Justification), "OpenVEX")) - return false - } - return true - }) + // Take the latest statement for a given vulnerability and product + // as a sequence of statements can be overridden by the newer one. + // cf. https://github.com/openvex/spec/blob/fa5ba0c0afedb008dc5ebad418548cacf16a3ca7/OPENVEX-SPEC.md#the-vex-statement + stmt := stmts[len(stmts)-1] + if stmt.Status == openvex.StatusNotAffected || stmt.Status == openvex.StatusFixed { + modifiedFindings := types.NewModifiedFinding(vuln, findingStatus(stmt.Status), string(stmt.Justification), "OpenVEX") + return modifiedFindings, true + } + return types.ModifiedFinding{}, false } -func (v *OpenVEX) Matches(vuln types.DetectedVulnerability, bom *core.BOM) []openvex.Statement { - root := bom.Root() - if root != nil && root.PkgIdentifier.PURL != nil { - stmts := v.vex.Matches(vuln.VulnerabilityID, root.PkgIdentifier.PURL.String(), []string{vuln.PkgIdentifier.PURL.String()}) - if len(stmts) != 0 { - return stmts - } +func (v *OpenVEX) Matches(vuln types.DetectedVulnerability, product, subComponent *core.Component) []openvex.Statement { + if product == nil || product.PkgIdentifier.PURL == nil { + return nil + } + + var subComponentPURL string + if subComponent != nil && subComponent.PkgIdentifier.PURL != nil { + subComponentPURL = subComponent.PkgIdentifier.PURL.String() } - return v.vex.Matches(vuln.VulnerabilityID, vuln.PkgIdentifier.PURL.String(), nil) + return v.vex.Matches(vuln.VulnerabilityID, product.PkgIdentifier.PURL.String(), []string{subComponentPURL}) } func findingStatus(status openvex.Status) types.FindingStatus { diff --git a/pkg/vex/testdata/openvex-nested.json b/pkg/vex/testdata/openvex-nested.json new file mode 100644 index 000000000000..da7dd68615a5 --- /dev/null +++ b/pkg/vex/testdata/openvex-nested.json @@ -0,0 +1,26 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "author": "Aqua Security", + "role": "Project Release Bot", + "timestamp": "2023-01-16T19:07:16.853479631-06:00", + "version": 1, + "statements": [ + { + "vulnerability": { + "name": "CVE-2024-0001" + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/go-direct1@2.0.0", + "subcomponents": [ + { + "@id": "pkg:golang/github.com/aquasecurity/go-transitive@4.0.0" + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } + ] +} diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index 64aa651d640c..c2b5ad10ea43 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -5,17 +5,20 @@ import ( "io" "os" - csaf "github.com/csaf-poc/csaf_distribution/v3/csaf" + "github.com/csaf-poc/csaf_distribution/v3/csaf" "github.com/hashicorp/go-multierror" openvex "github.com/openvex/go-vex/pkg/vex" + "github.com/samber/lo" "github.com/sirupsen/logrus" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/sbom" "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/uuid" ) // VEX represents Vulnerability Exploitability eXchange. It abstracts multiple VEX formats. @@ -104,3 +107,69 @@ func decodeCSAF(r io.ReadSeeker) (VEX, error) { } return newCSAF(adv), nil } + +type NotAffected func(vuln types.DetectedVulnerability, product, subComponent *core.Component) (types.ModifiedFinding, bool) + +func filterVulnerabilities(result *types.Result, bom *core.BOM, fn NotAffected) { + components := lo.MapEntries(bom.Components(), func(id uuid.UUID, component *core.Component) (string, *core.Component) { + return component.PkgIdentifier.UID, component + }) + + result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool { + if vuln.PkgIdentifier.PURL == nil { + return true + } + + c, ok := components[vuln.PkgIdentifier.UID] + if !ok { + log.Error("Component not found", log.String("uid", vuln.PkgIdentifier.UID)) + return true // Should never reach here + } + + notAffectedFn := func(c, leaf *core.Component) bool { + modified, notAffected := fn(vuln, c, leaf) + if notAffected { + result.ModifiedFindings = append(result.ModifiedFindings, modified) + return true + } + return false + } + + return reachRoot(c, bom.Components(), bom.Parents(), notAffectedFn) + }) +} + +// reachRoot traverses the component tree from the leaf to the root and returns true if the leaf reaches the root. +func reachRoot(leaf *core.Component, components map[uuid.UUID]*core.Component, parents map[uuid.UUID][]uuid.UUID, + notAffected func(c, leaf *core.Component) bool) bool { + + if notAffected(leaf, nil) { + return false + } + + visited := make(map[uuid.UUID]bool) + + // Use Depth First Search (DFS) + var dfs func(c *core.Component) bool + dfs = func(c *core.Component) bool { + // Call the function with the current component and the leaf component + if notAffected(c, leaf) { + return false + } else if c.Root { + return true + } + + visited[c.ID()] = true + for _, parent := range parents[c.ID()] { + if visited[parent] { + continue + } + if dfs(components[parent]) { + return true + } + } + return false + } + + return dfs(leaf) +} diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go index ad385b63596e..e7699aff2fb9 100644 --- a/pkg/vex/vex_test.go +++ b/pkg/vex/vex_test.go @@ -18,24 +18,40 @@ import ( ) var ( - vuln1 = types.DetectedVulnerability{ - VulnerabilityID: "CVE-2021-44228", - PkgName: "spring-boot", - InstalledVersion: "2.6.0", + ociComponent = core.Component{ + Root: true, + Type: core.TypeContainerImage, + Name: "debian:12", PkgIdentifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "org.springframework.boot", - Name: "spring-boot", - Version: "2.6.0", + Type: packageurl.TypeOCI, + Name: "debian", + Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", + Qualifiers: packageurl.Qualifiers{ + { + Key: "tag", + Value: "12", + }, + { + Key: "repository_url", + Value: "docker.io/library/debian", + }, + }, }, }, } - vuln2 = types.DetectedVulnerability{ - VulnerabilityID: "CVE-2021-0001", - PkgName: "spring-boot", - InstalledVersion: "2.6.0", + fsComponent = core.Component{ + Root: true, + Type: core.TypeFilesystem, + Name: ".", + } + springComponent = core.Component{ + Type: core.TypeLibrary, + Group: "org.springframework.boot", + Name: "spring-boot", + Version: "2.6.0", PkgIdentifier: ftypes.PkgIdentifier{ + UID: "01", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "org.springframework.boot", @@ -44,11 +60,12 @@ var ( }, }, } - vuln3 = types.DetectedVulnerability{ - VulnerabilityID: "CVE-2022-3715", - PkgName: "bash", - InstalledVersion: "5.2.15", + bashComponent = core.Component{ + Type: core.TypeLibrary, + Name: "bash", + Version: "5.3", PkgIdentifier: ftypes.PkgIdentifier{ + UID: "02", PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Namespace: "debian", @@ -57,6 +74,98 @@ var ( }, }, } + goModuleComponent = core.Component{ + Type: core.TypeLibrary, + Name: "github.com/aquasecurity/go-module", + Version: "1.0.0", + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "03", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/aquasecurity", + Name: "go-module", + Version: "1.0.0", + }, + }, + } + goDirectComponent1 = core.Component{ + Type: core.TypeLibrary, + Name: "github.com/aquasecurity/go-direct1", + Version: "2.0.0", + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "04", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/aquasecurity", + Name: "go-direct1", + Version: "2.0.0", + }, + }, + } + goDirectComponent2 = core.Component{ + Type: core.TypeLibrary, + Name: "github.com/aquasecurity/go-direct2", + Version: "3.0.0", + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "05", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/aquasecurity", + Name: "go-direct2", + Version: "3.0.0", + }, + }, + } + goTransitiveComponent = core.Component{ + Type: core.TypeLibrary, + Name: "github.com/aquasecurity/go-transitive", + Version: "4.0.0", + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "06", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "github.com/aquasecurity", + Name: "go-transitive", + Version: "4.0.0", + }, + }, + } + vuln1 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2021-44228", + PkgName: springComponent.Name, + InstalledVersion: springComponent.Version, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: springComponent.PkgIdentifier.UID, + PURL: springComponent.PkgIdentifier.PURL, + }, + } + vuln2 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2021-0001", + PkgName: springComponent.Name, + InstalledVersion: springComponent.Version, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: springComponent.PkgIdentifier.UID, + PURL: springComponent.PkgIdentifier.PURL, + }, + } + vuln3 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2022-3715", + PkgName: bashComponent.Name, + InstalledVersion: bashComponent.Version, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: bashComponent.PkgIdentifier.UID, + PURL: bashComponent.PkgIdentifier.PURL, + }, + } + vuln4 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2024-0001", + PkgName: goTransitiveComponent.Name, + InstalledVersion: goTransitiveComponent.Version, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: goTransitiveComponent.PkgIdentifier.UID, + PURL: goTransitiveComponent.PkgIdentifier.PURL, + }, + } ) func TestMain(m *testing.M) { @@ -87,7 +196,7 @@ func TestVEX_Filter(t *testing.T) { }, args: args{ vulns: []types.DetectedVulnerability{vuln1}, - bom: newTestBOM(), + bom: newTestBOM1(), }, want: []types.DetectedVulnerability{}, }, @@ -101,7 +210,7 @@ func TestVEX_Filter(t *testing.T) { vuln1, // filtered by VEX vuln2, }, - bom: newTestBOM(), + bom: newTestBOM1(), }, want: []types.DetectedVulnerability{ vuln2, @@ -116,7 +225,7 @@ func TestVEX_Filter(t *testing.T) { vulns: []types.DetectedVulnerability{ vuln3, }, - bom: newTestBOM(), + bom: newTestBOM1(), }, want: []types.DetectedVulnerability{}, }, @@ -131,6 +240,28 @@ func TestVEX_Filter(t *testing.T) { }, want: []types.DetectedVulnerability{vuln3}, }, + { + name: "OpenVEX, single path between product and subcomponent", + fields: fields{ + filePath: "testdata/openvex-nested.json", + }, + args: args{ + vulns: []types.DetectedVulnerability{vuln4}, + bom: newTestBOM3(), + }, + want: []types.DetectedVulnerability{}, + }, + { + name: "OpenVEX, multi paths between product and subcomponent", + fields: fields{ + filePath: "testdata/openvex-nested.json", + }, + args: args{ + vulns: []types.DetectedVulnerability{vuln4}, + bom: newTestBOM4(), + }, + want: []types.DetectedVulnerability{vuln4}, // Will not be filtered because of multi paths + }, { name: "CycloneDX SBOM with CycloneDX VEX", fields: fields{ @@ -373,30 +504,16 @@ func TestVEX_Filter(t *testing.T) { } } -func newTestBOM() *core.BOM { - bom := core.NewBOM(core.Options{}) - bom.AddComponent(&core.Component{ - Root: true, - Type: core.TypeContainerImage, - Name: "debian:12", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeOCI, - Name: "debian", - Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", - Qualifiers: packageurl.Qualifiers{ - { - Key: "tag", - Value: "12", - }, - { - Key: "repository_url", - Value: "docker.io/library/debian", - }, - }, - }, - }, - }) +func newTestBOM1() *core.BOM { + // - oci:debian?tag=12 + // - pkg:maven/org.springframework.boot/spring-boot@2.6.0 + // - pkg:deb/debian/bash@5.3 + bom := core.NewBOM(core.Options{Parents: true}) + bom.AddComponent(&ociComponent) + bom.AddComponent(&springComponent) + bom.AddComponent(&bashComponent) + bom.AddRelationship(&ociComponent, &springComponent, core.RelationshipContains) + bom.AddRelationship(&ociComponent, &bashComponent, core.RelationshipContains) return bom } @@ -426,3 +543,40 @@ func newTestBOM2() *core.BOM { }) return bom } + +func newTestBOM3() *core.BOM { + // - filesystem + // - pkg:golang/github.com/aquasecurity/go-module@1.0.0 + // - pkg:golang/github.com/aquasecurity/go-direct1@2.0.0 + // - pkg:golang/github.com/aquasecurity/go-transitive@4.0.0 + bom := core.NewBOM(core.Options{Parents: true}) + bom.AddComponent(&fsComponent) + bom.AddComponent(&goModuleComponent) + bom.AddComponent(&goDirectComponent1) + bom.AddComponent(&goTransitiveComponent) + bom.AddRelationship(&fsComponent, &goModuleComponent, core.RelationshipContains) + bom.AddRelationship(&goModuleComponent, &goDirectComponent1, core.RelationshipDependsOn) + bom.AddRelationship(&goDirectComponent1, &goTransitiveComponent, core.RelationshipDependsOn) + return bom +} + +func newTestBOM4() *core.BOM { + // - filesystem + // - pkg:golang/github.com/aquasecurity/go-module@2.0.0 + // - pkg:golang/github.com/aquasecurity/go-direct1@3.0.0 + // - pkg:golang/github.com/aquasecurity/go-transitive@5.0.0 + // - pkg:golang/github.com/aquasecurity/go-direct2@4.0.0 + // - pkg:golang/github.com/aquasecurity/go-transitive@5.0.0 + bom := core.NewBOM(core.Options{Parents: true}) + bom.AddComponent(&fsComponent) + bom.AddComponent(&goModuleComponent) + bom.AddComponent(&goDirectComponent1) + bom.AddComponent(&goDirectComponent2) + bom.AddComponent(&goTransitiveComponent) + bom.AddRelationship(&fsComponent, &goModuleComponent, core.RelationshipContains) + bom.AddRelationship(&goModuleComponent, &goDirectComponent1, core.RelationshipDependsOn) + bom.AddRelationship(&goModuleComponent, &goDirectComponent2, core.RelationshipDependsOn) + bom.AddRelationship(&goDirectComponent1, &goTransitiveComponent, core.RelationshipDependsOn) + bom.AddRelationship(&goDirectComponent2, &goTransitiveComponent, core.RelationshipDependsOn) + return bom +} From 1e0864842e32a709941d4b4e8f521602bcee684d Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 21 May 2024 17:23:26 +0600 Subject: [PATCH 086/352] feat(nodejs): add v9 pnpm lock file support (#6617) --- docs/docs/coverage/language/nodejs.md | 16 +- pkg/dependency/parser/nodejs/pnpm/parse.go | 278 +++++++++++++++--- .../parser/nodejs/pnpm/parse_test.go | 113 +++++-- .../parser/nodejs/pnpm/parse_testcase.go | 172 ++++++++++- .../nodejs/pnpm/testdata/pnpm-lock_v9.yaml | 191 ++++++++++++ 5 files changed, 698 insertions(+), 72 deletions(-) create mode 100644 pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml diff --git a/docs/docs/coverage/language/nodejs.md b/docs/docs/coverage/language/nodejs.md index addeb484867e..ded9ea144535 100644 --- a/docs/docs/coverage/language/nodejs.md +++ b/docs/docs/coverage/language/nodejs.md @@ -13,12 +13,12 @@ The following scanners are supported. The following table provides an outline of the features Trivy offers. -| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | -|:---------------:|-------------------|:-----------------------:|:-----------------:|:------------------------------------:|:--------:| -| npm | package-lock.json | ✓ | [Excluded](#npm) | ✓ | ✓ | -| Yarn | yarn.lock | ✓ | [Excluded](#yarn) | ✓ | ✓ | -| pnpm | pnpm-lock.yaml | ✓ | Excluded | ✓ | - | -| Bun | yarn.lock | ✓ | [Excluded](#yarn) | ✓ | ✓ | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|:---------------:|-------------------|:-----------------------:|:---------------------------------:|:------------------------------------:|:--------:| +| npm | package-lock.json | ✓ | [Excluded](#npm) | ✓ | ✓ | +| Yarn | yarn.lock | ✓ | [Excluded](#yarn) | ✓ | ✓ | +| pnpm | pnpm-lock.yaml | ✓ | [Excluded](#lock-file-v9-version) | ✓ | - | +| Bun | yarn.lock | ✓ | [Excluded](#yarn) | ✓ | ✓ | In addition, Trivy scans installed packages with `package.json`. @@ -55,8 +55,8 @@ By default, Trivy doesn't report development dependencies. Use the `--include-de ### pnpm Trivy parses `pnpm-lock.yaml`, then finds production dependencies and builds a [tree][dependency-graph] of dependencies with vulnerabilities. -!!! note - Trivy currently only supports Lockfile [v6][pnpm-lockfile-v6] or earlier. +#### lock file v9 version +Trivy supports `Dev` field for `pnpm-lock.yaml` v9 or later. Use the `--include-dev-deps` flag to include the developer's dependencies in the result. ### Bun Trivy supports scanning `yarn.lock` files generated by [Bun](https://bun.sh/docs/install/lockfile#how-do-i-inspect-bun-s-lockfile). You can use the command `bun install -y` to generate a Yarn-compatible `yarn.lock`. diff --git a/pkg/dependency/parser/nodejs/pnpm/parse.go b/pkg/dependency/parser/nodejs/pnpm/parse.go index 92fdc6131744..00c573a4c4db 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse.go @@ -2,10 +2,12 @@ package pnpm import ( "fmt" + "sort" "strconv" "strings" "github.com/samber/lo" + "golang.org/x/exp/maps" "golang.org/x/xerrors" "gopkg.in/yaml.v3" @@ -34,6 +36,28 @@ type LockFile struct { Dependencies map[string]any `yaml:"dependencies,omitempty"` DevDependencies map[string]any `yaml:"devDependencies,omitempty"` Packages map[string]PackageInfo `yaml:"packages,omitempty"` + + // V9 + Importers Importer `yaml:"importers,omitempty"` + Snapshots map[string]Snapshot `yaml:"snapshots,omitempty"` +} + +type Importer struct { + Root RootImporter `yaml:".,omitempty"` +} + +type RootImporter struct { + Dependencies map[string]ImporterDepVersion `yaml:"dependencies,omitempty"` + DevDependencies map[string]ImporterDepVersion `yaml:"devDependencies,omitempty"` +} + +type ImporterDepVersion struct { + Version string `yaml:"version,omitempty"` +} + +type Snapshot struct { + Dependencies map[string]string `yaml:"dependencies,omitempty"` + OptionalDependencies map[string]string `yaml:"optionalDependencies,omitempty"` } type Parser struct { @@ -57,8 +81,16 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc return nil, nil, nil } - pkgs, deps := p.parse(lockVer, lockFile) + var pkgs []ftypes.Package + var deps []ftypes.Dependency + if lockVer >= 9 { + pkgs, deps = p.parseV9(lockFile) + } else { + pkgs, deps = p.parse(lockVer, lockFile) + } + sort.Sort(ftypes.Packages(pkgs)) + sort.Sort(ftypes.Dependencies(deps)) return pkgs, deps, nil } @@ -78,9 +110,11 @@ func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]ftypes.Package, [] // cf. https://github.com/pnpm/spec/blob/274ff02de23376ad59773a9f25ecfedd03a41f64/lockfile/6.0.md#packagesdependencypathname name := info.Name version := info.Version + var ref string if name == "" { - name, version = p.parsePackage(depPath, lockVer) + name, version, ref = p.parseDepPath(depPath, lockVer) + version = p.parseVersion(depPath, version, lockVer) } pkgID := packageID(name, version) @@ -90,13 +124,15 @@ func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]ftypes.Package, [] } pkgs = append(pkgs, ftypes.Package{ - ID: pkgID, - Name: name, - Version: version, - Relationship: lo.Ternary(isDirectPkg(name, lockFile.Dependencies), ftypes.RelationshipDirect, ftypes.RelationshipIndirect), + ID: pkgID, + Name: name, + Version: version, + Relationship: lo.Ternary(isDirectPkg(name, lockFile.Dependencies), ftypes.RelationshipDirect, ftypes.RelationshipIndirect), + ExternalReferences: toExternalRefs(ref), }) if len(dependencies) > 0 { + sort.Strings(dependencies) deps = append(deps, ftypes.Dependency{ ID: pkgID, DependsOn: dependencies, @@ -107,6 +143,98 @@ func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]ftypes.Package, [] return pkgs, deps } +func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependency) { + lockVer := 9.0 + resolvedPkgs := make(map[string]ftypes.Package) + resolvedDeps := make(map[string]ftypes.Dependency) + + // Check all snapshots and save with resolved versions + resolvedSnapshots := make(map[string][]string) + for depPath, snapshot := range lockFile.Snapshots { + name, version, _ := p.parseDepPath(depPath, lockVer) + + var dependsOn []string + for depName, depVer := range lo.Assign(snapshot.OptionalDependencies, snapshot.Dependencies) { + depVer = p.trimPeerDeps(depVer, lockVer) // pnpm has already separated dep name. therefore, we only need to separate peer deps. + depVer = p.parseVersion(depPath, depVer, lockVer) + id := packageID(depName, depVer) + if _, ok := lockFile.Packages[id]; ok { + dependsOn = append(dependsOn, id) + } + } + if len(dependsOn) > 0 { + resolvedSnapshots[packageID(name, version)] = dependsOn + } + + } + + for depPath, pkgInfo := range lockFile.Packages { + name, ver, ref := p.parseDepPath(depPath, lockVer) + parsedVer := p.parseVersion(depPath, ver, lockVer) + + if pkgInfo.Version != "" { + parsedVer = pkgInfo.Version + } + + // By default, pkg is dev pkg. + // We will update `Dev` field later. + dev := true + relationship := ftypes.RelationshipIndirect + if dep, ok := lockFile.Importers.Root.DevDependencies[name]; ok && dep.Version == ver { + relationship = ftypes.RelationshipDirect + } + if dep, ok := lockFile.Importers.Root.Dependencies[name]; ok && dep.Version == ver { + relationship = ftypes.RelationshipDirect + dev = false // mark root direct deps to update `dev` field of their child deps. + } + + id := packageID(name, parsedVer) + resolvedPkgs[id] = ftypes.Package{ + ID: id, + Name: name, + Version: parsedVer, + Relationship: relationship, + Dev: dev, + ExternalReferences: toExternalRefs(ref), + } + + // Save child deps + if dependsOn, ok := resolvedSnapshots[depPath]; ok { + sort.Strings(dependsOn) + resolvedDeps[id] = ftypes.Dependency{ + ID: id, + DependsOn: dependsOn, // Deps from dependsOn has been resolved when parsing snapshots + } + } + } + + // Overwrite the `Dev` field for dev deps and their child dependencies. + for _, pkg := range resolvedPkgs { + if !pkg.Dev { + p.markRootPkgs(pkg.ID, resolvedPkgs, resolvedDeps) + } + } + + return maps.Values(resolvedPkgs), maps.Values(resolvedDeps) +} + +// markRootPkgs sets `Dev` to false for non dev dependency. +func (p *Parser) markRootPkgs(id string, pkgs map[string]ftypes.Package, deps map[string]ftypes.Dependency) { + pkg, ok := pkgs[id] + if !ok { + return + } + + pkg.Dev = false + pkgs[id] = pkg + + // Update child deps + for _, depID := range deps[id].DependsOn { + p.markRootPkgs(depID, pkgs, deps) + } + return +} + func (p *Parser) parseLockfileVersion(lockFile LockFile) float64 { switch v := lockFile.LockfileVersion.(type) { // v5 @@ -127,55 +255,109 @@ func (p *Parser) parseLockfileVersion(lockFile LockFile) float64 { } } -// cf. https://github.com/pnpm/pnpm/blob/ce61f8d3c29eee46cee38d56ced45aea8a439a53/packages/dependency-path/src/index.ts#L112-L163 -func (p *Parser) parsePackage(depPath string, lockFileVersion float64) (string, string) { - // The version separator is different between v5 and v6+. - versionSep := "@" - if lockFileVersion < 6 { - versionSep = "/" +func (p *Parser) parseDepPath(depPath string, lockVer float64) (string, string, string) { + dPath, nonDefaultRegistry := p.trimRegistry(depPath, lockVer) + + var scope string + scope, dPath = p.separateScope(dPath) + + var name string + name, dPath = p.separateName(dPath, lockVer) + + // add scope to pkg name + if scope != "" { + name = fmt.Sprintf("%s/%s", scope, name) } - return p.parseDepPath(depPath, versionSep) + + ver := p.trimPeerDeps(dPath, lockVer) + + return name, ver, lo.Ternary(nonDefaultRegistry, depPath, "") } -func (p *Parser) parseDepPath(depPath, versionSep string) (string, string) { - // Skip registry - // e.g. - // - "registry.npmjs.org/lodash/4.17.10" => "lodash/4.17.10" - // - "registry.npmjs.org/@babel/generator/7.21.9" => "@babel/generator/7.21.9" - // - "/lodash/4.17.10" => "lodash/4.17.10" - _, depPath, _ = strings.Cut(depPath, "/") +// trimRegistry trims registry (or `/` prefix) for depPath. +// It returns true if non-default registry has been trimmed. +// e.g. +// - "registry.npmjs.org/lodash/4.17.10" => "lodash/4.17.10", false +// - "registry.npmjs.org/@babel/generator/7.21.9" => "@babel/generator/7.21.9", false +// - "private.npm.org/@babel/generator/7.21.9" => "@babel/generator/7.21.9", true +// - "/lodash/4.17.10" => "lodash/4.17.10", false +// - "/asap@2.0.6" => "asap@2.0.6", false +func (p *Parser) trimRegistry(depPath string, lockVer float64) (string, bool) { + var nonDefaultRegistry bool + // lock file v9 doesn't use registry prefix + if lockVer < 9 { + var registry string + registry, depPath, _ = strings.Cut(depPath, "/") + if registry != "" && registry != "registry.npmjs.org" { + nonDefaultRegistry = true + } + } + return depPath, nonDefaultRegistry +} - // Parse scope - // e.g. - // - v5: "@babel/generator/7.21.9" => {"babel", "generator/7.21.9"} - // - v6+: "@babel/helper-annotate-as-pure@7.18.6" => "{"babel", "helper-annotate-as-pure@7.18.6"} +// separateScope separates the scope (if set) from the rest of the depPath. +// e.g. +// - v5: "@babel/generator/7.21.9" => {"babel", "generator/7.21.9"} +// - v6+: "@babel/helper-annotate-as-pure@7.18.6" => "{"babel", "helper-annotate-as-pure@7.18.6"} +func (p *Parser) separateScope(depPath string) (string, string) { var scope string if strings.HasPrefix(depPath, "@") { scope, depPath, _ = strings.Cut(depPath, "/") } + return scope, depPath +} - // Parse package name - // e.g. - // - v5: "generator/7.21.9" => {"generator", "7.21.9"} - // - v6+: "helper-annotate-as-pure@7.18.6" => {"helper-annotate-as-pure", "7.18.6"} - var name, version string - name, version, _ = strings.Cut(depPath, versionSep) - if scope != "" { - name = fmt.Sprintf("%s/%s", scope, name) +// separateName separates pkg name and version. +// e.g. +// - v5: "generator/7.21.9" => {"generator", "7.21.9"} +// - v6+: "7.21.5(@babel/core@7.20.7)" => "7.21.5" +// +// for v9+ version can be filePath or link: +// - "package1@file:package1:" +// - "is-negative@https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5" +// +// Also version can contain peer deps: +// - "debug@4.3.4(supports-color@8.1.1)" +func (p *Parser) separateName(depPath string, lockVer float64) (string, string) { + sep := "@" + if lockVer < 6 { + sep = "/" + } + name, version, _ := strings.Cut(depPath, sep) + return name, version +} + +// Trim peer deps +// e.g. +// - v5: "7.21.5_@babel+core@7.21.8" => "7.21.5" +// - v6+: "7.21.5(@babel/core@7.20.7)" => "7.21.5" +func (p *Parser) trimPeerDeps(depPath string, lockVer float64) string { + sep := "(" + if lockVer < 6 { + sep = "_" } - // Trim peer deps - // e.g. - // - v5: "7.21.5_@babel+core@7.21.8" => "7.21.5" - // - v6+: "7.21.5(@babel/core@7.20.7)" => "7.21.5" - if idx := strings.IndexAny(version, "_("); idx != -1 { - version = version[:idx] + version, _, _ := strings.Cut(depPath, sep) + return version +} + +// parseVersion parses version. +// v9 can use filePath or link as version - we need to clear these versions. +// e.g. +// - "package1@file:package1:" +// - "is-negative@https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5" +// +// Other versions should be semver valid. +func (p *Parser) parseVersion(depPath, ver string, lockVer float64) string { + if lockVer < 9 && (strings.HasPrefix(ver, "file:") || strings.HasPrefix(ver, "http")) { + return "" } - if _, err := semver.Parse(version); err != nil { + if _, err := semver.Parse(ver); err != nil { p.logger.Debug("Skip non-semver package", log.String("pkg_path", depPath), - log.String("version", version), log.Err(err)) - return "", "" + log.String("version", ver), log.Err(err)) + return "" } - return name, version + + return ver } func isDirectPkg(name string, directDeps map[string]interface{}) bool { @@ -186,3 +368,15 @@ func isDirectPkg(name string, directDeps map[string]interface{}) bool { func packageID(name, version string) string { return dependency.ID(ftypes.Pnpm, name, version) } + +func toExternalRefs(ref string) []ftypes.ExternalRef { + if ref == "" { + return nil + } + return []ftypes.ExternalRef{ + { + Type: ftypes.RefVCS, + URL: ref, + }, + } +} diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_test.go b/pkg/dependency/parser/nodejs/pnpm/parse_test.go index b5d8816516fe..7a9caeb1fee9 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_test.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_test.go @@ -5,10 +5,8 @@ import ( "sort" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/require" ) func TestParse(t *testing.T) { @@ -54,6 +52,12 @@ func TestParse(t *testing.T) { want: pnpmV6WithDev, wantDeps: pnpmV6WithDevDeps, }, + { + name: "v9", + file: "testdata/pnpm-lock_v9.yaml", + want: pnpmV9, + wantDeps: pnpmV9Deps, + }, } for _, tt := range tests { @@ -66,7 +70,7 @@ func TestParse(t *testing.T) { sort.Sort(ftypes.Packages(got)) sort.Sort(ftypes.Packages(tt.want)) - assert.Equal(t, tt.want, got) + require.Equal(t, tt.want, got) if tt.wantDeps != nil { sort.Sort(ftypes.Dependencies(deps)) @@ -77,19 +81,20 @@ func TestParse(t *testing.T) { for _, dep := range tt.wantDeps { sort.Strings(dep.DependsOn) } - assert.Equal(t, tt.wantDeps, deps) + require.Equal(t, tt.wantDeps, deps) } }) } } -func Test_parsePackage(t *testing.T) { +func Test_parseDepPath(t *testing.T) { tests := []struct { name string lockFileVer float64 pkg string wantName string wantVersion string + wantRef string }{ { name: "v5 - relative path", @@ -105,6 +110,14 @@ func Test_parsePackage(t *testing.T) { wantName: "lodash", wantVersion: "4.17.10", }, + { + name: "v5 - non-default registry", + lockFileVer: 5.0, + pkg: "private.npmjs.org/lodash/4.17.10", + wantName: "lodash", + wantVersion: "4.17.10", + wantRef: "private.npmjs.org/lodash/4.17.10", + }, { name: "v5 - relative path with slash", lockFileVer: 5.0, @@ -140,13 +153,6 @@ func Test_parsePackage(t *testing.T) { wantName: "@babel/helper-compilation-targets", wantVersion: "7.21.5", }, - { - name: "v5 - relative path with wrong version", - lockFileVer: 5.0, - pkg: "/lodash/4-wrong", - wantName: "", - wantVersion: "", - }, { name: "v6 - relative path", lockFileVer: 6.0, @@ -161,6 +167,14 @@ func Test_parsePackage(t *testing.T) { wantName: "lodash", wantVersion: "4.17.10", }, + { + name: "v6 - non-default registry", + lockFileVer: 6.0, + pkg: "private.npmjs.org/lodash@4.17.10", + wantName: "lodash", + wantVersion: "4.17.10", + wantRef: "private.npmjs.org/lodash@4.17.10", + }, { name: "v6 - relative path with slash", lockFileVer: 6.0, @@ -190,20 +204,77 @@ func Test_parsePackage(t *testing.T) { wantVersion: "7.21.5", }, { - name: "v6 - relative path with wrong version", - lockFileVer: 6.0, - pkg: "/lodash@4-wrong", - wantName: "", - wantVersion: "", + name: "v9 - scope and peer deps", + lockFileVer: 9.0, + pkg: "@babel/helper-compilation-targets@7.21.5(@babel/core@7.20.7)", + wantName: "@babel/helper-compilation-targets", + wantVersion: "7.21.5", + }, + { + name: "v9 - filePath as version", + lockFileVer: 9.0, + pkg: "lodash@file:foo/bar/lodash.tgz", + wantName: "lodash", + wantVersion: "file:foo/bar/lodash.tgz", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NewParser() + gotName, gotVersion, gotRef := p.parseDepPath(tt.pkg, tt.lockFileVer) + require.Equal(t, tt.wantName, gotName) + require.Equal(t, tt.wantVersion, gotVersion) + require.Equal(t, tt.wantRef, gotRef) + }) + + } +} + +func Test_parseVersion(t *testing.T) { + tests := []struct { + name string + ver string + lockVer float64 + wantVer string + }{ + { + name: "happy path", + ver: "0.0.1", + lockVer: 5.0, + wantVer: "0.0.1", + }, + { + name: "v6 version is file", + ver: "file:foo/bar/lodash.tgz", + lockVer: 6.0, + wantVer: "", + }, + { + name: "v9 version is file", + ver: "file:foo/bar/lodash.tgz", + lockVer: 9.0, + wantVer: "", + }, + { + name: "v6 version is url", + ver: "https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5", + lockVer: 6.0, + wantVer: "", + }, + { + name: "v9 version is url", + ver: "https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5", + lockVer: 9.0, + wantVer: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := NewParser() - gotName, gotVersion := p.parsePackage(tt.pkg, tt.lockFileVer) - assert.Equal(t, tt.wantName, gotName) - assert.Equal(t, tt.wantVersion, gotVersion) + gotVer := p.parseVersion("depPath", tt.ver, tt.lockVer) + require.Equal(t, tt.wantVer, gotVer) }) } diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go index 45eb7a7202e7..52b69ce79ee3 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go @@ -1,6 +1,8 @@ package pnpm -import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +import ( + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) var ( // docker run --name node --rm -it node:16-alpine sh @@ -730,4 +732,172 @@ var ( DependsOn: []string{"@babel/runtime@7.22.3"}, }, } + + pnpmV9 = []ftypes.Package{ + { + ID: "@babel/helper-string-parser@7.24.1", + Name: "@babel/helper-string-parser", + Version: "7.24.1", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "asap@2.0.6", + Name: "asap", + Version: "2.0.6", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "asynckit@0.4.0", + Name: "asynckit", + Version: "0.4.0", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "debug@4.3.4", + Name: "debug", + Version: "4.3.4", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "debug@4.3.5", + Name: "debug", + Version: "4.3.5", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "ee-first@1.1.1", + Name: "ee-first", + Version: "1.1.1", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "encodeurl@1.0.2", + Name: "encodeurl", + Version: "1.0.2", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "escape-html@1.0.3", + Name: "escape-html", + Version: "1.0.3", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "finalhandler@1.1.1", + Name: "finalhandler", + Version: "1.1.1", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "is-negative@2.0.1", + Name: "is-negative", + Version: "2.0.1", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "jquery@3.6.0", + Name: "jquery", + Version: "3.6.0", + Dev: true, + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "lodash@4.17.21", + Name: "lodash", + Version: "4.17.21", + Dev: true, + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "ms@2.1.2", + Name: "ms", + Version: "2.1.2", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "on-finished@2.3.0", + Name: "on-finished", + Version: "2.3.0", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "package1", + Name: "package1", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "parseurl@1.3.3", + Name: "parseurl", + Version: "1.3.3", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "promise@8.1.0", + Name: "promise", + Version: "8.1.0", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "statuses@1.4.0", + Name: "statuses", + Version: "1.4.0", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "unpipe@1.0.0", + Name: "unpipe", + Version: "1.0.0", + Relationship: ftypes.RelationshipIndirect, + }, + } + pnpmV9Deps = []ftypes.Dependency{ + { + ID: "debug@4.3.4", + DependsOn: []string{ + "ms@2.0.0", + }, + }, + { + ID: "debug@4.3.5", + DependsOn: []string{ + "ms@2.1.2", + }, + }, + { + ID: "finalhandler@1.1.1", + DependsOn: []string{ + "debug@4.3.4", + "encodeurl@1.0.2", + "escape-html@1.0.3", + "on-finished@2.3.0", + "parseurl@1.3.3", + "statuses@1.4.0", + "unpipe@1.0.0", + }, + }, + { + ID: "on-finished@2.3.0", + DependsOn: []string{ + "ee-first@1.1.1", + }, + }, + { + ID: "package1", + DependsOn: []string{ + "asynckit@0.4.0", + }, + }, + { + ID: "promise@8.1.0", + DependsOn: []string{ + "asap@2.0.6", + }, + }, + } ) diff --git a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml new file mode 100644 index 000000000000..12a61f02b79a --- /dev/null +++ b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml @@ -0,0 +1,191 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@babel/helper-string-parser': + specifier: 7.24.1 + version: 7.24.1 + debug: + specifier: https://github.com/debug-js/debug/tarball/4.3.5 + version: https://github.com/debug-js/debug/tarball/4.3.5 + finalhandler: + specifier: 1.1.1 + version: 1.1.1 + is-negative: + specifier: https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5 + version: https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5 + on-finished: + specifier: 2.3.0 + version: 2.3.0 + package1: + specifier: file:package1 + version: file:package1 + promise: + specifier: 8.1.0 + version: 8.1.0 + devDependencies: + jquery: + specifier: 3.6.0 + version: 3.6.0 + lodash: + specifier: file:foo/bar/lodash.tgz + version: file:foo/bar/lodash.tgz + ms: + specifier: 2.0.0 + version: 2.0.0 + +packages: + + '@babel/helper-string-parser@7.24.1': + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + engines: {node: '>=6.9.0'} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@https://github.com/debug-js/debug/tarball/4.3.5: + resolution: {tarball: https://github.com/debug-js/debug/tarball/4.3.5} + version: 4.3.5 + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + finalhandler@1.1.1: + resolution: {integrity: sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==} + engines: {node: '>= 0.8'} + + is-negative@https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5: + resolution: {tarball: https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5} + version: 2.0.1 + engines: {node: '>=0.10.0'} + + jquery@3.6.0: + resolution: {integrity: sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==} + + lodash@file:foo/bar/lodash.tgz: + resolution: {integrity: sha512-D2TlgcJ2ZQKKj4HVqOFkR+QETqRCXu+TCyxy7qH5QhxuJXCulHzBqmz4WrHA5Fs5ryY5RJYUqw2JBnbzveCtlA==, tarball: file:foo/bar/lodash.tgz} + version: 4.17.21 + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + package1@file:package1: + resolution: {directory: package1, type: directory} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + promise@8.1.0: + resolution: {integrity: sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==} + + statuses@1.4.0: + resolution: {integrity: sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==} + engines: {node: '>= 0.6'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + +snapshots: + + '@babel/helper-string-parser@7.24.1': {} + + asap@2.0.6: + optional: true + + asynckit@0.4.0: {} + + debug@4.3.4(supports-color@8.1.1): + dependencies: + ms: 2.0.0 + optionalDependencies: + supports-color: 8.1.1 + + debug@https://github.com/debug-js/debug/tarball/4.3.5: + dependencies: + ms: 2.1.2 + + ee-first@1.1.1: {} + + encodeurl@1.0.2: {} + + escape-html@1.0.3: {} + + finalhandler@1.1.1: + dependencies: + debug: 4.3.4(supports-color@8.1.1) + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.4.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + is-negative@https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5: {} + + jquery@3.6.0: {} + + lodash@file:foo/bar/lodash.tgz: {} + + ms@2.0.0: {} + + ms@2.1.2: {} + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + package1@file:package1: + dependencies: + asynckit: 0.4.0 + + parseurl@1.3.3: {} + + promise@8.1.0: + optionalDependencies: + asap: 2.0.6 + + statuses@1.4.0: {} + + unpipe@1.0.0: {} From 48bdc6e7347ed94f2f84d7edfaaa66d627a1f53d Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 22 May 2024 04:55:16 +0200 Subject: [PATCH 087/352] ci(deps): fix gci and gofmt in ".*_test.go$" (#6721) Signed-off-by: Matthieu MOREL --- .golangci.yaml | 2 -- pkg/cloud/aws/commands/run_test.go | 3 +-- pkg/cloud/report/service_test.go | 3 +-- pkg/dependency/id_test.go | 6 ++++-- pkg/dependency/parser/gradle/lockfile/parse_test.go | 3 ++- pkg/dependency/parser/hex/mix/parse_test.go | 3 ++- pkg/dependency/parser/java/jar/parse_test.go | 2 +- pkg/dependency/parser/nodejs/pnpm/parse_test.go | 3 ++- pkg/dependency/parser/php/composer/parse_test.go | 8 +++++--- pkg/dependency/parser/swift/cocoapods/parse_test.go | 5 +++-- pkg/dependency/parser/swift/swift/parse_test.go | 3 ++- pkg/dependency/parser/utils/utils_test.go | 6 ++++-- pkg/detector/ospkg/alma/alma_test.go | 6 +++--- pkg/detector/ospkg/alpine/alpine_test.go | 5 +++-- pkg/detector/ospkg/amazon/amazon_test.go | 6 +++--- pkg/detector/ospkg/debian/debian_test.go | 6 +++--- pkg/detector/ospkg/oracle/oracle_test.go | 6 +++--- pkg/detector/ospkg/photon/photon_test.go | 6 +++--- pkg/detector/ospkg/redhat/redhat_test.go | 7 ++++--- pkg/detector/ospkg/rocky/rocky_test.go | 6 +++--- pkg/detector/ospkg/suse/suse_test.go | 6 +++--- pkg/detector/ospkg/ubuntu/ubuntu_test.go | 6 +++--- pkg/fanal/analyzer/analyzer_test.go | 2 +- .../language/conda/environment/environment_test.go | 10 ++++++---- pkg/fanal/analyzer/language/java/gradle/pom_test.go | 3 ++- pkg/fanal/analyzer/language/java/jar/jar_test.go | 3 +-- pkg/fanal/analyzer/language/julia/pkg/pkg_test.go | 5 +++-- pkg/fanal/analyzer/pkg/rpm/rpm_test.go | 6 +++--- pkg/fanal/analyzer/sbom/sbom_test.go | 2 +- pkg/fanal/artifact/image/remote_sbom_test.go | 2 +- pkg/fanal/artifact/local/fs_test.go | 7 ++++--- pkg/fanal/artifact/repo/git_test.go | 5 +++-- pkg/fanal/artifact/sbom/sbom_test.go | 2 +- pkg/fanal/cache/key_test.go | 3 +-- pkg/fanal/handler/unpackaged/unpackaged_test.go | 2 +- pkg/fanal/image/registry/ecr/ecr_test.go | 4 ++-- pkg/fanal/secret/scanner_test.go | 5 +++-- pkg/fanal/walker/fs_test.go | 3 +-- pkg/fanal/walker/walk_test.go | 3 ++- pkg/flag/db_flags_test.go | 7 ++++--- pkg/flag/options_test.go | 13 +++++++------ pkg/flag/remote_flags_test.go | 7 ++++--- pkg/flag/report_flags_test.go | 8 ++++---- pkg/flag/scan_flags_test.go | 2 +- pkg/flag/vulnerability_flags_test.go | 9 +++++---- pkg/iac/adapters/arm/compute/adapt_test.go | 6 +++--- pkg/iac/adapters/arm/storage/adapt_test.go | 5 ++--- pkg/iac/adapters/cloudformation/aws/ecr/ecr_test.go | 3 ++- pkg/iac/adapters/cloudformation/aws/iam/iam_test.go | 3 ++- pkg/iac/adapters/cloudformation/aws/sam/sam_test.go | 3 ++- pkg/iac/adapters/cloudformation/aws/sqs/sqs_test.go | 3 ++- .../adapters/terraform/aws/apigateway/adapt_test.go | 5 +++-- pkg/iac/adapters/terraform/aws/athena/adapt_test.go | 9 ++++----- .../adapters/terraform/aws/cloudfront/adapt_test.go | 9 ++++----- .../adapters/terraform/aws/cloudtrail/adapt_test.go | 9 ++++----- .../adapters/terraform/aws/cloudwatch/adapt_test.go | 9 ++++----- .../adapters/terraform/aws/codebuild/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/config/adapt_test.go | 7 +++---- .../adapters/terraform/aws/documentdb/adapt_test.go | 9 ++++----- .../adapters/terraform/aws/dynamodb/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/ec2/adapt_test.go | 9 ++++----- .../adapters/terraform/aws/ec2/autoscaling_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/ec2/subnet_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/ec2/volume_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/ec2/vpc_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/ecr/adapt_test.go | 11 +++++------ pkg/iac/adapters/terraform/aws/ecs/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/efs/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/eks/adapt_test.go | 9 ++++----- .../terraform/aws/elasticache/adapt_test.go | 9 ++++----- .../terraform/aws/elasticsearch/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/elb/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/emr/adapt_test.go | 8 ++++---- pkg/iac/adapters/terraform/aws/iam/adapt_test.go | 3 ++- pkg/iac/adapters/terraform/aws/iam/groups_test.go | 3 +-- .../adapters/terraform/aws/iam/passwords_test.go | 3 +-- pkg/iac/adapters/terraform/aws/iam/policies_test.go | 6 +++--- pkg/iac/adapters/terraform/aws/iam/roles_test.go | 3 ++- .../adapters/terraform/aws/kinesis/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/kms/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/lambda/adapt_test.go | 8 ++++---- pkg/iac/adapters/terraform/aws/mq/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/msk/adapt_test.go | 9 ++++----- .../adapters/terraform/aws/neptune/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/rds/adapt_test.go | 8 ++++---- .../adapters/terraform/aws/redshift/adapt_test.go | 8 ++++---- pkg/iac/adapters/terraform/aws/s3/adapt_test.go | 10 +++++----- pkg/iac/adapters/terraform/aws/s3/bucket_test.go | 4 ++-- pkg/iac/adapters/terraform/aws/sns/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/aws/sqs/adapt_test.go | 11 +++++------ pkg/iac/adapters/terraform/aws/ssm/adapt_test.go | 9 ++++----- .../adapters/terraform/aws/workspaces/adapt_test.go | 9 ++++----- .../terraform/azure/appservice/adapt_test.go | 9 ++++----- .../terraform/azure/authorization/adapt_test.go | 9 ++++----- .../adapters/terraform/azure/compute/adapt_test.go | 9 ++++----- .../terraform/azure/container/adapt_test.go | 9 ++++----- .../adapters/terraform/azure/database/adapt_test.go | 8 ++++---- .../terraform/azure/datafactory/adapt_test.go | 9 ++++----- .../adapters/terraform/azure/datalake/adapt_test.go | 9 ++++----- .../adapters/terraform/azure/keyvault/adapt_test.go | 9 ++++----- .../adapters/terraform/azure/monitor/adapt_test.go | 9 ++++----- .../adapters/terraform/azure/network/adapt_test.go | 8 ++++---- .../terraform/azure/securitycenter/adapt_test.go | 9 ++++----- .../adapters/terraform/azure/storage/adapt_test.go | 8 ++++---- .../adapters/terraform/azure/synapse/adapt_test.go | 9 ++++----- .../terraform/cloudstack/compute/adapt_test.go | 9 ++++----- .../terraform/digitalocean/compute/adapt_test.go | 8 ++++---- .../terraform/digitalocean/spaces/adapt_test.go | 9 ++++----- .../github/branch_protections/adapt_test.go | 4 ++-- .../terraform/github/repositories/adapt_test.go | 4 ++-- .../adapters/terraform/github/secrets/adapt_test.go | 3 +-- .../terraform/google/bigquery/adapt_test.go | 9 ++++----- .../adapters/terraform/google/compute/adapt_test.go | 3 ++- .../adapters/terraform/google/compute/disks_test.go | 3 +-- .../terraform/google/compute/instances_test.go | 3 +-- .../terraform/google/compute/metadata_test.go | 3 +-- .../terraform/google/compute/networks_test.go | 3 +-- .../adapters/terraform/google/compute/ssl_test.go | 3 +-- pkg/iac/adapters/terraform/google/dns/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/google/gke/adapt_test.go | 8 ++++---- pkg/iac/adapters/terraform/google/iam/adapt_test.go | 8 ++++---- .../terraform/google/iam/project_iam_test.go | 3 +-- pkg/iac/adapters/terraform/google/kms/adapt_test.go | 9 ++++----- pkg/iac/adapters/terraform/google/sql/adapt_test.go | 9 ++++----- .../adapters/terraform/google/storage/adapt_test.go | 8 ++++---- .../terraform/nifcloud/computing/adapt_test.go | 4 ++-- .../terraform/nifcloud/computing/instance_test.go | 3 +-- .../nifcloud/computing/security_group_test.go | 3 +-- .../adapters/terraform/nifcloud/dns/adapt_test.go | 4 ++-- .../adapters/terraform/nifcloud/nas/adapt_test.go | 4 ++-- .../terraform/nifcloud/nas/nas_instance_test.go | 3 +-- .../nifcloud/nas/nas_security_group_test.go | 3 +-- .../terraform/nifcloud/network/adapt_test.go | 4 ++-- .../adapters/terraform/nifcloud/rdb/adapt_test.go | 4 ++-- .../terraform/nifcloud/rdb/db_instance_test.go | 3 +-- .../nifcloud/rdb/db_security_group_test.go | 3 +-- .../terraform/nifcloud/sslcertificate/adapt_test.go | 4 ++-- pkg/iac/adapters/terraform/openstack/adapt_test.go | 9 ++++----- .../adapters/terraform/openstack/networking_test.go | 4 ++-- pkg/iac/ignore/rule_test.go | 3 ++- pkg/iac/rego/convert/slice_test.go | 4 ++-- pkg/iac/rego/embed_test.go | 7 ++++--- pkg/iac/rego/load_test.go | 2 +- pkg/iac/rego/metadata_test.go | 5 +++-- pkg/iac/rego/scanner_test.go | 4 ++-- pkg/iac/rules/register_test.go | 5 +++-- pkg/iac/scan/result_test.go | 3 ++- .../scanners/azure/arm/parser/armjson/bench_test.go | 4 ++-- .../azure/arm/parser/armjson/decode_meta_test.go | 4 ++-- .../azure/arm/parser/armjson/parse_array_test.go | 4 ++-- .../azure/arm/parser/armjson/parse_boolean_test.go | 4 ++-- .../azure/arm/parser/armjson/parse_complex_test.go | 4 ++-- .../azure/arm/parser/armjson/parse_null_test.go | 4 ++-- .../azure/arm/parser/armjson/parse_number_test.go | 4 ++-- .../azure/arm/parser/armjson/parse_object_test.go | 4 ++-- .../azure/arm/parser/armjson/parse_string_test.go | 4 ++-- pkg/iac/scanners/azure/arm/parser/parser_test.go | 6 +++--- pkg/iac/scanners/azure/arm/parser/template_test.go | 5 +++-- pkg/iac/scanners/azure/resolver/resolver_test.go | 3 ++- pkg/iac/scanners/azure/value_test.go | 3 ++- .../scanners/cloudformation/parser/fn_and_test.go | 6 +++--- .../cloudformation/parser/fn_base64_test.go | 7 ++++--- .../cloudformation/parser/fn_condition_test.go | 3 ++- .../cloudformation/parser/fn_equals_test.go | 6 +++--- .../cloudformation/parser/fn_find_in_map_test.go | 4 ++-- .../cloudformation/parser/fn_get_attr_test.go | 4 ++-- .../scanners/cloudformation/parser/fn_if_test.go | 6 +++--- .../scanners/cloudformation/parser/fn_join_test.go | 6 +++--- .../cloudformation/parser/fn_length_test.go | 3 ++- .../scanners/cloudformation/parser/fn_not_test.go | 6 +++--- .../scanners/cloudformation/parser/fn_or_test.go | 6 +++--- .../scanners/cloudformation/parser/fn_ref_test.go | 6 +++--- .../scanners/cloudformation/parser/fn_split_test.go | 7 ++++--- .../scanners/cloudformation/parser/parser_test.go | 3 ++- .../cloudformation/parser/property_helpers_test.go | 3 ++- .../scanners/cloudformation/parser/resource_test.go | 3 ++- pkg/iac/scanners/cloudformation/scanner_test.go | 6 +++--- .../cloudformation/test/cf_scanning_test.go | 2 +- pkg/iac/scanners/dockerfile/scanner_test.go | 5 +++-- pkg/iac/scanners/helm/test/parser_test.go | 5 +++-- pkg/iac/scanners/helm/test/scanner_test.go | 5 +++-- pkg/iac/scanners/json/scanner_test.go | 5 +++-- pkg/iac/scanners/kubernetes/scanner_test.go | 5 +++-- pkg/iac/scanners/terraform/attribute_test.go | 3 ++- pkg/iac/scanners/terraform/count_test.go | 3 ++- pkg/iac/scanners/terraform/deterministic_test.go | 3 ++- .../scanners/terraform/executor/executor_test.go | 5 +++-- pkg/iac/scanners/terraform/fs_test.go | 3 ++- pkg/iac/scanners/terraform/ignore_test.go | 3 ++- pkg/iac/scanners/terraform/module_test.go | 6 +++--- pkg/iac/scanners/terraform/parser/load_vars_test.go | 6 +++--- pkg/iac/scanners/terraform/parser/modules_test.go | 3 ++- .../terraform/parser/parser_integration_test.go | 3 ++- pkg/iac/scanners/terraform/parser/parser_test.go | 7 ++++--- .../scanners/terraform/scanner_integration_test.go | 5 +++-- pkg/iac/scanners/terraform/scanner_test.go | 5 +++-- pkg/iac/scanners/terraform/setup_test.go | 3 ++- .../scanners/terraformplan/snapshot/scanner_test.go | 7 ++++--- .../scanners/terraformplan/tfjson/scanner_test.go | 5 +++-- .../terraformplan/tfjson/test/parser_test.go | 3 ++- .../terraformplan/tfjson/test/scanner_test.go | 4 ++-- pkg/iac/scanners/toml/scanner_test.go | 5 +++-- pkg/iac/scanners/yaml/scanner_test.go | 5 +++-- pkg/iac/state/merge_test.go | 8 +++----- pkg/iac/state/state_test.go | 6 ++---- pkg/iac/types/bool_test.go | 3 +-- pkg/iac/types/fskey_test.go | 1 - pkg/iac/types/string_test.go | 3 +-- pkg/k8s/scanner/scanner_test.go | 11 +++++------ pkg/k8s/writer_test.go | 3 +-- pkg/log/handler_test.go | 6 ++++-- pkg/plugin/index_test.go | 10 ++++++---- pkg/plugin/manager_test.go | 6 +++--- pkg/report/table/secret_test.go | 2 +- pkg/report/table/table_test.go | 3 +-- pkg/result/filter_test.go | 4 ++-- pkg/sbom/cyclonedx/marshal_test.go | 6 +++--- pkg/sbom/cyclonedx/unmarshal_test.go | 8 ++++---- pkg/sbom/io/encode_test.go | 10 ++++++---- pkg/sbom/spdx/marshal_test.go | 6 +++--- pkg/sbom/spdx/unmarshal_test.go | 4 ++-- pkg/scanner/scan_test.go | 2 +- pkg/vex/vex_test.go | 5 ++--- 223 files changed, 625 insertions(+), 629 deletions(-) mode change 100755 => 100644 pkg/iac/types/bool_test.go mode change 100755 => 100644 pkg/iac/types/string_test.go diff --git a/.golangci.yaml b/.golangci.yaml index e22dc0378556..3df1d3809f53 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -106,10 +106,8 @@ issues: - path: ".*_test.go$" linters: - bodyclose - - gci - gocritic - goconst - - gofmt - gosec - govet - ineffassign diff --git a/pkg/cloud/aws/commands/run_test.go b/pkg/cloud/aws/commands/run_test.go index 84528ab4ae6d..325df5330bc5 100644 --- a/pkg/cloud/aws/commands/run_test.go +++ b/pkg/cloud/aws/commands/run_test.go @@ -8,12 +8,11 @@ import ( "testing" "time" - "github.com/aquasecurity/trivy/pkg/clock" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/compliance/spec" "github.com/aquasecurity/trivy/pkg/flag" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" diff --git a/pkg/cloud/report/service_test.go b/pkg/cloud/report/service_test.go index 12c998e23913..8e35bb0194e2 100644 --- a/pkg/cloud/report/service_test.go +++ b/pkg/cloud/report/service_test.go @@ -6,13 +6,12 @@ import ( "testing" "time" - "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/iac/scan" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" diff --git a/pkg/dependency/id_test.go b/pkg/dependency/id_test.go index 173d71c3d726..68e380e6c651 100644 --- a/pkg/dependency/id_test.go +++ b/pkg/dependency/id_test.go @@ -1,10 +1,12 @@ package dependency_test import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/stretchr/testify/assert" - "testing" ) func TestID(t *testing.T) { diff --git a/pkg/dependency/parser/gradle/lockfile/parse_test.go b/pkg/dependency/parser/gradle/lockfile/parse_test.go index beed1ad675cc..521581c2f2cd 100644 --- a/pkg/dependency/parser/gradle/lockfile/parse_test.go +++ b/pkg/dependency/parser/gradle/lockfile/parse_test.go @@ -5,9 +5,10 @@ import ( "sort" "testing" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParser_Parse(t *testing.T) { diff --git a/pkg/dependency/parser/hex/mix/parse_test.go b/pkg/dependency/parser/hex/mix/parse_test.go index d92255614c19..7b2d82d39fc6 100644 --- a/pkg/dependency/parser/hex/mix/parse_test.go +++ b/pkg/dependency/parser/hex/mix/parse_test.go @@ -5,9 +5,10 @@ import ( "sort" "testing" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParser_Parse(t *testing.T) { diff --git a/pkg/dependency/parser/java/jar/parse_test.go b/pkg/dependency/parser/java/jar/parse_test.go index 6813349e9754..ccf1adc9f73d 100644 --- a/pkg/dependency/parser/java/jar/parse_test.go +++ b/pkg/dependency/parser/java/jar/parse_test.go @@ -2,7 +2,6 @@ package jar_test import ( "encoding/json" - "github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar/sonatype" "net/http" "net/http/httptest" "os" @@ -14,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar" + "github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar/sonatype" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_test.go b/pkg/dependency/parser/nodejs/pnpm/parse_test.go index 7a9caeb1fee9..317b468020c7 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_test.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_test.go @@ -5,8 +5,9 @@ import ( "sort" "testing" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/php/composer/parse_test.go b/pkg/dependency/parser/php/composer/parse_test.go index 726ac4676b37..58e19982720d 100644 --- a/pkg/dependency/parser/php/composer/parse_test.go +++ b/pkg/dependency/parser/php/composer/parse_test.go @@ -1,11 +1,13 @@ package composer import ( - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "os" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) var ( diff --git a/pkg/dependency/parser/swift/cocoapods/parse_test.go b/pkg/dependency/parser/swift/cocoapods/parse_test.go index f81b81929654..c6bf397529c4 100644 --- a/pkg/dependency/parser/swift/cocoapods/parse_test.go +++ b/pkg/dependency/parser/swift/cocoapods/parse_test.go @@ -4,10 +4,11 @@ import ( "os" "testing" - "github.com/aquasecurity/trivy/pkg/dependency/parser/swift/cocoapods" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/swift/cocoapods" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/swift/swift/parse_test.go b/pkg/dependency/parser/swift/swift/parse_test.go index 1bedadea29ed..55eba2f08801 100644 --- a/pkg/dependency/parser/swift/swift/parse_test.go +++ b/pkg/dependency/parser/swift/swift/parse_test.go @@ -4,9 +4,10 @@ import ( "os" "testing" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParser_Parse(t *testing.T) { diff --git a/pkg/dependency/parser/utils/utils_test.go b/pkg/dependency/parser/utils/utils_test.go index ed5d84135c19..0610fee0ff38 100644 --- a/pkg/dependency/parser/utils/utils_test.go +++ b/pkg/dependency/parser/utils/utils_test.go @@ -1,9 +1,11 @@ package utils import ( - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/stretchr/testify/require" "testing" + + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestUniqueLibraries(t *testing.T) { diff --git a/pkg/detector/ospkg/alma/alma_test.go b/pkg/detector/ospkg/alma/alma_test.go index ea012609bd94..9c5f3023563a 100644 --- a/pkg/detector/ospkg/alma/alma_test.go +++ b/pkg/detector/ospkg/alma/alma_test.go @@ -5,17 +5,17 @@ import ( "testing" "time" - "github.com/aquasecurity/trivy/pkg/clock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/alma" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestScanner_Detect(t *testing.T) { diff --git a/pkg/detector/ospkg/alpine/alpine_test.go b/pkg/detector/ospkg/alpine/alpine_test.go index 4556592ad83f..4ea5d59024f8 100644 --- a/pkg/detector/ospkg/alpine/alpine_test.go +++ b/pkg/detector/ospkg/alpine/alpine_test.go @@ -6,6 +6,9 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" @@ -14,8 +17,6 @@ import ( "github.com/aquasecurity/trivy/pkg/detector/ospkg/alpine" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestScanner_Detect(t *testing.T) { diff --git a/pkg/detector/ospkg/amazon/amazon_test.go b/pkg/detector/ospkg/amazon/amazon_test.go index a3f60fb3578e..3ac7b55d3ad0 100644 --- a/pkg/detector/ospkg/amazon/amazon_test.go +++ b/pkg/detector/ospkg/amazon/amazon_test.go @@ -5,17 +5,17 @@ import ( "testing" "time" - "github.com/aquasecurity/trivy/pkg/clock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/amazon" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestScanner_Detect(t *testing.T) { diff --git a/pkg/detector/ospkg/debian/debian_test.go b/pkg/detector/ospkg/debian/debian_test.go index 602afe5d6699..fa0e334eff33 100644 --- a/pkg/detector/ospkg/debian/debian_test.go +++ b/pkg/detector/ospkg/debian/debian_test.go @@ -6,17 +6,17 @@ import ( "testing" "time" - "github.com/aquasecurity/trivy/pkg/clock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/debian" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestScanner_Detect(t *testing.T) { diff --git a/pkg/detector/ospkg/oracle/oracle_test.go b/pkg/detector/ospkg/oracle/oracle_test.go index 0116effe9325..7e5e9b6a7f7b 100644 --- a/pkg/detector/ospkg/oracle/oracle_test.go +++ b/pkg/detector/ospkg/oracle/oracle_test.go @@ -5,16 +5,16 @@ import ( "testing" "time" - "github.com/aquasecurity/trivy/pkg/clock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/dbtest" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestScanner_IsSupportedVersion(t *testing.T) { diff --git a/pkg/detector/ospkg/photon/photon_test.go b/pkg/detector/ospkg/photon/photon_test.go index 6d45edeea1ee..ffa978adc612 100644 --- a/pkg/detector/ospkg/photon/photon_test.go +++ b/pkg/detector/ospkg/photon/photon_test.go @@ -5,17 +5,17 @@ import ( "testing" "time" - "github.com/aquasecurity/trivy/pkg/clock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/photon" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestScanner_Detect(t *testing.T) { diff --git a/pkg/detector/ospkg/redhat/redhat_test.go b/pkg/detector/ospkg/redhat/redhat_test.go index fe24220490a8..17a6a8768554 100644 --- a/pkg/detector/ospkg/redhat/redhat_test.go +++ b/pkg/detector/ospkg/redhat/redhat_test.go @@ -2,20 +2,21 @@ package redhat_test import ( "context" - "github.com/aquasecurity/trivy/pkg/clock" "os" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/redhat" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestMain(m *testing.M) { diff --git a/pkg/detector/ospkg/rocky/rocky_test.go b/pkg/detector/ospkg/rocky/rocky_test.go index 799eb8bc6fc0..e61c69076cce 100644 --- a/pkg/detector/ospkg/rocky/rocky_test.go +++ b/pkg/detector/ospkg/rocky/rocky_test.go @@ -5,17 +5,17 @@ import ( "testing" "time" - "github.com/aquasecurity/trivy/pkg/clock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/rocky" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestScanner_Detect(t *testing.T) { diff --git a/pkg/detector/ospkg/suse/suse_test.go b/pkg/detector/ospkg/suse/suse_test.go index 793127f1b07d..663e502e717c 100644 --- a/pkg/detector/ospkg/suse/suse_test.go +++ b/pkg/detector/ospkg/suse/suse_test.go @@ -5,17 +5,17 @@ import ( "testing" "time" - "github.com/aquasecurity/trivy/pkg/clock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/suse" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestScanner_Detect(t *testing.T) { diff --git a/pkg/detector/ospkg/ubuntu/ubuntu_test.go b/pkg/detector/ospkg/ubuntu/ubuntu_test.go index eb2048532dbe..750d49d4bd5f 100644 --- a/pkg/detector/ospkg/ubuntu/ubuntu_test.go +++ b/pkg/detector/ospkg/ubuntu/ubuntu_test.go @@ -6,17 +6,17 @@ import ( "testing" "time" - "github.com/aquasecurity/trivy/pkg/clock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/ubuntu" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestScanner_Detect(t *testing.T) { diff --git a/pkg/fanal/analyzer/analyzer_test.go b/pkg/fanal/analyzer/analyzer_test.go index 1aa2eab607e6..671c1050837f 100644 --- a/pkg/fanal/analyzer/analyzer_test.go +++ b/pkg/fanal/analyzer/analyzer_test.go @@ -3,11 +3,11 @@ package analyzer_test import ( "context" "fmt" - "github.com/google/go-containerregistry/pkg/name" "os" "sync" "testing" + "github.com/google/go-containerregistry/pkg/name" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/sync/semaphore" diff --git a/pkg/fanal/analyzer/language/conda/environment/environment_test.go b/pkg/fanal/analyzer/language/conda/environment/environment_test.go index c9610289584e..044585f14683 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment_test.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment_test.go @@ -2,12 +2,14 @@ package environment import ( "context" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "os" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" ) func Test_environmentAnalyzer_Analyze(t *testing.T) { diff --git a/pkg/fanal/analyzer/language/java/gradle/pom_test.go b/pkg/fanal/analyzer/language/java/gradle/pom_test.go index 4ca85c647e2e..90591e96d66a 100644 --- a/pkg/fanal/analyzer/language/java/gradle/pom_test.go +++ b/pkg/fanal/analyzer/language/java/gradle/pom_test.go @@ -1,10 +1,11 @@ package gradle import ( - "github.com/stretchr/testify/require" "os" "path/filepath" "testing" + + "github.com/stretchr/testify/require" ) func Test_parsePom(t *testing.T) { diff --git a/pkg/fanal/analyzer/language/java/jar/jar_test.go b/pkg/fanal/analyzer/language/java/jar/jar_test.go index 304f5a33f861..225737166c81 100644 --- a/pkg/fanal/analyzer/language/java/jar/jar_test.go +++ b/pkg/fanal/analyzer/language/java/jar/jar_test.go @@ -7,9 +7,8 @@ import ( "testing" "github.com/google/go-containerregistry/pkg/name" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go b/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go index d5562ef7e18c..6217f031f46e 100644 --- a/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go +++ b/pkg/fanal/analyzer/language/julia/pkg/pkg_test.go @@ -5,10 +5,11 @@ import ( "os" "testing" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" ) func Test_juliaAnalyzer_Analyze(t *testing.T) { diff --git a/pkg/fanal/analyzer/pkg/rpm/rpm_test.go b/pkg/fanal/analyzer/pkg/rpm/rpm_test.go index 98cd1a2f3acc..91df2d2f3bb8 100644 --- a/pkg/fanal/analyzer/pkg/rpm/rpm_test.go +++ b/pkg/fanal/analyzer/pkg/rpm/rpm_test.go @@ -7,13 +7,13 @@ import ( "strings" "testing" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/types" rpmdb "github.com/knqyf263/go-rpmdb/pkg" "github.com/samber/lo" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/stretchr/testify/assert" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" ) type mockRPMDB struct { diff --git a/pkg/fanal/analyzer/sbom/sbom_test.go b/pkg/fanal/analyzer/sbom/sbom_test.go index c1c09b24a5bc..cce12a7c4955 100644 --- a/pkg/fanal/analyzer/sbom/sbom_test.go +++ b/pkg/fanal/analyzer/sbom/sbom_test.go @@ -2,10 +2,10 @@ package sbom import ( "context" - "github.com/package-url/packageurl-go" "os" "testing" + "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/fanal/artifact/image/remote_sbom_test.go b/pkg/fanal/artifact/image/remote_sbom_test.go index 2b58d2fdb8a3..1fd29fe2c69a 100644 --- a/pkg/fanal/artifact/image/remote_sbom_test.go +++ b/pkg/fanal/artifact/image/remote_sbom_test.go @@ -2,7 +2,6 @@ package image_test import ( "context" - "github.com/package-url/packageurl-go" "net/http" "net/http/httptest" "net/url" @@ -11,6 +10,7 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" fakei "github.com/google/go-containerregistry/pkg/v1/fake" + "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index 707818c3cdf2..1d4029578ca9 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -3,18 +3,19 @@ package local import ( "context" "errors" - "github.com/aquasecurity/trivy/pkg/fanal/walker" "os" "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/fanal/walker" "github.com/aquasecurity/trivy/pkg/misconf" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/python/pip" diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go index e755ebbd0d2c..4914588540d5 100644 --- a/pkg/fanal/artifact/repo/git_test.go +++ b/pkg/fanal/artifact/repo/git_test.go @@ -11,11 +11,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" - _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/walker" + + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret" ) func setupGitServer() (*httptest.Server, error) { diff --git a/pkg/fanal/artifact/sbom/sbom_test.go b/pkg/fanal/artifact/sbom/sbom_test.go index d7af10196a1c..37ea39380b43 100644 --- a/pkg/fanal/artifact/sbom/sbom_test.go +++ b/pkg/fanal/artifact/sbom/sbom_test.go @@ -3,11 +3,11 @@ package sbom_test import ( "context" "errors" - "github.com/package-url/packageurl-go" "path/filepath" "strings" "testing" + "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/fanal/cache/key_test.go b/pkg/fanal/cache/key_test.go index 9e036155f5ea..012e9edd407f 100644 --- a/pkg/fanal/cache/key_test.go +++ b/pkg/fanal/cache/key_test.go @@ -3,13 +3,12 @@ package cache import ( "testing" - "github.com/aquasecurity/trivy/pkg/fanal/walker" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/walker" "github.com/aquasecurity/trivy/pkg/misconf" ) diff --git a/pkg/fanal/handler/unpackaged/unpackaged_test.go b/pkg/fanal/handler/unpackaged/unpackaged_test.go index be2ddd116151..e6edc62ae165 100644 --- a/pkg/fanal/handler/unpackaged/unpackaged_test.go +++ b/pkg/fanal/handler/unpackaged/unpackaged_test.go @@ -2,9 +2,9 @@ package unpackaged_test import ( "context" - "github.com/package-url/packageurl-go" "testing" + "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/fanal/image/registry/ecr/ecr_test.go b/pkg/fanal/image/registry/ecr/ecr_test.go index f968cf1d850d..68b1870f07da 100644 --- a/pkg/fanal/image/registry/ecr/ecr_test.go +++ b/pkg/fanal/image/registry/ecr/ecr_test.go @@ -22,11 +22,11 @@ func TestCheckOptions(t *testing.T) { wantErr: types.InvalidURLPattern, }, "InvalidDomain": { - domain: "xxx.ecr.ap-northeast-1.not-amazonaws.com", + domain: "xxx.ecr.ap-northeast-1.not-amazonaws.com", wantErr: types.InvalidURLPattern, }, "InvalidSubdomain": { - domain: "xxx.s3.ap-northeast-1.amazonaws.com", + domain: "xxx.s3.ap-northeast-1.amazonaws.com", wantErr: types.InvalidURLPattern, }, "NoOption": { diff --git a/pkg/fanal/secret/scanner_test.go b/pkg/fanal/secret/scanner_test.go index 0d23f9959e24..d152591cb2ca 100644 --- a/pkg/fanal/secret/scanner_test.go +++ b/pkg/fanal/secret/scanner_test.go @@ -6,11 +6,12 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/fanal/secret" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestMain(m *testing.M) { diff --git a/pkg/fanal/walker/fs_test.go b/pkg/fanal/walker/fs_test.go index 0f4da5eefe72..2b7b2117afb1 100644 --- a/pkg/fanal/walker/fs_test.go +++ b/pkg/fanal/walker/fs_test.go @@ -9,10 +9,9 @@ import ( "strings" "testing" - "golang.org/x/exp/slices" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/walker" diff --git a/pkg/fanal/walker/walk_test.go b/pkg/fanal/walker/walk_test.go index 765f5c07d7b3..82f71c875b81 100644 --- a/pkg/fanal/walker/walk_test.go +++ b/pkg/fanal/walker/walk_test.go @@ -2,11 +2,12 @@ package walker_test import ( "fmt" - "github.com/aquasecurity/trivy/pkg/fanal/walker" "path/filepath" "testing" "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/fanal/walker" ) func TestSkipFile(t *testing.T) { diff --git a/pkg/flag/db_flags_test.go b/pkg/flag/db_flags_test.go index 5d121033fd3b..8a9960f91c24 100644 --- a/pkg/flag/db_flags_test.go +++ b/pkg/flag/db_flags_test.go @@ -1,14 +1,15 @@ package flag_test import ( - "github.com/aquasecurity/trivy/pkg/log" - "github.com/google/go-containerregistry/pkg/name" "testing" - "github.com/aquasecurity/trivy/pkg/flag" + "github.com/google/go-containerregistry/pkg/name" "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) { diff --git a/pkg/flag/options_test.go b/pkg/flag/options_test.go index 7827b4303b7d..2c5d5d0a4348 100644 --- a/pkg/flag/options_test.go +++ b/pkg/flag/options_test.go @@ -2,17 +2,18 @@ package flag_test import ( "bytes" - "github.com/aquasecurity/trivy/pkg/flag" - "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/types" - "github.com/samber/lo" - "github.com/spf13/cobra" - "github.com/stretchr/testify/require" "log/slog" "strings" "testing" + "github.com/samber/lo" + "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" ) func TestFlag_Parse(t *testing.T) { diff --git a/pkg/flag/remote_flags_test.go b/pkg/flag/remote_flags_test.go index d6a7a95387db..844831093ba8 100644 --- a/pkg/flag/remote_flags_test.go +++ b/pkg/flag/remote_flags_test.go @@ -1,14 +1,15 @@ package flag_test import ( - "github.com/aquasecurity/trivy/pkg/log" - "github.com/stretchr/testify/require" "net/http" "testing" - "github.com/aquasecurity/trivy/pkg/flag" "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 TestRemoteFlagGroup_ToOptions(t *testing.T) { diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go index 9aa3c0a58d69..58b050fd095b 100644 --- a/pkg/flag/report_flags_test.go +++ b/pkg/flag/report_flags_test.go @@ -3,16 +3,16 @@ package flag_test import ( "testing" - "github.com/aquasecurity/trivy/pkg/log" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/compliance/spec" "github.com/aquasecurity/trivy/pkg/flag" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestReportFlagGroup_ToOptions(t *testing.T) { diff --git a/pkg/flag/scan_flags_test.go b/pkg/flag/scan_flags_test.go index 2d5cb718b0d1..d3ad792ae948 100644 --- a/pkg/flag/scan_flags_test.go +++ b/pkg/flag/scan_flags_test.go @@ -1,9 +1,9 @@ package flag_test import ( - "github.com/spf13/viper" "testing" + "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/flag/vulnerability_flags_test.go b/pkg/flag/vulnerability_flags_test.go index 4f4490753aff..a1bcd90b3473 100644 --- a/pkg/flag/vulnerability_flags_test.go +++ b/pkg/flag/vulnerability_flags_test.go @@ -1,14 +1,15 @@ package flag_test import ( - "github.com/aquasecurity/trivy/pkg/log" - "github.com/stretchr/testify/require" "testing" - "github.com/aquasecurity/trivy/pkg/flag" - "github.com/aquasecurity/trivy/pkg/types" "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" + "github.com/aquasecurity/trivy/pkg/types" ) func TestVulnerabilityFlagGroup_ToOptions(t *testing.T) { diff --git a/pkg/iac/adapters/arm/compute/adapt_test.go b/pkg/iac/adapters/arm/compute/adapt_test.go index 8763a0aac28a..819f79993e5e 100644 --- a/pkg/iac/adapters/arm/compute/adapt_test.go +++ b/pkg/iac/adapters/arm/compute/adapt_test.go @@ -3,11 +3,11 @@ package compute import ( "testing" - azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" - "github.com/aquasecurity/trivy/pkg/iac/types" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + + azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_AdaptLinuxVM(t *testing.T) { diff --git a/pkg/iac/adapters/arm/storage/adapt_test.go b/pkg/iac/adapters/arm/storage/adapt_test.go index 9d8d2f0f1cbb..ae2e497580bb 100644 --- a/pkg/iac/adapters/arm/storage/adapt_test.go +++ b/pkg/iac/adapters/arm/storage/adapt_test.go @@ -3,12 +3,11 @@ package storage import ( "testing" - azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/stretchr/testify/require" ) func Test_AdaptStorageDefaults(t *testing.T) { diff --git a/pkg/iac/adapters/cloudformation/aws/ecr/ecr_test.go b/pkg/iac/adapters/cloudformation/aws/ecr/ecr_test.go index cb3e4b6b4b8d..6bcd0a4952dd 100644 --- a/pkg/iac/adapters/cloudformation/aws/ecr/ecr_test.go +++ b/pkg/iac/adapters/cloudformation/aws/ecr/ecr_test.go @@ -3,11 +3,12 @@ package ecr import ( "testing" + "github.com/liamg/iamgo" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecr" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/liamg/iamgo" ) func TestAdapt(t *testing.T) { diff --git a/pkg/iac/adapters/cloudformation/aws/iam/iam_test.go b/pkg/iac/adapters/cloudformation/aws/iam/iam_test.go index 3e548dec0cf7..1ea6a099a698 100644 --- a/pkg/iac/adapters/cloudformation/aws/iam/iam_test.go +++ b/pkg/iac/adapters/cloudformation/aws/iam/iam_test.go @@ -3,10 +3,11 @@ package iam import ( "testing" + "github.com/liamg/iamgo" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/liamg/iamgo" ) func TestAdapt(t *testing.T) { diff --git a/pkg/iac/adapters/cloudformation/aws/sam/sam_test.go b/pkg/iac/adapters/cloudformation/aws/sam/sam_test.go index ec2fed201ea6..1d9586775044 100644 --- a/pkg/iac/adapters/cloudformation/aws/sam/sam_test.go +++ b/pkg/iac/adapters/cloudformation/aws/sam/sam_test.go @@ -3,11 +3,12 @@ package sam import ( "testing" + "github.com/liamg/iamgo" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/liamg/iamgo" ) func TestAdapt(t *testing.T) { diff --git a/pkg/iac/adapters/cloudformation/aws/sqs/sqs_test.go b/pkg/iac/adapters/cloudformation/aws/sqs/sqs_test.go index 8abeff2aca3e..5e7fe278fcad 100644 --- a/pkg/iac/adapters/cloudformation/aws/sqs/sqs_test.go +++ b/pkg/iac/adapters/cloudformation/aws/sqs/sqs_test.go @@ -3,11 +3,12 @@ package sqs import ( "testing" + "github.com/liamg/iamgo" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sqs" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/liamg/iamgo" ) func TestAdapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/apigateway/adapt_test.go b/pkg/iac/adapters/terraform/aws/apigateway/adapt_test.go index 92d96f396e9c..65393ae82f82 100644 --- a/pkg/iac/adapters/terraform/aws/apigateway/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/apigateway/adapt_test.go @@ -3,14 +3,15 @@ package apigateway import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway" v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/athena/adapt_test.go b/pkg/iac/adapters/terraform/aws/athena/adapt_test.go index e734b024b274..274167e71599 100644 --- a/pkg/iac/adapters/terraform/aws/athena/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/athena/adapt_test.go @@ -3,14 +3,13 @@ package athena import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/athena" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptDatabase(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go b/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go index eade38048204..d20520cd4651 100644 --- a/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go @@ -3,14 +3,13 @@ package cloudfront import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptDistribution(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/cloudtrail/adapt_test.go b/pkg/iac/adapters/terraform/aws/cloudtrail/adapt_test.go index 9088b115752e..f79664d0b7de 100644 --- a/pkg/iac/adapters/terraform/aws/cloudtrail/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/cloudtrail/adapt_test.go @@ -3,14 +3,13 @@ package cloudtrail import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptTrail(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/cloudwatch/adapt_test.go b/pkg/iac/adapters/terraform/aws/cloudwatch/adapt_test.go index 5febcd592dfe..86a484aecce6 100644 --- a/pkg/iac/adapters/terraform/aws/cloudwatch/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/cloudwatch/adapt_test.go @@ -3,14 +3,13 @@ package cloudwatch import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptLogGroups(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/codebuild/adapt_test.go b/pkg/iac/adapters/terraform/aws/codebuild/adapt_test.go index 58aeca7df9d1..c53a509606dc 100644 --- a/pkg/iac/adapters/terraform/aws/codebuild/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/codebuild/adapt_test.go @@ -3,14 +3,13 @@ package codebuild import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/codebuild" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptProject(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/config/adapt_test.go b/pkg/iac/adapters/terraform/aws/config/adapt_test.go index 94917b430fbe..74cf720caa10 100644 --- a/pkg/iac/adapters/terraform/aws/config/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/config/adapt_test.go @@ -3,13 +3,12 @@ package config import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/config" - - "github.com/stretchr/testify/assert" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptConfigurationAggregrator(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/documentdb/adapt_test.go b/pkg/iac/adapters/terraform/aws/documentdb/adapt_test.go index db7dbd7937e8..76ea0a8e3103 100644 --- a/pkg/iac/adapters/terraform/aws/documentdb/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/documentdb/adapt_test.go @@ -3,14 +3,13 @@ package documentdb import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/documentdb" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptCluster(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/dynamodb/adapt_test.go b/pkg/iac/adapters/terraform/aws/dynamodb/adapt_test.go index 3ebd86b70199..72f6e8ed4ac2 100644 --- a/pkg/iac/adapters/terraform/aws/dynamodb/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/dynamodb/adapt_test.go @@ -3,14 +3,13 @@ package dynamodb import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/dynamodb" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptCluster(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/ec2/adapt_test.go b/pkg/iac/adapters/terraform/aws/ec2/adapt_test.go index e539b3f827dc..665852321db4 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/ec2/adapt_test.go @@ -3,14 +3,13 @@ package ec2 import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/ec2/autoscaling_test.go b/pkg/iac/adapters/terraform/aws/ec2/autoscaling_test.go index a6437340aad4..25efdbe70993 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/autoscaling_test.go +++ b/pkg/iac/adapters/terraform/aws/ec2/autoscaling_test.go @@ -3,14 +3,13 @@ package ec2 import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_AdaptAutoscaling(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/ec2/subnet_test.go b/pkg/iac/adapters/terraform/aws/ec2/subnet_test.go index cbc080d939dc..c371ef4f504f 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/subnet_test.go +++ b/pkg/iac/adapters/terraform/aws/ec2/subnet_test.go @@ -3,14 +3,13 @@ package ec2 import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptSubnet(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/ec2/volume_test.go b/pkg/iac/adapters/terraform/aws/ec2/volume_test.go index e7d260eae3e4..c48955ca7d0b 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/volume_test.go +++ b/pkg/iac/adapters/terraform/aws/ec2/volume_test.go @@ -3,14 +3,13 @@ package ec2 import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptVolume(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go b/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go index ab372f8f1084..72005e5b8559 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go +++ b/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go @@ -3,14 +3,13 @@ package ec2 import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_AdaptVPC(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/ecr/adapt_test.go b/pkg/iac/adapters/terraform/aws/ecr/adapt_test.go index 746adf8eacad..f2fb4d3dc9c9 100644 --- a/pkg/iac/adapters/terraform/aws/ecr/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/ecr/adapt_test.go @@ -3,16 +3,15 @@ package ecr import ( "testing" + "github.com/liamg/iamgo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecr" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" - - "github.com/liamg/iamgo" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptRepository(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/ecs/adapt_test.go b/pkg/iac/adapters/terraform/aws/ecs/adapt_test.go index d7c0033cefba..c35bcc12d9b1 100644 --- a/pkg/iac/adapters/terraform/aws/ecs/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/ecs/adapt_test.go @@ -3,14 +3,13 @@ package ecs import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecs" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptClusterSettings(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/efs/adapt_test.go b/pkg/iac/adapters/terraform/aws/efs/adapt_test.go index b5516fe97771..481391c342af 100644 --- a/pkg/iac/adapters/terraform/aws/efs/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/efs/adapt_test.go @@ -3,14 +3,13 @@ package efs import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/efs" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptFileSystem(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/eks/adapt_test.go b/pkg/iac/adapters/terraform/aws/eks/adapt_test.go index 7b466d4a5e0c..bdbfc48d0d84 100644 --- a/pkg/iac/adapters/terraform/aws/eks/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/eks/adapt_test.go @@ -3,14 +3,13 @@ package eks import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/eks" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptCluster(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/elasticache/adapt_test.go b/pkg/iac/adapters/terraform/aws/elasticache/adapt_test.go index 62ee9a6bac08..4caf04afcd11 100644 --- a/pkg/iac/adapters/terraform/aws/elasticache/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/elasticache/adapt_test.go @@ -3,14 +3,13 @@ package elasticache import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticache" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptCluster(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/elasticsearch/adapt_test.go b/pkg/iac/adapters/terraform/aws/elasticsearch/adapt_test.go index 83e7f87d9ae5..8384b63fa561 100644 --- a/pkg/iac/adapters/terraform/aws/elasticsearch/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/elasticsearch/adapt_test.go @@ -3,14 +3,13 @@ package elasticsearch import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticsearch" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptDomain(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/elb/adapt_test.go b/pkg/iac/adapters/terraform/aws/elb/adapt_test.go index b938ce563106..f06682d402c7 100644 --- a/pkg/iac/adapters/terraform/aws/elb/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/elb/adapt_test.go @@ -3,14 +3,13 @@ package elb import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/emr/adapt_test.go b/pkg/iac/adapters/terraform/aws/emr/adapt_test.go index 5b0e43f4186a..726c8862e0fb 100644 --- a/pkg/iac/adapters/terraform/aws/emr/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/emr/adapt_test.go @@ -3,13 +3,13 @@ package emr import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/emr" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptSecurityConfiguration(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/iam/adapt_test.go b/pkg/iac/adapters/terraform/aws/iam/adapt_test.go index 66387056736c..6da793915155 100644 --- a/pkg/iac/adapters/terraform/aws/iam/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/iam/adapt_test.go @@ -3,9 +3,10 @@ package iam import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" ) func TestLines(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/iam/groups_test.go b/pkg/iac/adapters/terraform/aws/iam/groups_test.go index f522130b30d2..9ac6be72c46d 100644 --- a/pkg/iac/adapters/terraform/aws/iam/groups_test.go +++ b/pkg/iac/adapters/terraform/aws/iam/groups_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptGroups(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/iam/passwords_test.go b/pkg/iac/adapters/terraform/aws/iam/passwords_test.go index ca94ce305147..11ca16c0dcdd 100644 --- a/pkg/iac/adapters/terraform/aws/iam/passwords_test.go +++ b/pkg/iac/adapters/terraform/aws/iam/passwords_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptPasswordPolicy(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/iam/policies_test.go b/pkg/iac/adapters/terraform/aws/iam/policies_test.go index aa6b9f9b59f5..a1e989c06979 100644 --- a/pkg/iac/adapters/terraform/aws/iam/policies_test.go +++ b/pkg/iac/adapters/terraform/aws/iam/policies_test.go @@ -3,12 +3,12 @@ package iam import ( "testing" + "github.com/liamg/iamgo" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" - "github.com/liamg/iamgo" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func defaultPolicyDocuemnt(offset bool) iam.Document { diff --git a/pkg/iac/adapters/terraform/aws/iam/roles_test.go b/pkg/iac/adapters/terraform/aws/iam/roles_test.go index 61dfc1facb08..93bc5a9e8168 100644 --- a/pkg/iac/adapters/terraform/aws/iam/roles_test.go +++ b/pkg/iac/adapters/terraform/aws/iam/roles_test.go @@ -4,11 +4,12 @@ import ( "sort" "testing" + "github.com/liamg/iamgo" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/liamg/iamgo" ) func Test_adaptRoles(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/kinesis/adapt_test.go b/pkg/iac/adapters/terraform/aws/kinesis/adapt_test.go index c1138d2f7748..c0d1a50b53e9 100644 --- a/pkg/iac/adapters/terraform/aws/kinesis/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/kinesis/adapt_test.go @@ -3,14 +3,13 @@ package kinesis import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/kinesis" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptStream(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/kms/adapt_test.go b/pkg/iac/adapters/terraform/aws/kms/adapt_test.go index 721d0ee19d66..bc86f3366af9 100644 --- a/pkg/iac/adapters/terraform/aws/kms/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/kms/adapt_test.go @@ -3,14 +3,13 @@ package kms import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/kms" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptKey(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/lambda/adapt_test.go b/pkg/iac/adapters/terraform/aws/lambda/adapt_test.go index 8ec555c7d90f..c0e58104819d 100644 --- a/pkg/iac/adapters/terraform/aws/lambda/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/lambda/adapt_test.go @@ -3,13 +3,13 @@ package lambda import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/lambda" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/mq/adapt_test.go b/pkg/iac/adapters/terraform/aws/mq/adapt_test.go index fe8d77432b62..e581cf43be24 100644 --- a/pkg/iac/adapters/terraform/aws/mq/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/mq/adapt_test.go @@ -3,14 +3,13 @@ package mq import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/mq" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptBroker(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/msk/adapt_test.go b/pkg/iac/adapters/terraform/aws/msk/adapt_test.go index dae131487b4f..6157655dbfed 100644 --- a/pkg/iac/adapters/terraform/aws/msk/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/msk/adapt_test.go @@ -3,14 +3,13 @@ package msk import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/msk" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptCluster(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/neptune/adapt_test.go b/pkg/iac/adapters/terraform/aws/neptune/adapt_test.go index 5ad4f3de82ad..7e67e51e394c 100644 --- a/pkg/iac/adapters/terraform/aws/neptune/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/neptune/adapt_test.go @@ -3,14 +3,13 @@ package neptune import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/neptune" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptCluster(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/rds/adapt_test.go b/pkg/iac/adapters/terraform/aws/rds/adapt_test.go index 3776e4c4ad48..76f045faf288 100644 --- a/pkg/iac/adapters/terraform/aws/rds/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/rds/adapt_test.go @@ -3,13 +3,13 @@ package rds import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/redshift/adapt_test.go b/pkg/iac/adapters/terraform/aws/redshift/adapt_test.go index e52db1c2256b..6c07f83b3363 100644 --- a/pkg/iac/adapters/terraform/aws/redshift/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/redshift/adapt_test.go @@ -4,13 +4,13 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/redshift" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/s3/adapt_test.go b/pkg/iac/adapters/terraform/aws/s3/adapt_test.go index b17a00391b34..1cd1dec76270 100644 --- a/pkg/iac/adapters/terraform/aws/s3/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/s3/adapt_test.go @@ -3,15 +3,15 @@ package s3 import ( "testing" + "github.com/liamg/iamgo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" - "github.com/liamg/iamgo" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_PublicAccessBlock(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/s3/bucket_test.go b/pkg/iac/adapters/terraform/aws/s3/bucket_test.go index 84c0ddc60ecd..aeaca3d5a4c9 100644 --- a/pkg/iac/adapters/terraform/aws/s3/bucket_test.go +++ b/pkg/iac/adapters/terraform/aws/s3/bucket_test.go @@ -3,10 +3,10 @@ package s3 import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/stretchr/testify/assert" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" ) func Test_GetBuckets(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/sns/adapt_test.go b/pkg/iac/adapters/terraform/aws/sns/adapt_test.go index caae71b2dc17..377a918b67ae 100644 --- a/pkg/iac/adapters/terraform/aws/sns/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/sns/adapt_test.go @@ -3,14 +3,13 @@ package sns import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sns" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptTopic(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/sqs/adapt_test.go b/pkg/iac/adapters/terraform/aws/sqs/adapt_test.go index dc95257d258e..0a4726140456 100644 --- a/pkg/iac/adapters/terraform/aws/sqs/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/sqs/adapt_test.go @@ -3,16 +3,15 @@ package sqs import ( "testing" + "github.com/liamg/iamgo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sqs" - - "github.com/liamg/iamgo" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/ssm/adapt_test.go b/pkg/iac/adapters/terraform/aws/ssm/adapt_test.go index 67afec8a3941..c5cf726995fa 100644 --- a/pkg/iac/adapters/terraform/aws/ssm/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/ssm/adapt_test.go @@ -3,14 +3,13 @@ package ssm import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ssm" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/aws/workspaces/adapt_test.go b/pkg/iac/adapters/terraform/aws/workspaces/adapt_test.go index 457960128947..372124340c41 100644 --- a/pkg/iac/adapters/terraform/aws/workspaces/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/workspaces/adapt_test.go @@ -3,14 +3,13 @@ package workspaces import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/workspaces" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptWorkspace(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/appservice/adapt_test.go b/pkg/iac/adapters/terraform/azure/appservice/adapt_test.go index 94b6b7f13b77..28b468a3ba39 100644 --- a/pkg/iac/adapters/terraform/azure/appservice/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/appservice/adapt_test.go @@ -3,14 +3,13 @@ package appservice import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/appservice" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptService(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/authorization/adapt_test.go b/pkg/iac/adapters/terraform/azure/authorization/adapt_test.go index f1e211ec4f5f..3bd3b13fbdd5 100644 --- a/pkg/iac/adapters/terraform/azure/authorization/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/authorization/adapt_test.go @@ -3,14 +3,13 @@ package authorization import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/authorization" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptRoleDefinition(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/compute/adapt_test.go b/pkg/iac/adapters/terraform/azure/compute/adapt_test.go index 141b81ffecd6..9cafbc79aee5 100644 --- a/pkg/iac/adapters/terraform/azure/compute/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/compute/adapt_test.go @@ -3,14 +3,13 @@ package compute import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/compute" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptManagedDisk(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/container/adapt_test.go b/pkg/iac/adapters/terraform/azure/container/adapt_test.go index 13d8c712a621..0bd5070c8395 100644 --- a/pkg/iac/adapters/terraform/azure/container/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/container/adapt_test.go @@ -3,14 +3,13 @@ package container import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/container" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptCluster(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/database/adapt_test.go b/pkg/iac/adapters/terraform/azure/database/adapt_test.go index 9616659b30e3..05dc61173d8e 100644 --- a/pkg/iac/adapters/terraform/azure/database/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/database/adapt_test.go @@ -3,13 +3,13 @@ package database import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/database" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/datafactory/adapt_test.go b/pkg/iac/adapters/terraform/azure/datafactory/adapt_test.go index cefe5709d42e..5a98977002d9 100644 --- a/pkg/iac/adapters/terraform/azure/datafactory/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/datafactory/adapt_test.go @@ -3,14 +3,13 @@ package datafactory import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/datafactory" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptFactory(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/datalake/adapt_test.go b/pkg/iac/adapters/terraform/azure/datalake/adapt_test.go index c1ca5410384f..c902787eb790 100644 --- a/pkg/iac/adapters/terraform/azure/datalake/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/datalake/adapt_test.go @@ -3,14 +3,13 @@ package datalake import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/datalake" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptStore(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/keyvault/adapt_test.go b/pkg/iac/adapters/terraform/azure/keyvault/adapt_test.go index b21d25b12c70..9c8f1ace0320 100644 --- a/pkg/iac/adapters/terraform/azure/keyvault/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/keyvault/adapt_test.go @@ -4,14 +4,13 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/keyvault" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/monitor/adapt_test.go b/pkg/iac/adapters/terraform/azure/monitor/adapt_test.go index a0cf262fca7c..d8b075a0d09c 100644 --- a/pkg/iac/adapters/terraform/azure/monitor/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/monitor/adapt_test.go @@ -3,14 +3,13 @@ package monitor import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/monitor" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptLogProfile(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/network/adapt_test.go b/pkg/iac/adapters/terraform/azure/network/adapt_test.go index a6abf380145c..15b966b06ffc 100644 --- a/pkg/iac/adapters/terraform/azure/network/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/network/adapt_test.go @@ -3,13 +3,13 @@ package network import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/network" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/securitycenter/adapt_test.go b/pkg/iac/adapters/terraform/azure/securitycenter/adapt_test.go index 3b61ebecf0b9..acd3760952c2 100644 --- a/pkg/iac/adapters/terraform/azure/securitycenter/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/securitycenter/adapt_test.go @@ -3,14 +3,13 @@ package securitycenter import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/securitycenter" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptContact(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/storage/adapt_test.go b/pkg/iac/adapters/terraform/azure/storage/adapt_test.go index 3b13c3c4df35..87ffb1a919f4 100644 --- a/pkg/iac/adapters/terraform/azure/storage/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/storage/adapt_test.go @@ -3,13 +3,13 @@ package storage import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/storage" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/azure/synapse/adapt_test.go b/pkg/iac/adapters/terraform/azure/synapse/adapt_test.go index 79062878e092..0b4cb3c0ea9e 100644 --- a/pkg/iac/adapters/terraform/azure/synapse/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/synapse/adapt_test.go @@ -3,14 +3,13 @@ package synapse import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/synapse" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptWorkspace(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/cloudstack/compute/adapt_test.go b/pkg/iac/adapters/terraform/cloudstack/compute/adapt_test.go index 14576c13bbf8..d869947197ee 100644 --- a/pkg/iac/adapters/terraform/cloudstack/compute/adapt_test.go +++ b/pkg/iac/adapters/terraform/cloudstack/compute/adapt_test.go @@ -3,14 +3,13 @@ package compute import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/cloudstack/compute" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptInstance(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/digitalocean/compute/adapt_test.go b/pkg/iac/adapters/terraform/digitalocean/compute/adapt_test.go index e4c81deaaa9b..b92a1e1e352a 100644 --- a/pkg/iac/adapters/terraform/digitalocean/compute/adapt_test.go +++ b/pkg/iac/adapters/terraform/digitalocean/compute/adapt_test.go @@ -3,13 +3,13 @@ package compute import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/digitalocean/compute" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptDroplets(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/digitalocean/spaces/adapt_test.go b/pkg/iac/adapters/terraform/digitalocean/spaces/adapt_test.go index bbe85a321505..cf3fc84e4ff7 100644 --- a/pkg/iac/adapters/terraform/digitalocean/spaces/adapt_test.go +++ b/pkg/iac/adapters/terraform/digitalocean/spaces/adapt_test.go @@ -3,14 +3,13 @@ package spaces import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/digitalocean/spaces" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptBuckets(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/github/branch_protections/adapt_test.go b/pkg/iac/adapters/terraform/github/branch_protections/adapt_test.go index 4317f84bde9e..bb5bfac0f71b 100644 --- a/pkg/iac/adapters/terraform/github/branch_protections/adapt_test.go +++ b/pkg/iac/adapters/terraform/github/branch_protections/adapt_test.go @@ -3,10 +3,10 @@ package branch_protections import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" ) func Test_AdaptDefaults(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/github/repositories/adapt_test.go b/pkg/iac/adapters/terraform/github/repositories/adapt_test.go index 2199744bbde9..ea26c9978095 100644 --- a/pkg/iac/adapters/terraform/github/repositories/adapt_test.go +++ b/pkg/iac/adapters/terraform/github/repositories/adapt_test.go @@ -3,10 +3,10 @@ package repositories import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" ) func Test_AdaptDefaults(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/github/secrets/adapt_test.go b/pkg/iac/adapters/terraform/github/secrets/adapt_test.go index 61f49de8220a..f1c28f2596a1 100644 --- a/pkg/iac/adapters/terraform/github/secrets/adapt_test.go +++ b/pkg/iac/adapters/terraform/github/secrets/adapt_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/github" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/bigquery/adapt_test.go b/pkg/iac/adapters/terraform/google/bigquery/adapt_test.go index 55d1b2ffa8c4..675fd9aa8422 100644 --- a/pkg/iac/adapters/terraform/google/bigquery/adapt_test.go +++ b/pkg/iac/adapters/terraform/google/bigquery/adapt_test.go @@ -3,14 +3,13 @@ package bigquery import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/bigquery" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/compute/adapt_test.go b/pkg/iac/adapters/terraform/google/compute/adapt_test.go index a30fa260281b..a90c4315364a 100644 --- a/pkg/iac/adapters/terraform/google/compute/adapt_test.go +++ b/pkg/iac/adapters/terraform/google/compute/adapt_test.go @@ -3,9 +3,10 @@ package compute import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" ) func TestLines(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/compute/disks_test.go b/pkg/iac/adapters/terraform/google/compute/disks_test.go index 0036e11a0163..81768758c544 100644 --- a/pkg/iac/adapters/terraform/google/compute/disks_test.go +++ b/pkg/iac/adapters/terraform/google/compute/disks_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/compute" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptDisks(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/compute/instances_test.go b/pkg/iac/adapters/terraform/google/compute/instances_test.go index 5f1105df742f..e37655b3d076 100644 --- a/pkg/iac/adapters/terraform/google/compute/instances_test.go +++ b/pkg/iac/adapters/terraform/google/compute/instances_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/compute" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptInstances(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/compute/metadata_test.go b/pkg/iac/adapters/terraform/google/compute/metadata_test.go index 9a3b38abedac..00913b678721 100644 --- a/pkg/iac/adapters/terraform/google/compute/metadata_test.go +++ b/pkg/iac/adapters/terraform/google/compute/metadata_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/compute" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptProjectMetadata(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/compute/networks_test.go b/pkg/iac/adapters/terraform/google/compute/networks_test.go index 1a40a597dcf3..32ad00bc5334 100644 --- a/pkg/iac/adapters/terraform/google/compute/networks_test.go +++ b/pkg/iac/adapters/terraform/google/compute/networks_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/compute" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptNetworks(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/compute/ssl_test.go b/pkg/iac/adapters/terraform/google/compute/ssl_test.go index 30cecdba34f3..aa0d5655b3a7 100644 --- a/pkg/iac/adapters/terraform/google/compute/ssl_test.go +++ b/pkg/iac/adapters/terraform/google/compute/ssl_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/compute" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptSSLPolicies(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/dns/adapt_test.go b/pkg/iac/adapters/terraform/google/dns/adapt_test.go index 60974fb01df1..489884afe79f 100644 --- a/pkg/iac/adapters/terraform/google/dns/adapt_test.go +++ b/pkg/iac/adapters/terraform/google/dns/adapt_test.go @@ -3,14 +3,13 @@ package dns import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/dns" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/gke/adapt_test.go b/pkg/iac/adapters/terraform/google/gke/adapt_test.go index f45cdc24b2fb..1309288dc42d 100644 --- a/pkg/iac/adapters/terraform/google/gke/adapt_test.go +++ b/pkg/iac/adapters/terraform/google/gke/adapt_test.go @@ -3,13 +3,13 @@ package gke import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/gke" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/iam/adapt_test.go b/pkg/iac/adapters/terraform/google/iam/adapt_test.go index 8297d83a5335..2a7cbf2640a0 100644 --- a/pkg/iac/adapters/terraform/google/iam/adapt_test.go +++ b/pkg/iac/adapters/terraform/google/iam/adapt_test.go @@ -3,13 +3,13 @@ package iam import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/iam/project_iam_test.go b/pkg/iac/adapters/terraform/google/iam/project_iam_test.go index fc2803c2dc4a..2ddeb9bbf3ca 100644 --- a/pkg/iac/adapters/terraform/google/iam/project_iam_test.go +++ b/pkg/iac/adapters/terraform/google/iam/project_iam_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_AdaptBinding(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/kms/adapt_test.go b/pkg/iac/adapters/terraform/google/kms/adapt_test.go index c6025dc86f1e..7bc9a9646914 100644 --- a/pkg/iac/adapters/terraform/google/kms/adapt_test.go +++ b/pkg/iac/adapters/terraform/google/kms/adapt_test.go @@ -3,14 +3,13 @@ package kms import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/kms" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptKeyRings(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/sql/adapt_test.go b/pkg/iac/adapters/terraform/google/sql/adapt_test.go index a31f649ffe99..29e89d6282b7 100644 --- a/pkg/iac/adapters/terraform/google/sql/adapt_test.go +++ b/pkg/iac/adapters/terraform/google/sql/adapt_test.go @@ -3,14 +3,13 @@ package sql import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/google/storage/adapt_test.go b/pkg/iac/adapters/terraform/google/storage/adapt_test.go index fe7a82e62f9c..0af0a99cb10a 100644 --- a/pkg/iac/adapters/terraform/google/storage/adapt_test.go +++ b/pkg/iac/adapters/terraform/google/storage/adapt_test.go @@ -3,14 +3,14 @@ package storage import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" "github.com/aquasecurity/trivy/pkg/iac/providers/google/storage" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Adapt(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/nifcloud/computing/adapt_test.go b/pkg/iac/adapters/terraform/nifcloud/computing/adapt_test.go index be6efb30493f..88af58c58674 100644 --- a/pkg/iac/adapters/terraform/nifcloud/computing/adapt_test.go +++ b/pkg/iac/adapters/terraform/nifcloud/computing/adapt_test.go @@ -3,10 +3,10 @@ package computing import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" ) func TestLines(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/nifcloud/computing/instance_test.go b/pkg/iac/adapters/terraform/nifcloud/computing/instance_test.go index 8055681c1353..81d996cc7e62 100644 --- a/pkg/iac/adapters/terraform/nifcloud/computing/instance_test.go +++ b/pkg/iac/adapters/terraform/nifcloud/computing/instance_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/computing" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptInstances(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/nifcloud/computing/security_group_test.go b/pkg/iac/adapters/terraform/nifcloud/computing/security_group_test.go index 250f99a96fdc..d7e514831667 100644 --- a/pkg/iac/adapters/terraform/nifcloud/computing/security_group_test.go +++ b/pkg/iac/adapters/terraform/nifcloud/computing/security_group_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/computing" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptSecurityGroups(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/nifcloud/dns/adapt_test.go b/pkg/iac/adapters/terraform/nifcloud/dns/adapt_test.go index 38ff119054bd..b3c0f535667b 100644 --- a/pkg/iac/adapters/terraform/nifcloud/dns/adapt_test.go +++ b/pkg/iac/adapters/terraform/nifcloud/dns/adapt_test.go @@ -3,10 +3,10 @@ package dns import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" ) func TestLines(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/nifcloud/nas/adapt_test.go b/pkg/iac/adapters/terraform/nifcloud/nas/adapt_test.go index b43b874974b8..bbd18e71a7d2 100644 --- a/pkg/iac/adapters/terraform/nifcloud/nas/adapt_test.go +++ b/pkg/iac/adapters/terraform/nifcloud/nas/adapt_test.go @@ -3,10 +3,10 @@ package nas import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" ) func TestLines(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/nifcloud/nas/nas_instance_test.go b/pkg/iac/adapters/terraform/nifcloud/nas/nas_instance_test.go index 29e52ea65037..f7a4965b1703 100644 --- a/pkg/iac/adapters/terraform/nifcloud/nas/nas_instance_test.go +++ b/pkg/iac/adapters/terraform/nifcloud/nas/nas_instance_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/nas" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptNASInstances(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/nifcloud/nas/nas_security_group_test.go b/pkg/iac/adapters/terraform/nifcloud/nas/nas_security_group_test.go index 786aec2fa6d6..a83ac31024ac 100644 --- a/pkg/iac/adapters/terraform/nifcloud/nas/nas_security_group_test.go +++ b/pkg/iac/adapters/terraform/nifcloud/nas/nas_security_group_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/nas" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptNASSecurityGroups(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/nifcloud/network/adapt_test.go b/pkg/iac/adapters/terraform/nifcloud/network/adapt_test.go index 9a4277b28558..44af9f204343 100644 --- a/pkg/iac/adapters/terraform/nifcloud/network/adapt_test.go +++ b/pkg/iac/adapters/terraform/nifcloud/network/adapt_test.go @@ -3,10 +3,10 @@ package network import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" ) func TestLines(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/nifcloud/rdb/adapt_test.go b/pkg/iac/adapters/terraform/nifcloud/rdb/adapt_test.go index 5360773bd505..4aeb48ce5b53 100644 --- a/pkg/iac/adapters/terraform/nifcloud/rdb/adapt_test.go +++ b/pkg/iac/adapters/terraform/nifcloud/rdb/adapt_test.go @@ -3,10 +3,10 @@ package rdb import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" ) func TestLines(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/nifcloud/rdb/db_instance_test.go b/pkg/iac/adapters/terraform/nifcloud/rdb/db_instance_test.go index de09fe59e4e8..b6ffd7985a5b 100644 --- a/pkg/iac/adapters/terraform/nifcloud/rdb/db_instance_test.go +++ b/pkg/iac/adapters/terraform/nifcloud/rdb/db_instance_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/rdb" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptDBInstances(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/nifcloud/rdb/db_security_group_test.go b/pkg/iac/adapters/terraform/nifcloud/rdb/db_security_group_test.go index ab4fc34f1384..3833e8241101 100644 --- a/pkg/iac/adapters/terraform/nifcloud/rdb/db_security_group_test.go +++ b/pkg/iac/adapters/terraform/nifcloud/rdb/db_security_group_test.go @@ -5,9 +5,8 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/nifcloud/rdb" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_adaptDBSecurityGroups(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/nifcloud/sslcertificate/adapt_test.go b/pkg/iac/adapters/terraform/nifcloud/sslcertificate/adapt_test.go index 0c044cb049a9..0f045f1c2c52 100644 --- a/pkg/iac/adapters/terraform/nifcloud/sslcertificate/adapt_test.go +++ b/pkg/iac/adapters/terraform/nifcloud/sslcertificate/adapt_test.go @@ -3,10 +3,10 @@ package sslcertificate import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" ) func TestLines(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/openstack/adapt_test.go b/pkg/iac/adapters/terraform/openstack/adapt_test.go index 0f04b570ff86..7baec6bb8413 100644 --- a/pkg/iac/adapters/terraform/openstack/adapt_test.go +++ b/pkg/iac/adapters/terraform/openstack/adapt_test.go @@ -3,14 +3,13 @@ package openstack import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/openstack" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func TestFields(t *testing.T) { diff --git a/pkg/iac/adapters/terraform/openstack/networking_test.go b/pkg/iac/adapters/terraform/openstack/networking_test.go index 8790dff42cf7..9fafa04bbfc1 100644 --- a/pkg/iac/adapters/terraform/openstack/networking_test.go +++ b/pkg/iac/adapters/terraform/openstack/networking_test.go @@ -3,10 +3,10 @@ package openstack import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/tftestutil" ) func Test_Networking(t *testing.T) { diff --git a/pkg/iac/ignore/rule_test.go b/pkg/iac/ignore/rule_test.go index 29c38bf76b5d..7cd4d382a410 100644 --- a/pkg/iac/ignore/rule_test.go +++ b/pkg/iac/ignore/rule_test.go @@ -3,9 +3,10 @@ package ignore_test import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/aquasecurity/trivy/pkg/iac/ignore" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" ) func metadataWithLine(path string, line int) types.Metadata { diff --git a/pkg/iac/rego/convert/slice_test.go b/pkg/iac/rego/convert/slice_test.go index 5e071d0ed52a..ef0b6da6fa21 100644 --- a/pkg/iac/rego/convert/slice_test.go +++ b/pkg/iac/rego/convert/slice_test.go @@ -4,9 +4,9 @@ import ( "reflect" "testing" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_SliceConversion(t *testing.T) { diff --git a/pkg/iac/rego/embed_test.go b/pkg/iac/rego/embed_test.go index 35fd4a667e80..15001119a934 100644 --- a/pkg/iac/rego/embed_test.go +++ b/pkg/iac/rego/embed_test.go @@ -3,12 +3,13 @@ package rego import ( "testing" - checks "github.com/aquasecurity/trivy-checks" - "github.com/aquasecurity/trivy/pkg/iac/rules" - "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/open-policy-agent/opa/ast" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + checks "github.com/aquasecurity/trivy-checks" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scan" ) func Test_EmbeddedLoading(t *testing.T) { diff --git a/pkg/iac/rego/load_test.go b/pkg/iac/rego/load_test.go index a2aca7abaff5..ede06e4610ed 100644 --- a/pkg/iac/rego/load_test.go +++ b/pkg/iac/rego/load_test.go @@ -9,11 +9,11 @@ import ( "testing" "testing/fstest" - checks "github.com/aquasecurity/trivy-checks" "github.com/open-policy-agent/opa/ast" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + checks "github.com/aquasecurity/trivy-checks" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" diff --git a/pkg/iac/rego/metadata_test.go b/pkg/iac/rego/metadata_test.go index 29f990a9b506..c2b38ba79740 100644 --- a/pkg/iac/rego/metadata_test.go +++ b/pkg/iac/rego/metadata_test.go @@ -3,10 +3,11 @@ package rego import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/framework" - "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/scan" ) func Test_UpdateStaticMetadata(t *testing.T) { diff --git a/pkg/iac/rego/scanner_test.go b/pkg/iac/rego/scanner_test.go index d97ffa675b26..cc4bb785433a 100644 --- a/pkg/iac/rego/scanner_test.go +++ b/pkg/iac/rego/scanner_test.go @@ -10,13 +10,13 @@ import ( "testing" "testing/fstest" - "github.com/aquasecurity/trivy/pkg/iac/severity" - "github.com/aquasecurity/trivy/pkg/iac/types" "github.com/liamg/memoryfs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/severity" + "github.com/aquasecurity/trivy/pkg/iac/types" ) func CreateFS(t *testing.T, files map[string]string) fs.FS { diff --git a/pkg/iac/rules/register_test.go b/pkg/iac/rules/register_test.go index 5f2ec0874535..9c6ca00985c9 100644 --- a/pkg/iac/rules/register_test.go +++ b/pkg/iac/rules/register_test.go @@ -4,10 +4,11 @@ import ( "fmt" "testing" - "github.com/aquasecurity/trivy/pkg/iac/framework" - "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/scan" ) func Test_Reset(t *testing.T) { diff --git a/pkg/iac/scan/result_test.go b/pkg/iac/scan/result_test.go index d5b9a4a577f0..67da0d61be86 100644 --- a/pkg/iac/scan/result_test.go +++ b/pkg/iac/scan/result_test.go @@ -3,9 +3,10 @@ package scan_test import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" ) func Test_Occurrences(t *testing.T) { diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/bench_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/bench_test.go index 12bc71eae899..4d08c8e3b9c6 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/bench_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/bench_test.go @@ -4,9 +4,9 @@ import ( "encoding/json" "testing" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/types" ) func BenchmarkUnmarshal_JFather(b *testing.B) { diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/decode_meta_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/decode_meta_test.go index 9924288497fe..29c1d4c5aa59 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/decode_meta_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/decode_meta_test.go @@ -3,10 +3,10 @@ package armjson import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/types" ) type TestParent struct { diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go index 87169460cd0d..6f25eb4d5aad 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go @@ -3,10 +3,10 @@ package armjson import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Array_Empty(t *testing.T) { diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean_test.go index 106d55f426f5..6abb9da200b7 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean_test.go @@ -3,10 +3,10 @@ package armjson import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Boolean_True(t *testing.T) { diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_complex_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_complex_test.go index 3584ebc97fa1..550f94a55794 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_complex_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_complex_test.go @@ -3,9 +3,9 @@ package armjson import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Complex(t *testing.T) { diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_null_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_null_test.go index 4f6d109fdf4f..3455809b9874 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_null_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_null_test.go @@ -3,9 +3,9 @@ package armjson import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Null(t *testing.T) { diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_number_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_number_test.go index 39226b090ede..f0f7532c15a4 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_number_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_number_test.go @@ -3,10 +3,10 @@ package armjson import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Number_IntToInt(t *testing.T) { diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go index f689ebd0fa26..1477f56f9231 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go @@ -3,10 +3,10 @@ package armjson import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Object(t *testing.T) { diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_string_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_string_test.go index b2c546f479d2..ab630222b6c5 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_string_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_string_test.go @@ -3,10 +3,10 @@ package armjson import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_String(t *testing.T) { diff --git a/pkg/iac/scanners/azure/arm/parser/parser_test.go b/pkg/iac/scanners/azure/arm/parser/parser_test.go index 759127ca0b00..3273c0dd6596 100644 --- a/pkg/iac/scanners/azure/arm/parser/parser_test.go +++ b/pkg/iac/scanners/azure/arm/parser/parser_test.go @@ -6,14 +6,14 @@ import ( "os" "testing" - azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" - "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/resolver" - "github.com/aquasecurity/trivy/pkg/iac/types" "github.com/liamg/memoryfs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/resolver" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/types" ) func createMetadata(targetFS fs.FS, filename string, start, end int, ref string, parent *types.Metadata) types.Metadata { diff --git a/pkg/iac/scanners/azure/arm/parser/template_test.go b/pkg/iac/scanners/azure/arm/parser/template_test.go index 3c96c8cf588f..493cee263582 100644 --- a/pkg/iac/scanners/azure/arm/parser/template_test.go +++ b/pkg/iac/scanners/azure/arm/parser/template_test.go @@ -5,11 +5,12 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + types2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser/armjson" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_JSONUnmarshal(t *testing.T) { diff --git a/pkg/iac/scanners/azure/resolver/resolver_test.go b/pkg/iac/scanners/azure/resolver/resolver_test.go index b4aabdce0a7f..9b9502c06c08 100644 --- a/pkg/iac/scanners/azure/resolver/resolver_test.go +++ b/pkg/iac/scanners/azure/resolver/resolver_test.go @@ -4,9 +4,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/require" ) func Test_resolveFunc(t *testing.T) { diff --git a/pkg/iac/scanners/azure/value_test.go b/pkg/iac/scanners/azure/value_test.go index 646ddc0b0cd0..9392250b03d4 100644 --- a/pkg/iac/scanners/azure/value_test.go +++ b/pkg/iac/scanners/azure/value_test.go @@ -3,8 +3,9 @@ package azure import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/types" "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_ValueAsInt(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_and_test.go b/pkg/iac/scanners/cloudformation/parser/fn_and_test.go index 2663270e99dc..5222decef06f 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_and_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_and_test.go @@ -3,11 +3,11 @@ package parser import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_and_value(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go b/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go index 7a30827f761c..7fdc77b30fa5 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go @@ -1,12 +1,13 @@ package parser import ( - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_base64_value(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go b/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go index 0bea529c280e..c5f0af721c36 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go @@ -3,9 +3,10 @@ package parser import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" ) func Test_resolve_condition_value(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go b/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go index e3a806798393..0e550d162986 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go @@ -3,11 +3,11 @@ package parser import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_equals_value(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_find_in_map_test.go b/pkg/iac/scanners/cloudformation/parser/fn_find_in_map_test.go index 6063c39fc006..254c0b63c464 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_find_in_map_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_find_in_map_test.go @@ -1,10 +1,10 @@ package parser import ( + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "testing" ) func Test_resolve_find_in_map_value(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_get_attr_test.go b/pkg/iac/scanners/cloudformation/parser/fn_get_attr_test.go index ebd52da035b0..83e059c5c0f9 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_get_attr_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_get_attr_test.go @@ -1,10 +1,10 @@ package parser import ( + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "testing" ) func Test_resolve_get_attr_value(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_if_test.go b/pkg/iac/scanners/cloudformation/parser/fn_if_test.go index 0e5e41bbc963..4ecd5f9483c8 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_if_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_if_test.go @@ -3,11 +3,11 @@ package parser import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_if_value(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_join_test.go b/pkg/iac/scanners/cloudformation/parser/fn_join_test.go index 8eca9ad5763b..628ade9a1acf 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_join_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_join_test.go @@ -3,11 +3,11 @@ package parser import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_join_value(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_length_test.go b/pkg/iac/scanners/cloudformation/parser/fn_length_test.go index aa916ad0a972..402211bdcd98 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_length_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_length_test.go @@ -3,8 +3,9 @@ package parser import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" ) func Test_ResolveLength_WhenPropIsArray(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_not_test.go b/pkg/iac/scanners/cloudformation/parser/fn_not_test.go index 44df6fa6d421..563964c32290 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_not_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_not_test.go @@ -3,11 +3,11 @@ package parser import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_not_value(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_or_test.go b/pkg/iac/scanners/cloudformation/parser/fn_or_test.go index 18340031434b..7e0e1dcf0f0a 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_or_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_or_test.go @@ -3,11 +3,11 @@ package parser import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_or_value(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go b/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go index a535b30386e2..7f0e141b96a6 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go @@ -3,11 +3,11 @@ package parser import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_referenced_value(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_split_test.go b/pkg/iac/scanners/cloudformation/parser/fn_split_test.go index 59261ff57a20..33ca111c15ea 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_split_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_split_test.go @@ -1,12 +1,13 @@ package parser import ( - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" + "github.com/aquasecurity/trivy/pkg/iac/types" ) /* diff --git a/pkg/iac/scanners/cloudformation/parser/parser_test.go b/pkg/iac/scanners/cloudformation/parser/parser_test.go index 3a517552af57..fc8a087bdfec 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser_test.go +++ b/pkg/iac/scanners/cloudformation/parser/parser_test.go @@ -7,9 +7,10 @@ import ( "strings" "testing" - "github.com/aquasecurity/trivy/internal/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/internal/testutil" ) func parseFile(t *testing.T, source string, name string) (FileContexts, error) { diff --git a/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go b/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go index 4b3779eac587..a537a3c1e9ab 100644 --- a/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go +++ b/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go @@ -3,9 +3,10 @@ package parser import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/assert" ) func newProp(inner PropertyInner) *Property { diff --git a/pkg/iac/scanners/cloudformation/parser/resource_test.go b/pkg/iac/scanners/cloudformation/parser/resource_test.go index 89d2448954e6..1b67fc1d773b 100644 --- a/pkg/iac/scanners/cloudformation/parser/resource_test.go +++ b/pkg/iac/scanners/cloudformation/parser/resource_test.go @@ -3,8 +3,9 @@ package parser import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" ) func Test_GetProperty_PropIsFunction(t *testing.T) { diff --git a/pkg/iac/scanners/cloudformation/scanner_test.go b/pkg/iac/scanners/cloudformation/scanner_test.go index 3264609557ac..0319a862ab74 100644 --- a/pkg/iac/scanners/cloudformation/scanner_test.go +++ b/pkg/iac/scanners/cloudformation/scanner_test.go @@ -5,12 +5,12 @@ import ( "strings" "testing" - "github.com/aquasecurity/trivy/internal/testutil" - "github.com/aquasecurity/trivy/pkg/iac/framework" - "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) diff --git a/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go b/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go index 3fd466e40ed8..4ab3009d4dab 100644 --- a/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go +++ b/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go @@ -6,10 +6,10 @@ import ( "os" "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) diff --git a/pkg/iac/scanners/dockerfile/scanner_test.go b/pkg/iac/scanners/dockerfile/scanner_test.go index 4d4d88bcb675..310f104affaa 100644 --- a/pkg/iac/scanners/dockerfile/scanner_test.go +++ b/pkg/iac/scanners/dockerfile/scanner_test.go @@ -5,14 +5,15 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/rego/schemas" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) const DS006PolicyWithDockerfileSchema = `# METADATA diff --git a/pkg/iac/scanners/helm/test/parser_test.go b/pkg/iac/scanners/helm/test/parser_test.go index 762905c2abd4..0116e8b7670b 100644 --- a/pkg/iac/scanners/helm/test/parser_test.go +++ b/pkg/iac/scanners/helm/test/parser_test.go @@ -7,10 +7,11 @@ import ( "strings" "testing" - "github.com/aquasecurity/trivy/pkg/iac/detection" - "github.com/aquasecurity/trivy/pkg/iac/scanners/helm/parser" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/iac/scanners/helm/parser" ) func Test_helm_parser(t *testing.T) { diff --git a/pkg/iac/scanners/helm/test/scanner_test.go b/pkg/iac/scanners/helm/test/scanner_test.go index a7228601b24d..45a426762439 100644 --- a/pkg/iac/scanners/helm/test/scanner_test.go +++ b/pkg/iac/scanners/helm/test/scanner_test.go @@ -10,10 +10,11 @@ import ( "strings" "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/helm" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/helm" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_helm_scanner_with_archive(t *testing.T) { diff --git a/pkg/iac/scanners/json/scanner_test.go b/pkg/iac/scanners/json/scanner_test.go index 774d26612823..bd24a09e055c 100644 --- a/pkg/iac/scanners/json/scanner_test.go +++ b/pkg/iac/scanners/json/scanner_test.go @@ -4,12 +4,13 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_BasicScan(t *testing.T) { diff --git a/pkg/iac/scanners/kubernetes/scanner_test.go b/pkg/iac/scanners/kubernetes/scanner_test.go index a5f3aaf90938..4745b442402d 100644 --- a/pkg/iac/scanners/kubernetes/scanner_test.go +++ b/pkg/iac/scanners/kubernetes/scanner_test.go @@ -6,12 +6,13 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_BasicScan_YAML(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/attribute_test.go b/pkg/iac/scanners/terraform/attribute_test.go index e81486e938a2..5c97985d7146 100644 --- a/pkg/iac/scanners/terraform/attribute_test.go +++ b/pkg/iac/scanners/terraform/attribute_test.go @@ -3,8 +3,9 @@ package terraform import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/terraform" "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/iac/terraform" ) func Test_AttributeStartsWith(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/count_test.go b/pkg/iac/scanners/terraform/count_test.go index 99e447196fa1..89aadbbf82c8 100644 --- a/pkg/iac/scanners/terraform/count_test.go +++ b/pkg/iac/scanners/terraform/count_test.go @@ -3,13 +3,14 @@ package terraform import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/severity" "github.com/aquasecurity/trivy/pkg/iac/terraform" - "github.com/stretchr/testify/assert" ) func Test_ResourcesWithCount(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/deterministic_test.go b/pkg/iac/scanners/terraform/deterministic_test.go index 258fe5bbbd16..ccf2b7123e0c 100644 --- a/pkg/iac/scanners/terraform/deterministic_test.go +++ b/pkg/iac/scanners/terraform/deterministic_test.go @@ -4,11 +4,12 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" - "github.com/stretchr/testify/require" ) func Test_DeterministicResults(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/executor/executor_test.go b/pkg/iac/scanners/terraform/executor/executor_test.go index fa25a133ded8..838701d163f4 100644 --- a/pkg/iac/scanners/terraform/executor/executor_test.go +++ b/pkg/iac/scanners/terraform/executor/executor_test.go @@ -4,6 +4,9 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rules" @@ -11,8 +14,6 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" "github.com/aquasecurity/trivy/pkg/iac/severity" "github.com/aquasecurity/trivy/pkg/iac/terraform" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var panicRule = scan.Rule{ diff --git a/pkg/iac/scanners/terraform/fs_test.go b/pkg/iac/scanners/terraform/fs_test.go index 0089bc49a477..79b0844fe983 100644 --- a/pkg/iac/scanners/terraform/fs_test.go +++ b/pkg/iac/scanners/terraform/fs_test.go @@ -5,9 +5,10 @@ import ( "os" "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_OS_FS(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/ignore_test.go b/pkg/iac/scanners/terraform/ignore_test.go index ce0596f157a9..3b0a83428a50 100644 --- a/pkg/iac/scanners/terraform/ignore_test.go +++ b/pkg/iac/scanners/terraform/ignore_test.go @@ -5,12 +5,13 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/severity" "github.com/aquasecurity/trivy/pkg/iac/terraform" - "github.com/stretchr/testify/assert" ) var exampleRule = scan.Rule{ diff --git a/pkg/iac/scanners/terraform/module_test.go b/pkg/iac/scanners/terraform/module_test.go index d369ffefe44c..5469204e741f 100644 --- a/pkg/iac/scanners/terraform/module_test.go +++ b/pkg/iac/scanners/terraform/module_test.go @@ -7,6 +7,9 @@ import ( "os" "testing" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-checks/checks/cloud/aws/iam" "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rules" @@ -16,9 +19,6 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" "github.com/aquasecurity/trivy/pkg/iac/severity" "github.com/aquasecurity/trivy/pkg/iac/terraform" - "github.com/stretchr/testify/require" - - "github.com/aquasecurity/trivy-checks/checks/cloud/aws/iam" ) var badRule = scan.Rule{ diff --git a/pkg/iac/scanners/terraform/parser/load_vars_test.go b/pkg/iac/scanners/terraform/parser/load_vars_test.go index a207a5383125..384ced396eba 100644 --- a/pkg/iac/scanners/terraform/parser/load_vars_test.go +++ b/pkg/iac/scanners/terraform/parser/load_vars_test.go @@ -3,11 +3,11 @@ package parser import ( "testing" - "github.com/aquasecurity/trivy/internal/testutil" - "github.com/zclconf/go-cty/cty" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" + + "github.com/aquasecurity/trivy/internal/testutil" ) func Test_TFVarsFile(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/parser/modules_test.go b/pkg/iac/scanners/terraform/parser/modules_test.go index 142783160f01..404a1effcfb1 100644 --- a/pkg/iac/scanners/terraform/parser/modules_test.go +++ b/pkg/iac/scanners/terraform/parser/modules_test.go @@ -5,11 +5,12 @@ import ( "path" "testing" - "github.com/aquasecurity/trivy/internal/testutil" "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" + + "github.com/aquasecurity/trivy/internal/testutil" ) func TestFindRootModules(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/parser/parser_integration_test.go b/pkg/iac/scanners/terraform/parser/parser_integration_test.go index 58b7b3cfbed7..f73e9b85539c 100644 --- a/pkg/iac/scanners/terraform/parser/parser_integration_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_integration_test.go @@ -4,8 +4,9 @@ import ( "context" "testing" - "github.com/aquasecurity/trivy/internal/testutil" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/internal/testutil" ) func Test_DefaultRegistry(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index 48b8b6765bae..b00b8c42ce56 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -7,12 +7,13 @@ import ( "sort" "testing" - "github.com/aquasecurity/trivy/internal/testutil" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - "github.com/aquasecurity/trivy/pkg/iac/terraform" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zclconf/go-cty/cty" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/terraform" ) func Test_BasicParsing(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/scanner_integration_test.go b/pkg/iac/scanners/terraform/scanner_integration_test.go index 56c557e8be2d..6c60dd8a9f2c 100644 --- a/pkg/iac/scanners/terraform/scanner_integration_test.go +++ b/pkg/iac/scanners/terraform/scanner_integration_test.go @@ -6,10 +6,11 @@ import ( "fmt" "testing" - "github.com/aquasecurity/trivy/internal/testutil" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_ScanRemoteModule(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/scanner_test.go b/pkg/iac/scanners/terraform/scanner_test.go index 047ceb972a2a..cfa7cd0d0775 100644 --- a/pkg/iac/scanners/terraform/scanner_test.go +++ b/pkg/iac/scanners/terraform/scanner_test.go @@ -7,6 +7,9 @@ import ( "strconv" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rules" @@ -14,8 +17,6 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/severity" "github.com/aquasecurity/trivy/pkg/iac/terraform" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var alwaysFailRule = scan.Rule{ diff --git a/pkg/iac/scanners/terraform/setup_test.go b/pkg/iac/scanners/terraform/setup_test.go index 84bf3fdcc338..06930e6ffda4 100644 --- a/pkg/iac/scanners/terraform/setup_test.go +++ b/pkg/iac/scanners/terraform/setup_test.go @@ -4,12 +4,13 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" "github.com/aquasecurity/trivy/pkg/iac/terraform" - "github.com/stretchr/testify/require" ) func createModulesFromSource(t *testing.T, source string, ext string) terraform.Modules { diff --git a/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go b/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go index 474894339b17..9eca249c1f16 100644 --- a/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go @@ -9,12 +9,13 @@ import ( "sort" "testing" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - tfscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + tfscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" ) func initScanner(opts ...options.ScannerOption) *Scanner { diff --git a/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go b/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go index 664799f74036..8a878e206bb3 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go @@ -7,10 +7,11 @@ import ( "os" "testing" - "github.com/aquasecurity/trivy/internal/testutil" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_TerraformScanner(t *testing.T) { diff --git a/pkg/iac/scanners/terraformplan/tfjson/test/parser_test.go b/pkg/iac/scanners/terraformplan/tfjson/test/parser_test.go index 97b9ba4fcf7b..85a63c6227ef 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/test/parser_test.go +++ b/pkg/iac/scanners/terraformplan/tfjson/test/parser_test.go @@ -3,9 +3,10 @@ package json import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson/parser" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson/parser" ) func Test_Parse_Plan_File(t *testing.T) { diff --git a/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go b/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go index 2feb3d2fae9b..5b684dc77ed0 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go @@ -5,12 +5,12 @@ import ( "testing" "testing/fstest" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson" ) func Test_Scanning_Plan(t *testing.T) { diff --git a/pkg/iac/scanners/toml/scanner_test.go b/pkg/iac/scanners/toml/scanner_test.go index c3217587cd55..15583428d1da 100644 --- a/pkg/iac/scanners/toml/scanner_test.go +++ b/pkg/iac/scanners/toml/scanner_test.go @@ -4,12 +4,13 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_BasicScan(t *testing.T) { diff --git a/pkg/iac/scanners/yaml/scanner_test.go b/pkg/iac/scanners/yaml/scanner_test.go index f4554997cf5a..a9d124be6858 100644 --- a/pkg/iac/scanners/yaml/scanner_test.go +++ b/pkg/iac/scanners/yaml/scanner_test.go @@ -4,12 +4,13 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_BasicScan(t *testing.T) { diff --git a/pkg/iac/state/merge_test.go b/pkg/iac/state/merge_test.go index ff8da78a5ac2..baf3959f3890 100644 --- a/pkg/iac/state/merge_test.go +++ b/pkg/iac/state/merge_test.go @@ -3,14 +3,12 @@ package state import ( "testing" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/stretchr/testify/assert" "github.com/aquasecurity/trivy/pkg/iac/providers/aws" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" - - "github.com/stretchr/testify/assert" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_Merging(t *testing.T) { diff --git a/pkg/iac/state/state_test.go b/pkg/iac/state/state_test.go index 0b3f3235f6bb..89169693b8c9 100644 --- a/pkg/iac/state/state_test.go +++ b/pkg/iac/state/state_test.go @@ -4,14 +4,12 @@ import ( "encoding/json" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/iac/providers/aws" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" - - "github.com/stretchr/testify/assert" + iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_RegoConversion(t *testing.T) { diff --git a/pkg/iac/types/bool_test.go b/pkg/iac/types/bool_test.go old mode 100755 new mode 100644 index ebd65329086d..a63a95eb7a63 --- a/pkg/iac/types/bool_test.go +++ b/pkg/iac/types/bool_test.go @@ -4,9 +4,8 @@ import ( "encoding/json" "testing" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var fakeMetadata = NewMetadata(NewRange("main.tf", 123, 123, "", nil), "") diff --git a/pkg/iac/types/fskey_test.go b/pkg/iac/types/fskey_test.go index 1b3e6cf42aca..37be8fce4f0d 100644 --- a/pkg/iac/types/fskey_test.go +++ b/pkg/iac/types/fskey_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/liamg/memoryfs" - "github.com/stretchr/testify/assert" ) diff --git a/pkg/iac/types/string_test.go b/pkg/iac/types/string_test.go old mode 100755 new mode 100644 index 1f135874bf18..a923d193d85e --- a/pkg/iac/types/string_test.go +++ b/pkg/iac/types/string_test.go @@ -4,9 +4,8 @@ import ( "encoding/json" "testing" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_StringValueEqualTo(t *testing.T) { diff --git a/pkg/k8s/scanner/scanner_test.go b/pkg/k8s/scanner/scanner_test.go index ec6aa50baef0..3364c4ce299c 100644 --- a/pkg/k8s/scanner/scanner_test.go +++ b/pkg/k8s/scanner/scanner_test.go @@ -5,19 +5,18 @@ import ( "sort" "testing" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/sbom/core" - "github.com/aquasecurity/trivy/pkg/uuid" - "github.com/stretchr/testify/require" - "golang.org/x/exp/maps" - "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" cmd "github.com/aquasecurity/trivy/pkg/commands/artifact" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/uuid" ) func TestScanner_Scan(t *testing.T) { diff --git a/pkg/k8s/writer_test.go b/pkg/k8s/writer_test.go index 1b5b6af93c9c..0b2253d9ac24 100644 --- a/pkg/k8s/writer_test.go +++ b/pkg/k8s/writer_test.go @@ -7,9 +7,8 @@ import ( "strings" "testing" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/k8s/report" diff --git a/pkg/log/handler_test.go b/pkg/log/handler_test.go index 4d106535b8d8..a122e46f5880 100644 --- a/pkg/log/handler_test.go +++ b/pkg/log/handler_test.go @@ -5,14 +5,16 @@ import ( "context" "errors" "fmt" - "github.com/aquasecurity/trivy/pkg/log" - "github.com/stretchr/testify/assert" "log/slog" "os" "strings" "testing" "testing/slogtest" "time" + + "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/log" ) func TestColorHandler(t *testing.T) { diff --git a/pkg/plugin/index_test.go b/pkg/plugin/index_test.go index 028c46a10ca4..7e5621d5ca1c 100644 --- a/pkg/plugin/index_test.go +++ b/pkg/plugin/index_test.go @@ -3,15 +3,17 @@ package plugin_test import ( "bytes" "context" - "github.com/aquasecurity/trivy/pkg/plugin" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "net/http" "net/http/httptest" "os" "path/filepath" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/plugin" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) func TestManager_Update(t *testing.T) { diff --git a/pkg/plugin/manager_test.go b/pkg/plugin/manager_test.go index e2f3c5b6e82e..ce86fde40088 100644 --- a/pkg/plugin/manager_test.go +++ b/pkg/plugin/manager_test.go @@ -6,9 +6,6 @@ import ( "bytes" "context" "fmt" - "github.com/aquasecurity/trivy/pkg/clock" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" - v1 "github.com/google/go-containerregistry/pkg/v1" "log/slog" "os" "path/filepath" @@ -16,9 +13,12 @@ import ( "testing" "time" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/clock" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/plugin" ) diff --git a/pkg/report/table/secret_test.go b/pkg/report/table/secret_test.go index 92011f8d17ff..8eb59af18476 100644 --- a/pkg/report/table/secret_test.go +++ b/pkg/report/table/secret_test.go @@ -1,7 +1,6 @@ package table_test import ( - "github.com/aquasecurity/trivy/pkg/types" "strings" "testing" @@ -10,6 +9,7 @@ import ( dbTypes "github.com/aquasecurity/trivy-db/pkg/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/report/table" + "github.com/aquasecurity/trivy/pkg/types" ) func TestSecretRenderer(t *testing.T) { diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index 2cec7d98c801..b66828241634 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -4,12 +4,11 @@ import ( "bytes" "testing" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/report/table" "github.com/aquasecurity/trivy/pkg/types" ) diff --git a/pkg/result/filter_test.go b/pkg/result/filter_test.go index 71ac8644cd36..5c1eeb6771c3 100644 --- a/pkg/result/filter_test.go +++ b/pkg/result/filter_test.go @@ -2,16 +2,16 @@ package result_test import ( "context" - "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/package-url/packageurl-go" "testing" "time" + "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/result" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go index d3d104b28347..d86cbfd1a218 100644 --- a/pkg/sbom/cyclonedx/marshal_test.go +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -2,14 +2,12 @@ package cyclonedx_test import ( "context" - "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/sbom/core" - "github.com/package-url/packageurl-go" "testing" "time" cdx "github.com/CycloneDX/cyclonedx-go" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/package-url/packageurl-go" "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -17,8 +15,10 @@ import ( dtypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/uuid" diff --git a/pkg/sbom/cyclonedx/unmarshal_test.go b/pkg/sbom/cyclonedx/unmarshal_test.go index 9988d21c75ed..834e886c46dd 100644 --- a/pkg/sbom/cyclonedx/unmarshal_test.go +++ b/pkg/sbom/cyclonedx/unmarshal_test.go @@ -2,18 +2,18 @@ package cyclonedx_test import ( "encoding/json" - "github.com/aquasecurity/trivy/pkg/purl" - sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" - "github.com/aquasecurity/trivy/pkg/types" - "github.com/package-url/packageurl-go" "os" "testing" + "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/purl" "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" + "github.com/aquasecurity/trivy/pkg/types" ) func TestUnmarshaler_Unmarshal(t *testing.T) { diff --git a/pkg/sbom/io/encode_test.go b/pkg/sbom/io/encode_test.go index e26dd83848e8..43c0d022556b 100644 --- a/pkg/sbom/io/encode_test.go +++ b/pkg/sbom/io/encode_test.go @@ -1,6 +1,12 @@ package io_test import ( + "testing" + + "github.com/package-url/packageurl-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + dtypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -8,10 +14,6 @@ import ( sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/uuid" - "github.com/package-url/packageurl-go" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "testing" ) func TestEncoder_Encode(t *testing.T) { diff --git a/pkg/sbom/spdx/marshal_test.go b/pkg/sbom/spdx/marshal_test.go index ee4c10949148..02585ceaa418 100644 --- a/pkg/sbom/spdx/marshal_test.go +++ b/pkg/sbom/spdx/marshal_test.go @@ -2,23 +2,23 @@ package spdx_test import ( "context" - "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/sbom/core" - "github.com/package-url/packageurl-go" "hash/fnv" "testing" "time" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/mitchellh/hashstructure/v2" + "github.com/package-url/packageurl-go" "github.com/spdx/tools-golang/spdx" "github.com/spdx/tools-golang/spdx/v2/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/sbom/core" tspdx "github.com/aquasecurity/trivy/pkg/sbom/spdx" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/uuid" diff --git a/pkg/sbom/spdx/unmarshal_test.go b/pkg/sbom/spdx/unmarshal_test.go index 4348618e0f4d..c68d9f32e654 100644 --- a/pkg/sbom/spdx/unmarshal_test.go +++ b/pkg/sbom/spdx/unmarshal_test.go @@ -2,16 +2,16 @@ package spdx_test import ( "encoding/json" - sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" - "github.com/package-url/packageurl-go" "os" "sort" "testing" + "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" "github.com/aquasecurity/trivy/pkg/sbom/spdx" "github.com/aquasecurity/trivy/pkg/types" ) diff --git a/pkg/scanner/scan_test.go b/pkg/scanner/scan_test.go index eaa0a6028e69..287935ddd41f 100644 --- a/pkg/scanner/scan_test.go +++ b/pkg/scanner/scan_test.go @@ -3,13 +3,13 @@ package scanner import ( "context" "errors" - "github.com/aquasecurity/trivy/pkg/clock" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go index e7699aff2fb9..cce5b31d8517 100644 --- a/pkg/vex/vex_test.go +++ b/pkg/vex/vex_test.go @@ -1,18 +1,17 @@ package vex_test import ( - "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/sbom/core" "os" "testing" "github.com/package-url/packageurl-go" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/vex" ) From 48a718394da176807b7ccefd38b11407a3807cef Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 22 May 2024 11:32:48 +0600 Subject: [PATCH 088/352] ci: add groups for `dependabot` (#6734) --- .github/dependabot.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a61a92b3cb11..cc152f6b231c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,12 +4,34 @@ updates: directory: / schedule: interval: monthly + groups: + github-actions: + patterns: + - "*" - package-ecosystem: docker directory: / schedule: interval: monthly + groups: + docker: + patterns: + - "*" - package-ecosystem: gomod open-pull-requests-limit: 10 directory: / schedule: - interval: monthly + interval: weekly + groups: + aws: + patterns: + - "github.com/aws/*" + docker: + patterns: + - "github.com/docker/*" + - "github.com/moby/*" + testcontainers: + patterns: + - "github.com/testcontainers/*" + common: + patterns: + - "*" \ No newline at end of file From 693d8c5293ad5b9663667c77e713e2e08d5ed053 Mon Sep 17 00:00:00 2001 From: Anais Urlichs <33576047+AnaisUrlichs@users.noreply.github.com> Date: Wed, 22 May 2024 12:40:55 +0100 Subject: [PATCH 089/352] docs: Add documentation for contributing additional checks to the trivy policies repo (#6234) Signed-off-by: AnaisUrlichs Co-authored-by: simar7 <1254783+simar7@users.noreply.github.com> --- docs/community/contribute/checks/overview.md | 130 ++++++++++++++++++ .../contribute/checks/service-support.md | 69 ++++++++++ .../custom/contribute-checks.md | 5 + .../scanner/misconfiguration/custom/index.md | 37 +++-- .../scanner/misconfiguration/custom/schema.md | 3 +- mkdocs.yml | 4 + 6 files changed, 239 insertions(+), 9 deletions(-) create mode 100644 docs/community/contribute/checks/overview.md create mode 100644 docs/community/contribute/checks/service-support.md create mode 100644 docs/docs/scanner/misconfiguration/custom/contribute-checks.md diff --git a/docs/community/contribute/checks/overview.md b/docs/community/contribute/checks/overview.md new file mode 100644 index 000000000000..ac6d90aef2fd --- /dev/null +++ b/docs/community/contribute/checks/overview.md @@ -0,0 +1,130 @@ +# Contribute Rego Checks + +The following guide provides an overview of contributing checks to the default checks in Trivy. + +All of the checks in Trivy can be found in the [trivy-checks](https://github.com/aquasecurity/trivy-checks/tree/main) repository on GitHub. Before you begin writing a check, ensure: + +1. The check does not already exist as part of the default checks in the [trivy-checks](https://github.com/aquasecurity/trivy-checks/tree/main) repository. +2. The pull requests in the [trivy-checks](https://github.com/aquasecurity/trivy-checks/pulls) repository to see whether someone else is already contributing the check that you wanted to add. +3. The [issues in Trivy](https://github.com/aquasecurity/trivy/issues) to see whether any specific checks are missing in Trivy that you can contribute. + +If anything is unclear, please [start a discussion](https://github.com/aquasecurity/trivy/discussions/new) and we will do our best to help. + +## Check structure + +Checks are written in Rego and follow a particular structure in Trivy. Below is an example check for AWS: + +```rego +# METADATA +# title: "RDS IAM Database Authentication Disabled" +# description: "Ensure IAM Database Authentication is enabled for RDS database instances to manage database access" +# scope: package +# schemas: +# - input: schema["aws"] +# related_resources: +# - https://docs.aws.amazon.com/neptune/latest/userguide/iam-auth.html +# custom: +# id: AVD-AWS-0176 +# avd_id: AVD-AWS-0176 +# provider: aws +# service: rds +# severity: MEDIUM +# short_code: enable-iam-auth +# recommended_action: "Modify the PostgreSQL and MySQL type RDS instances to enable IAM database authentication." +# input: +# selector: +# - type: cloud +# subtypes: +# - service: rds +# provider: aws + +package builtin.aws.rds.aws0176 + +deny[res] { + instance := input.aws.rds.instances[_] + instance.engine.value == ["postgres", "mysql"][_] + not instance.iamauthenabled.value + res := result.new("Instance does not have IAM Authentication enabled", instance.iamauthenabled) +} +``` + +## Verify the provider and service exists + +Every check for a cloud service references a cloud provider. The list of providers are found in the [Trivy](https://github.com/aquasecurity/trivy/tree/main/pkg/iac/providers) repository. + +Before writing a new check for a cloud provider, you need to verify if the cloud provider or resource type that your check targets is supported by Trivy. If it's not, you'll need to add support for it. Additionally, if the provider that you want to target exists, you need to check whether the service your policy will target is supported. As a reference you can take a look at the AWS provider [here](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/providers/aws/aws.go). + +???+ note + New Kubernetes and Dockerfile checks do not require any additional provider definitions. You can find an example of a Dockerfile check [here](https://github.com/aquasecurity/trivy-checks/blob/main/checks/docker/add_instead_of_copy.rego) and a Kubernetes check [here](https://github.com/aquasecurity/trivy-checks/blob/main/checks/kubernetes/general/CPU_not_limited.rego). + + +### Add Support for a New Service in an existing Provider + +[Please reference the documentation on adding Support for a New Service](./service-support.md). + +This guide also showcases how to add new properties for an existing Service. + +## Create a new .rego file + +The following directory in the trivy-checks repository contains all of our custom checks. Depending on what type of check you want to create, you will need to nest a new `.rego` file in either of the [subdirectories](https://github.com/aquasecurity/trivy-checks/tree/main/checks): + +* cloud: All checks related to cloud providers and their services +* docker: Docker specific checks +* kubernetes: Kubernetes specific checks + +## Check Package name + +Have a look at the existing package names in the [built in checks](https://github.com/aquasecurity/trivy-checks/tree/main/checks). + +The package name should be in the format `builtin.PROVIDER.SERVICE.ID`, e.g. `builtin.aws.rds.aws0176`. + +## Generating an ID + +Every check has a custom ID that is referenced throughout the metadata of the check to uniquely identify the check. If you plan to contribue your check back into the [trivy-checks](https://github.com/aquasecurity/trivy-checks) repository, it will require a valid ID. + +Running `make id` in the root of the trivy-checks repository will provide you with the next available _ID_ for your rule. + +## Check Schemas + +Rego Checks for Trivy can utilise Schemas to map the input to specific objects. The schemas available are listed [here.](https://github.com/aquasecurity/trivy/tree/main/pkg/iac/rego/schemas). + +More information on using the builtin schemas is provided in the [main documentation.](../../../docs/scanner/misconfiguration/custom/schema.md) + +## Check Metadata + +The metadata is the top section that starts with `# METADATA`, and has to be placed on top of the check. You can copy and paste from another check as a starting point. This format is effectively _yaml_ within a Rego comment, and is [defined as part of Rego itself](https://www.openpolicyagent.org/docs/latest/policy-language/#metadata). + +For detailed information on each component of the Check Metadata, please refer to the [main documentation.](../../../docs/scanner/misconfiguration/custom/index.md) + +Note that while the Metadata is optional in your own custom checks for Trivy, if you are contributing your check to the Trivy builtin checks, the Metadata section will be required. + + +## Writing Rego Rules + +Rules are defined using _OPA Rego_. You can find a number of examples in the `checks` directory ([Link](https://github.com/aquasecurity/trivy-checks/tree/main/checks)). The [OPA documentation](https://www.openpolicyagent.org/docs/latest/policy-language/) is a great place to start learning Rego. You can also check out the [Rego Playground](https://play.openpolicyagent.org/) to experiment with Rego, and [join the OPA Slack](https://slack.openpolicyagent.org/). + + +```rego +deny[res] { + instance := input.aws.rds.instances[_] + instance.engine.value == ["postgres", "mysql"][_] + not instance.iamauthenabled.value + res := result.new("Instance does not have IAM Authentication enabled", instance.iamauthenabled) +} +``` + +The rule should return a result, which can be created using `result.new`. This function does not need to be imported, it is defined internally and provided at runtime. The first argument is the message to display and the second argument is the resource that the issue was detected on. + +It is possible to pass any rego variable that references a field of the input document. + +## Generate docs + +Finally, you'll want to generate documentation for your newly added rule. Please run `make docs` in the [trivy-checks](https://github.com/aquasecurity/trivy-checks) directory to generate the documentation for your new policy and submit a PR for us to take a look at. + +## Adding Tests + +All Rego checks need to have tests. There are many examples of these in the `checks` directory for each check ([Link](https://github.com/aquasecurity/trivy-checks/tree/main/checks)). More information on how to write tests for Rego checks is provided in the [custom misconfiguration](../../../docs/scanner/misconfiguration/custom/testing.md) section of the docs. + +## Example PR + +You can see a full example PR for a new rule being added here: [https://github.com/aquasecurity/defsec/pull/1000](https://github.com/aquasecurity/defsec/pull/1000). diff --git a/docs/community/contribute/checks/service-support.md b/docs/community/contribute/checks/service-support.md new file mode 100644 index 000000000000..91bcefe14da9 --- /dev/null +++ b/docs/community/contribute/checks/service-support.md @@ -0,0 +1,69 @@ +# Add Service Support + +A service refers to a service by a cloud provider. This section details how to add a new service to an existing provider. All contributions need to be made to the [trivy repository](https://github.com/aquasecurity/trivy/). + +## Prerequisites + +Before you begin, verify that the [provider](https://github.com/aquasecurity/trivy/tree/main/pkg/iac/providers) does not already have the service that you plan to add. + +## Adding a new service to an existing provider + +Adding a new service involves two steps. The service will need a data structure to store information about the required resources that will be scanned. Additionally, the service will require one or more adapters to convert the scan targetes as input(s) into the aforementioned data structure. + +### Create a new file in the provider directory + +In this example, we are adding the CodeBuild service to the AWS provider. + +First, create a new directory and file for your new service under the provider directory: e.g. [aws/codebuild/codebuild.go](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/providers/aws/codebuild/codebuild.go) + +The CodeBuild service will require a structure `struct` to hold the information on the input that is scanned. The input is the CodeBuild resource that a user configured and wants to scan for misconfiguration. + +``` +type CodeBuild struct { + Projects []Project +} +``` + +The CodeBuild service manages `Project` resources. The `Project` struct has been added to hold information about each Project resources; `Project` Resources in turn manage `ArtifactSettings`: + +``` +type Project struct { + Metadata iacTypes.Metadata + ArtifactSettings ArtifactSettings + SecondaryArtifactSettings []ArtifactSettings +} + +type ArtifactSettings struct { + Metadata iacTypes.Metadata + EncryptionEnabled iacTypes.BoolValue +} +``` + +The `iacTypes.Metadata` struct is embedded in all of the Trivy types and provides a common set of metadata for all resources. This includes the file and line number where the resource was defined and the name of the resource. + +A resource in this example `Project` can have a name and can optionally be encrypted. Instead of using raw string and bool types respectively, we use the trivy types `iacTypes.Metadata` and `iacTypes.BoolValue`. These types wrap the raw values and provide additional metadata about the value. For instance, whether it was set by the user and the file and line number where the resource was defined. + +Have a look at the other providers and services in the [`iac/providers`](https://github.com/aquasecurity/trivy/tree/main/pkg/iac/providers) directory in Trivy. + +Next you'll need to add a reference to your new service struct in the [provider struct](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/providers/aws/aws.go) at `pkg/iac/providers/aws/aws.go`: + +``` +type AWS struct { + ... + CodeBuild codebuild.CodeBuild + ... +} +``` + +### Update Adapters + +Now you'll need to update all of the [adapters](https://github.com/aquasecurity/trivy/tree/main/pkg/iac/adapters) which populate the struct of the provider that you have been using. Following the example above, if you want to add support for CodeBuild in Terraform, you'll need to update the Terraform AWS adatper as shown here: [`trivy/pkg/iac/adapters/terraform/aws/codebuild/adapt.go`](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/adapters/terraform/aws/codebuild/adapt.go). + +Another example for updating the adapters is provided in the [following PR.](https://github.com/aquasecurity/defsec/pull/1000/files) Additionally, please refer to the respective Terraform documentation on the provider to which you are adding the service. For instance, the Terraform documentation for AWS CodeBuild is provided [here.](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codebuild_project) + + +## Create a new Schema for your provider + +Once the new service has been added to the provider, you need to create the schema for the service as part of the provider schema. + +This process has been automated with mage commands. In the Trivy root directory run `mage schema:generate` to generate the schema for your new service and `mage schema:verify`. diff --git a/docs/docs/scanner/misconfiguration/custom/contribute-checks.md b/docs/docs/scanner/misconfiguration/custom/contribute-checks.md new file mode 100644 index 000000000000..8288d3b2189d --- /dev/null +++ b/docs/docs/scanner/misconfiguration/custom/contribute-checks.md @@ -0,0 +1,5 @@ +## Contribute Rego Checks + +The contributing section provides detailed information on how to contribute custom checks to the [trivy-checks repository](../../../../community/contribute/checks/overview.md/) + +This way, they become accessible as default [checks.](https://github.com/aquasecurity/trivy-checks) \ No newline at end of file diff --git a/docs/docs/scanner/misconfiguration/custom/index.md b/docs/docs/scanner/misconfiguration/custom/index.md index 9ce6250552bf..925b72cedf09 100644 --- a/docs/docs/scanner/misconfiguration/custom/index.md +++ b/docs/docs/scanner/misconfiguration/custom/index.md @@ -61,9 +61,9 @@ If you add a new custom policy, it must be defined under a new package like `use ### Policy structure -`# METADATA` (optional) +`# METADATA` (optional unless the check will be contributed into Trivy) : - SHOULD be defined for clarity since these values will be displayed in the scan results - - `custom.input` SHOULD be set to indicate the input type the policy should be applied to. See [list of available types](https://github.com/aquasecurity/defsec/blob/418759b4dc97af25f30f32e0bd365be7984003a1/pkg/types/sources.go) + - `custom.input` SHOULD be set to indicate the input type the policy should be applied to. See [list of available types][source-types] `package` (required) : - MUST follow the Rego's [specification][package] @@ -80,7 +80,6 @@ If you add a new custom policy, it must be defined under a new package like `use - A `string` denoting the detected issue - Although `object` with `msg` field is accepted, other fields are dropped and `string` is recommended if `result.new()` is not utilised. - e.g. `{"msg": "deny message", "details": "something"}` - ### Package A package name must be unique per policy. @@ -91,7 +90,7 @@ A package name must be unique per policy. ``` By default, only `builtin.*` packages will be evaluated. -If you define custom packages, you have to specify the package prefix via `--namespaces` option. +If you define custom packages, you have to specify the package prefix via `--namespaces` option. By default, Trivy only runs in its own namespace, unless specified by the user. Note that the custom namespace does not have to be `user` as in this example. It could be anything user-defined. ``` bash trivy conf --policy /path/to/custom_policies --namespaces user /path/to/config_dir @@ -119,8 +118,7 @@ Trivy supports extra fields in the `custom` section as described below. # - type: kubernetes ``` -All fields are optional. The `schemas` field should be used to enable policy validation using a built-in schema. The -schema that will be used is based on the input document type. It is recommended to use this to ensure your checks are +If you are creating checks for your Trivy misconfiguration scans, some fields are optional as referenced in the table below. The `schemas` field should be used to enable policy validation using a built-in schema. It is recommended to use this to ensure your checks are correct and do not reference incorrect properties/values. | Field name | Allowed values | Default value | In table | In JSON | @@ -135,6 +133,29 @@ correct and do not reference incorrect properties/values. | custom.input.selector.type | Any item(s) in [this list][source-types] | | :material-close: | :material-check: | | url | Any characters | | :material-close: | :material-check: | +#### custom.avd_id and custom.id + +The AVD_ID can be used to link the check to the Aqua Vulnerability Database (AVD) entry. For example, the `avd_id` `AVD-AWS-0176` is the ID of the check in the [AWS Vulnerability Database](https://avd.aquasec.com/). If you are [contributing your check to trivy-policies](../../../../community/contribute/checks/overview.md), you need to generate an ID using `make id` in the [trivy-checks](https://github.com/aquasecurity/trivy-checks) repository. The output of the command will provide you the next free IDs for the different providers in Trivy. + +The ID is based on the AVD_ID. For instance if the `avd_id` is `AVD-AWS-0176`, the ID is `ID0176`. + +#### custom.provider + +The `provider` field references the [provider](https://github.com/aquasecurity/trivy/tree/main/pkg/iac/providers) available in Trivy. This should be the same as the provider name in the `pkg/iac/providers` directory, e.g. `aws`. + +#### custom.service + +Services are defined within a provider. For instance, RDS is a service and AWS is a provider. This should be the same as the service name in one of the provider directories. ([Link](https://github.com/aquasecurity/trivy/tree/main/pkg/iac/providers)), e.g. `aws/rds`. + +#### custom.input + +The `input` tells Trivy what inputs this check should be applied to. Cloud provider checks should always use the `selector` input, and should always use the `type` selector with `cloud`. Check targeting Kubernetes yaml can use `kubenetes`, RBAC can use `rbac`, and so on. + +#### Subtypes in the custom data + +Subtypes currently only need to be defined for cloud providers [as detailed in the documentation.](./selectors.md/#enabling-selectors-and-subtypes) + +#### Scan Result Some fields are displayed in scan results. @@ -182,7 +203,7 @@ You can specify input format via the `custom.input` annotation. - `dockerfile` (Dockerfile) - `kubernetes` (Kubernetes YAML/JSON) - `rbac` (Kubernetes RBAC YAML/JSON) - - `cloud` (Cloud format, as defined by defsec - this is used for Terraform, CloudFormation, and Cloud/AWS scanning) + - `cloud` (Cloud format, as defined by Trivy - this is used for Terraform, CloudFormation, and Cloud/AWS scanning) - `yaml` (Generic YAML) - `json` (Generic JSON) - `toml` (Generic TOML) @@ -201,4 +222,4 @@ See [here](schema.md) for the detail. [rego]: https://www.openpolicyagent.org/docs/latest/policy-language/ [package]: https://www.openpolicyagent.org/docs/latest/policy-language/#packages -[source-types]: https://github.com/aquasecurity/defsec/blob/418759b4dc97af25f30f32e0bd365be7984003a1/pkg/types/sources.go +[source-types]: https://github.com/aquasecurity/trivy/blob/9361cdb7e28fd304d6fd2a1091feac64a6786672/pkg/iac/types/sources.go#L4 diff --git a/docs/docs/scanner/misconfiguration/custom/schema.md b/docs/docs/scanner/misconfiguration/custom/schema.md index 34872997238d..612025d38866 100644 --- a/docs/docs/scanner/misconfiguration/custom/schema.md +++ b/docs/docs/scanner/misconfiguration/custom/schema.md @@ -4,7 +4,8 @@ Policies can be defined with custom schemas that allow inputs to be verified against them. Adding a policy schema enables Trivy to show more detailed error messages when an invalid input is encountered. -In Trivy we have been able to define a schema for a [Dockerfile](https://github.com/aquasecurity/trivy/blob/main/pkg/iac/rego/schemas/dockerfile.json). Without input schemas, a policy would be as follows: +In Trivy we have been able to define a schema for a [Dockerfile](https://github.com/aquasecurity/trivy/tree/main/pkg/iac/rego/schemas) +Without input schemas, a policy would be as follows: !!! example ``` diff --git a/mkdocs.yml b/mkdocs.yml index 4f8a42c9cec3..0e92cb2d12cd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -65,6 +65,7 @@ nav: - Schemas: docs/scanner/misconfiguration/custom/schema.md - Testing: docs/scanner/misconfiguration/custom/testing.md - Debugging Policies: docs/scanner/misconfiguration/custom/debug.md + - Contribute Checks: docs/scanner/misconfiguration/custom/contribute-checks.md - Secret: docs/scanner/secret.md - License: docs/scanner/license.md - Coverage: @@ -193,6 +194,9 @@ nav: - Issues: community/contribute/issue.md - Discussions: community/contribute/discussion.md - Pull Requests: community/contribute/pr.md + - Contribute Rego Checks: + - Overview: community/contribute/checks/overview.md + - Add Service Support: community/contribute/checks/service-support.md - Maintainer: - Help Wanted: community/maintainer/help-wanted.md - Triage: community/maintainer/triage.md From 28194e581506b2e74ecc934780a51714b62f7332 Mon Sep 17 00:00:00 2001 From: Anais Urlichs <33576047+AnaisUrlichs@users.noreply.github.com> Date: Wed, 22 May 2024 12:51:28 +0100 Subject: [PATCH 090/352] docs: add info on adding compliance checks (#6275) Signed-off-by: AnaisUrlichs Co-authored-by: chenk --- docs/docs/compliance/compliance.md | 2 +- docs/docs/compliance/contrib-compliance.md | 101 +++++++++++++++++++++ docs/imgs/eks-benchmarks.png | Bin 0 -> 85807 bytes mkdocs.yml | 7 +- 4 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 docs/docs/compliance/contrib-compliance.md create mode 100644 docs/imgs/eks-benchmarks.png diff --git a/docs/docs/compliance/compliance.md b/docs/docs/compliance/compliance.md index 1d54af24a69a..2301fc3fb279 100644 --- a/docs/docs/compliance/compliance.md +++ b/docs/docs/compliance/compliance.md @@ -1,4 +1,4 @@ -# Compliance Reports +# Built-in Compliance Reports !!! warning "EXPERIMENTAL" This feature might change without preserving backwards compatibility. diff --git a/docs/docs/compliance/contrib-compliance.md b/docs/docs/compliance/contrib-compliance.md new file mode 100644 index 000000000000..a848b1bf4367 --- /dev/null +++ b/docs/docs/compliance/contrib-compliance.md @@ -0,0 +1,101 @@ +# Custom Compliance Spec + +Trivy supports several different compliance specs. The details on compliance scanning with Trivy are provided in the [compliance documentation](../../docs/compliance/compliance.md). +All of the Compliance Specs currently available in Trivy can be found in the `trivy-checks/specs/compliance/` directory ([Link](https://github.com/aquasecurity/trivy-checks/tree/main/specs/compliance)). + +New checks are based on the custom compliance report detailed in the [main documentation.](../../docs/compliance/compliance/#custom-compliance) +If you would like to create your custom compliance report, please reference the information in the main documentation. This section details how community members can contribute new Compliance Specs to Trivy. + +All compliance specs in Trivy are based on formal compliance reports such as CIS Benchmarks. + +## Contributing new Compliance Specs + +Compliance specs can be based on new compliance reports becoming available e.g. a new CIS Benchmark version, or identifying missing compliance specs that Trivy users would like to access. + +### Create a new Compliance Spec + +The existing compliance specs in Trivy are located under the `trivy-checks/specs/compliance/` directory ([Link](https://github.com/aquasecurity/trivy-checks/tree/main/specs/compliance)). + +Create a new file under `trivy-checks/specs/compliance/` and name the file in the format of "provider-resource-spectype-version.yaml". For example, the file name for AWS CIS Benchmarks for EKS version 1.4 is: `aws-eks-cis-1.4.yaml`. Note that if the compliance spec is not specific to a provider, the `provider` field can be ignored. + +### Minimum spec structure + +The structure of the compliance spec is detailed in the [main documentation](./compliance/#custom-compliance). + +The first section in the spec is focused on the metadata of the spec. Replace all the fields of the metadata with the information relevant to the compliance spec that will be added. This information can be taken from the official report e.g. the CIS Benchmark report. + +### Populating the `control` section + +Compliance specs detail a set of checks that should pass so that the resource is compliant with the official benchmark specifications. There are two ways in which Trivy compliance checks can enforce the compliance specification: + +1. The check is available in Trivy, as part of the `trivy-checks` and can be referenced in the Compliance Spec +2. The check is not available in Trivy and a manual check has to be added to the Compliance Spec + +Additional information is provided below. + +#### 1. Referencing a check that is already part of Trivy + +Trivy has a comprehensive list of checks as part of its misconfiguration scanning. These can be found in the `trivy-policies/checks` directory ([Link](https://github.com/aquasecurity/trivy-checks/tree/main/checks)). If the check is present, the `AVD_ID` and other information from the check has to be used. + +Note: Take a look at the more generic compliance specs that are already available in Trivy. If you are adding new compliance spec to Kubernetes e.g. AWS EKS CIS Benchmarks, chances are high that the check you would like to add to the new spec has already been defined in the general `k8s-ci-v.000.yaml` compliance spec. The same applies for creating specific Cloud Provider Compliance Specs and the [generic compliance specs](https://github.com/aquasecurity/trivy-checks/tree/main/specs/compliance) available. + +For example, the following check is detailed in the AWS EKS CIS v1.4 Benchmark: +`3.1.2 Ensure that the kubelet kubeconfig file ownership is set to root:root (Manual)` + +This check can be found in the general K8s CIS Compliance Benchmark: `k8s-cis-1.23.yaml` ([Link](https://github.com/aquasecurity/trivy-checks/blob/31e779916f3863dd74a28cee869ea24fdc4ca8c2/specs/compliance/k8s-cis-1.23.yaml#L480)) + +Thus, we can use the information already present: + +``` + - id: 3.1.2 + name: Ensure that the kubelet service file ownership is set to root:root (Manual) + description: Ensure that the kubelet service file ownership is set to root:root + checks: + - id: AVD-KCV-0070 + severity: HIGH +``` + +- The `ID`, `name`, and `description` is taken directly from the AWS EKS CIS Benchmarks +- The `check` and `severity` are taken from the existing complaince check in the `k8s-cis-1.23.yaml` + + +#### 2. Referencing a check manually that is not part of the Trivy default checks + +If the check does not already exist in the [Aqua Vulnerability Database](https://avd.aquasec.com/) (AVD) and is not part of the trivy-checks, the fields in the compliance spec for the check have to be populated manually. This is done by referencing the information in the official compliance specification. + +Below is the beginning of the information of the EKS CIS Benchmarks v1.4.0: + +![EKS Benchmarks 2.1.1](../../imgs/eks-benchmarks.png) + +The corresponding check in the `control` section will look like this: + +``` + - id: 2.1.1 + name: Enable audit Logs (Manual) + description: | + Control plane logs provide visibility into operation of the EKS Control plane components systems. + The API server audit logs record all accepted and rejected requests in the cluster. + When enabled via EKS configuration the control plane logs for a cluster are exported to a CloudWatch + Log Group for persistence. + checks: null + severity: MEDIUM +``` + +- Again, the `id`, `name` and `description` are taken directly from the EKS CIS Benchmarks v1.4.0 +- The `checks` is in this case `null` as the check is not currently present in the AVD and does not have a check in the [trivy policies](https://github.com/aquasecurity/trivy-checks/tree/main/checks) repository +- Since the check does not exist in Trivy, the `severity` will be `MEDIUM`. However, in some cases, the compliance report e.g. the CIS Benchmark report will specify the severity + +#### Contributing new checks to trivy-checks + +All of the checks in trivy-policies can be referenced in the compliance specs. +To write new Rego checks for Trivy, please take a look at the contributing documentation for checks. + +### Test the Compliance Spec + +To test the compliance check, pass the new path into the Trivy scan through the `--compliance` flag. For instance, to pass the check to the Trivy Kubernetes scan use the following command structure: + +``` +trivy k8s cluster --compliance @ --report summary +``` + +Note: The `@` is required before the filepath. \ No newline at end of file diff --git a/docs/imgs/eks-benchmarks.png b/docs/imgs/eks-benchmarks.png new file mode 100644 index 0000000000000000000000000000000000000000..68d931cffe3706374d265e8cfc8a5768e474051f GIT binary patch literal 85807 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 z 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 literal 0 HcmV?d00001 diff --git a/mkdocs.yml b/mkdocs.yml index 0e92cb2d12cd..42233f0f1395 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -129,11 +129,8 @@ nav: - SBOM Attestation in Rekor: docs/supply-chain/attestation/rekor.md - VEX: docs/supply-chain/vex.md - Compliance: - - Reports: docs/compliance/compliance.md - - Plugin: - - Overview: docs/plugin/index.md - - User Guide: docs/plugin/user-guide.md - - Developer Guide: docs/plugin/developer-guide.md + - Built-in Compliance: docs/compliance/compliance.md + - Custom Compliance: docs/compliance/contrib-compliance.md - Advanced: - Modules: docs/advanced/modules.md - Air-Gapped Environment: docs/advanced/air-gap.md From 9297885c75fe502107572e3f0b6771b60e8a699b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 May 2024 16:12:24 +0400 Subject: [PATCH 091/352] chore(deps): bump the testcontainers group with 2 updates (#6740) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 27 ++++++++++++++------------- go.sum | 51 +++++++++++++++++++++++++++++---------------------- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index 5d28534b19d0..908ccbdd8e93 100644 --- a/go.mod +++ b/go.mod @@ -109,8 +109,8 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.30.0 - github.com/testcontainers/testcontainers-go/modules/localstack v0.28.0 + github.com/testcontainers/testcontainers-go v0.31.0 + github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0 github.com/tetratelabs/wazero v1.7.0 github.com/twitchtv/twirp v8.1.2+incompatible github.com/xeipuuv/gojsonschema v1.2.0 @@ -145,7 +145,7 @@ require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect @@ -160,7 +160,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/hcsshim v0.12.0 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/ProtonMail/go-crypto v1.1.0-alpha.0 // indirect github.com/VividCortex/ewma v1.2.0 // indirect @@ -224,8 +224,9 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.7 // indirect - github.com/containerd/cgroups v1.1.0 // indirect + github.com/containerd/cgroups/v3 v3.0.2 // indirect github.com/containerd/continuity v0.4.2 // indirect + github.com/containerd/errdefs v0.1.0 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect @@ -259,7 +260,7 @@ require ( github.com/go-ini/ini v1.67.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -304,12 +305,12 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.17.7 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -346,7 +347,7 @@ require ( github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.19.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.48.0 // indirect @@ -361,7 +362,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sergi/go-diff v1.3.1 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect - github.com/shirou/gopsutil/v3 v3.23.12 // indirect + github.com/shirou/gopsutil/v3 v3.24.2 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/skeema/knownhosts v1.2.1 // indirect @@ -370,8 +371,8 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tklauser/go-sysconf v0.3.13 // indirect + github.com/tklauser/numcpus v0.7.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/vbatts/tar-split v0.11.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect @@ -379,7 +380,7 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect github.com/yuin/gopher-lua v1.1.0 // indirect - github.com/yusufpapurcu/wmi v1.2.3 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect diff --git a/go.sum b/go.sum index 55e43b57a536..041a667cf850 100644 --- a/go.sum +++ b/go.sum @@ -624,8 +624,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMiv github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= @@ -697,8 +697,8 @@ github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg3 github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/hcsshim v0.12.0 h1:rbICA+XZFwrBef2Odk++0LjFvClNCJGRK+fsrP254Ts= +github.com/Microsoft/hcsshim v0.12.0/go.mod h1:RZV12pcHCXQ42XnlQ3pz6FZfmrC1C+R4gaOHhRNML1g= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -999,8 +999,8 @@ github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4S github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= -github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= +github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= +github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= @@ -1030,6 +1030,8 @@ github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EX github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= +github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= @@ -1273,8 +1275,9 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= @@ -1645,8 +1648,8 @@ github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.13.0/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= @@ -1693,8 +1696,9 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNeKIkUct/gl9eod1TcXuj8stxvi/GoI= +github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= @@ -1933,8 +1937,9 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= @@ -2019,8 +2024,8 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= -github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= -github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= +github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -2120,16 +2125,18 @@ github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BG github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= -github.com/testcontainers/testcontainers-go v0.30.0 h1:jmn/XS22q4YRrcMwWg0pAwlClzs/abopbsBzrepyc4E= -github.com/testcontainers/testcontainers-go v0.30.0/go.mod h1:K+kHNGiM5zjklKjgTtcrEetF3uhWbMUyqAQoyoh8Pf0= -github.com/testcontainers/testcontainers-go/modules/localstack v0.28.0 h1:NOtK4tz2J1KbdAV6Lk9AQPUXB6Op8jGzKNfwVCThRxU= -github.com/testcontainers/testcontainers-go/modules/localstack v0.28.0/go.mod h1:nLimAfgHTQfaDZ2cO8/B4Z1qr8e020sM3ybpSsOVAUY= +github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U= +github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI= +github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0 h1:pPz0J5Gbu7eAirpWP7QDT/v3s0zpNb/sNA8Ww/rjkoQ= +github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0/go.mod h1:vqOXktUtHpTte9ilzE5enoUO8wt4FYDpZ3ARIAp28PM= github.com/tetratelabs/wazero v1.7.0 h1:jg5qPydno59wqjpGrHph81lbtHzTrWzwwtD4cD88+hQ= github.com/tetratelabs/wazero v1.7.0/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= +github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= +github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/twitchtv/twirp v8.1.2+incompatible h1:0O6TfzZW09ZP5r+ORA90XQEE3PTgA6C7MBbl2KxvVgE= @@ -2181,8 +2188,8 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= -github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= -github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= @@ -2605,8 +2612,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From bac46896975d8a8ca950046d0e8357b7456af543 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 May 2024 16:12:42 +0400 Subject: [PATCH 092/352] chore(deps): bump the github-actions group with 4 updates (#6737) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-update-labels.yaml | 4 ++-- .github/workflows/mkdocs-dev.yaml | 2 +- .github/workflows/mkdocs-latest.yaml | 2 +- .github/workflows/publish-chart.yaml | 6 +++--- .github/workflows/release.yaml | 4 ++-- .github/workflows/reusable-release.yaml | 2 +- .github/workflows/roadmap.yaml | 8 ++++---- .github/workflows/scan.yaml | 2 +- .github/workflows/test-docs.yaml | 2 +- .github/workflows/test.yaml | 22 +++++++++++----------- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/auto-update-labels.yaml b/.github/workflows/auto-update-labels.yaml index 5080e5d0a28c..941e7b5db9df 100644 --- a/.github/workflows/auto-update-labels.yaml +++ b/.github/workflows/auto-update-labels.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout main - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 - name: Set up Go uses: actions/setup-go@v5 @@ -22,7 +22,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: Install aqua tools - uses: aquaproj/aqua-installer@v3.0.0 + uses: aquaproj/aqua-installer@v3.0.1 with: aqua_version: v1.25.0 diff --git a/.github/workflows/mkdocs-dev.yaml b/.github/workflows/mkdocs-dev.yaml index c4bdf02a7583..f89deb9f5151 100644 --- a/.github/workflows/mkdocs-dev.yaml +++ b/.github/workflows/mkdocs-dev.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout main - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 with: fetch-depth: 0 persist-credentials: true diff --git a/.github/workflows/mkdocs-latest.yaml b/.github/workflows/mkdocs-latest.yaml index 0dd993b90513..0f07db482a05 100644 --- a/.github/workflows/mkdocs-latest.yaml +++ b/.github/workflows/mkdocs-latest.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout main - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 with: fetch-depth: 0 persist-credentials: true diff --git a/.github/workflows/publish-chart.yaml b/.github/workflows/publish-chart.yaml index a37dd8a24b8b..3a7db4970065 100644 --- a/.github/workflows/publish-chart.yaml +++ b/.github/workflows/publish-chart.yaml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 with: fetch-depth: 0 - name: Install Helm @@ -37,7 +37,7 @@ jobs: id: lint uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 - name: Setup Kubernetes cluster (KIND) - uses: helm/kind-action@99576bfa6ddf9a8e612d83b513da5a75875caced + uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde with: version: ${{ env.KIND_VERSION }} image: ${{ env.KIND_IMAGE }} @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 with: fetch-depth: 0 - name: Install chart-releaser diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b830d044768e..92dcaf759a39 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout code - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 with: fetch-depth: 0 @@ -35,7 +35,7 @@ jobs: sudo apt-get -y install rpm reprepro createrepo-c distro-info - name: Checkout trivy-repo - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 with: repository: ${{ github.repository_owner }}/trivy-repo path: trivy-repo diff --git a/.github/workflows/reusable-release.yaml b/.github/workflows/reusable-release.yaml index 39ca65809f06..2305a83b2159 100644 --- a/.github/workflows/reusable-release.yaml +++ b/.github/workflows/reusable-release.yaml @@ -70,7 +70,7 @@ jobs: password: ${{ secrets.ECR_SECRET_ACCESS_KEY }} - name: Checkout code - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 with: fetch-depth: 0 diff --git a/.github/workflows/roadmap.yaml b/.github/workflows/roadmap.yaml index 001d7a34a6f6..39c80f367281 100644 --- a/.github/workflows/roadmap.yaml +++ b/.github/workflows/roadmap.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: # 'kind/feature' AND 'priority/backlog' labels -> 'Backlog' column - - uses: actions/add-to-project@v1.0.0 # add new issue to project + - uses: actions/add-to-project@v1.0.1 # add new issue to project with: project-url: https://github.com/orgs/aquasecurity/projects/25 github-token: ${{ secrets.ORG_PROJECT_TOKEN }} @@ -28,7 +28,7 @@ jobs: field-values: Backlog # 'kind/feature' AND 'priority/important-longterm' labels -> 'Important (long-term)' column - - uses: actions/add-to-project@v1.0.0 # add new issue to project + - uses: actions/add-to-project@v1.0.1 # add new issue to project with: project-url: https://github.com/orgs/aquasecurity/projects/25 github-token: ${{ secrets.ORG_PROJECT_TOKEN }} @@ -45,7 +45,7 @@ jobs: field-values: Important (long-term) # 'kind/feature' AND 'priority/important-soon' labels -> 'Important (soon)' column - - uses: actions/add-to-project@v1.0.0 # add new issue to project + - uses: actions/add-to-project@v1.0.1 # add new issue to project with: project-url: https://github.com/orgs/aquasecurity/projects/25 github-token: ${{ secrets.ORG_PROJECT_TOKEN }} @@ -62,7 +62,7 @@ jobs: field-values: Important (soon) # 'kind/feature' AND 'priority/critical-urgent' labels -> 'Urgent' column - - uses: actions/add-to-project@v1.0.0 # add new issue to project + - uses: actions/add-to-project@v1.0.1 # add new issue to project with: project-url: https://github.com/orgs/aquasecurity/projects/25 github-token: ${{ secrets.ORG_PROJECT_TOKEN }} diff --git a/.github/workflows/scan.yaml b/.github/workflows/scan.yaml index 475397dc6e35..f2ce9385fe28 100644 --- a/.github/workflows/scan.yaml +++ b/.github/workflows/scan.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 - name: Run Trivy vulnerability scanner and create GitHub issues uses: knqyf263/trivy-issue-action@v0.0.6 diff --git a/.github/workflows/test-docs.yaml b/.github/workflows/test-docs.yaml index 550f56c57eed..59c989d99bad 100644 --- a/.github/workflows/test-docs.yaml +++ b/.github/workflows/test-docs.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 with: fetch-depth: 0 persist-credentials: true diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 59f27efc1bd5..5688c7b3f031 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -27,7 +27,7 @@ jobs: remove-haskell: "true" if: matrix.operating-system == 'ubuntu-latest' - - uses: actions/checkout@v4.1.4 + - uses: actions/checkout@v4.1.6 - name: Set up Go uses: actions/setup-go@v5 @@ -57,7 +57,7 @@ jobs: if: ${{ failure() && steps.lint.conclusion == 'failure' }} - name: Install tools - uses: aquaproj/aqua-installer@v3.0.0 + uses: aquaproj/aqua-installer@v3.0.1 with: aqua_version: v1.25.0 aqua_opts: "" @@ -79,7 +79,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 - name: Set up Go uses: actions/setup-go@v5 @@ -87,7 +87,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: Install tools - uses: aquaproj/aqua-installer@v3.0.0 + uses: aquaproj/aqua-installer@v3.0.1 with: aqua_version: v1.25.0 @@ -108,7 +108,7 @@ jobs: remove-haskell: "true" - name: Check out code into the Go module directory - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 - name: Set up Go uses: actions/setup-go@v5 @@ -116,7 +116,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: Install tools - uses: aquaproj/aqua-installer@v3.0.0 + uses: aquaproj/aqua-installer@v3.0.1 with: aqua_version: v1.25.0 @@ -128,7 +128,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 - name: Set up Go uses: actions/setup-go@v5 @@ -136,7 +136,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: Install tools - uses: aquaproj/aqua-installer@v3.0.0 + uses: aquaproj/aqua-installer@v3.0.1 with: aqua_version: v1.25.0 @@ -159,14 +159,14 @@ jobs: remove-haskell: 'true' - name: Checkout - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 - name: Set up Go uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - name: Install tools - uses: aquaproj/aqua-installer@v3.0.0 + uses: aquaproj/aqua-installer@v3.0.1 with: aqua_version: v1.25.0 - name: Run vm integration tests @@ -193,7 +193,7 @@ jobs: if: matrix.operating-system == 'ubuntu-latest' - name: Checkout - uses: actions/checkout@v4.1.4 + uses: actions/checkout@v4.1.6 - name: Set up Go uses: actions/setup-go@v5 From df422c8bf584a55bfb7de92628dfbe8c01b8b1bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 08:21:56 +0400 Subject: [PATCH 093/352] chore(deps): bump the docker group with 2 updates (#6739) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 16 ++++++++-------- go.sum | 37 ++++++++++++++++--------------------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index 908ccbdd8e93..e09ef8e80fc3 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/cheggaaa/pb/v3 v3.1.4 github.com/containerd/containerd v1.7.16 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 - github.com/docker/docker v26.0.2+incompatible + github.com/docker/docker v26.1.3+incompatible github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.16.0 github.com/go-git/go-git/v5 v5.11.0 @@ -89,7 +89,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 - github.com/moby/buildkit v0.12.5 + github.com/moby/buildkit v0.13.2 github.com/open-policy-agent/opa v0.64.1 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 @@ -225,11 +225,11 @@ require ( github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/cgroups/v3 v3.0.2 // indirect - github.com/containerd/continuity v0.4.2 // indirect + github.com/containerd/continuity v0.4.3 // indirect github.com/containerd/errdefs v0.1.0 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect github.com/containerd/ttrpc v1.2.3 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect @@ -239,9 +239,9 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect - github.com/docker/cli v25.0.1+incompatible // indirect + github.com/docker/cli v25.0.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker-credential-helpers v0.7.0 // indirect + github.com/docker/docker-credential-helpers v0.8.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -325,7 +325,7 @@ require ( github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/sys/mountinfo v0.6.2 // indirect + github.com/moby/sys/mountinfo v0.7.1 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/signal v0.7.0 // indirect github.com/moby/sys/user v0.1.0 // indirect @@ -374,7 +374,7 @@ require ( github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect - github.com/vbatts/tar-split v0.11.3 // indirect + github.com/vbatts/tar-split v0.11.5 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect diff --git a/go.sum b/go.sum index 041a667cf850..00509ff4406b 100644 --- a/go.sum +++ b/go.sum @@ -651,7 +651,6 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -1028,8 +1027,8 @@ github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cE github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= -github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= -github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= +github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= @@ -1057,8 +1056,8 @@ github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFY github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/stargz-snapshotter/estargz v0.7.0/go.mod h1:83VWDqHnurTKliEB0YvWMiCfLDwv4Cjj1X9Vk98GJZw= -github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= -github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= +github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= +github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= @@ -1104,7 +1103,6 @@ github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoY github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -1146,8 +1144,8 @@ github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyG github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU= -github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284= +github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= @@ -1155,11 +1153,11 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v26.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v26.0.2+incompatible h1:yGVmKUFGgcxA6PXWAokO0sQL22BrQ67cgVjko8tGdXE= -github.com/docker/docker v26.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo= +github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= -github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= +github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= @@ -1796,8 +1794,8 @@ github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQ 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/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.12.5 h1:RNHH1l3HDhYyZafr5EgstEu8aGNCwyfvMtrQDtjH9T0= -github.com/moby/buildkit v0.12.5/go.mod h1:YGwjA2loqyiYfZeEo8FtI7z4x5XponAaIWsWcSjWwso= +github.com/moby/buildkit v0.13.2 h1:nXNszM4qD9E7QtG7bFWPnDI1teUQFQglBzon/IU3SzI= +github.com/moby/buildkit v0.13.2/go.mod h1:2cyVOv9NoHM7arphK9ZfHIWKn9YVZRFd1wXB8kKmEzY= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= @@ -1808,8 +1806,8 @@ github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8 github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= +github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= @@ -2044,7 +2042,6 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= @@ -2149,10 +2146,9 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= -github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= +github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= +github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= @@ -2601,7 +2597,6 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From db5c523144121e8e49e419403bf8a96d6caab25b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 08:47:26 +0400 Subject: [PATCH 094/352] chore(deps): bump the aws group with 8 updates (#6738) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 34 ++++++++++++++--------------- go.sum | 68 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index e09ef8e80fc3..db432ba848d1 100644 --- a/go.mod +++ b/go.mod @@ -31,14 +31,14 @@ require ( github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7 - github.com/aws/aws-sdk-go-v2 v1.26.1 - github.com/aws/aws-sdk-go-v2/config v1.27.11 - github.com/aws/aws-sdk-go-v2/credentials v1.17.11 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.155.1 - github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4 - github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 + github.com/aws/aws-sdk-go-v2 v1.27.0 + github.com/aws/aws-sdk-go-v2/config v1.27.15 + github.com/aws/aws-sdk-go-v2/credentials v1.17.15 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.20 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.161.3 + github.com/aws/aws-sdk-go-v2/service/ecr v1.28.2 + github.com/aws/aws-sdk-go-v2/service/s3 v1.54.2 + github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 github.com/aws/smithy-go v1.20.2 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 @@ -173,11 +173,11 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.53.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 // indirect github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.26.7 // indirect github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6 // indirect github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6 // indirect @@ -199,10 +199,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/emr v1.36.0 // indirect github.com/aws/aws-sdk-go-v2/service/iam v1.28.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 // indirect github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5 // indirect github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 // indirect @@ -214,8 +214,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/service/sns v1.26.6 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 // indirect github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect diff --git a/go.sum b/go.sum index 00509ff4406b..c25280fbdb42 100644 --- a/go.sum +++ b/go.sum @@ -797,26 +797,26 @@ github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.53.0 h1:MMo1x1ggPPxDfHMXJnQudTbGXYlD4UigUAud1DJxPVo= github.com/aws/aws-sdk-go v1.53.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= -github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2 v1.27.0 h1:7bZWKoXhzI+mMR/HjdMx8ZCC5+6fY0lS5tr0bbgiLlo= +github.com/aws/aws-sdk-go-v2 v1.27.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= -github.com/aws/aws-sdk-go-v2/config v1.27.11 h1:f47rANd2LQEYHda2ddSCKYId18/8BhSRM4BULGmfgNA= -github.com/aws/aws-sdk-go-v2/config v1.27.11/go.mod h1:SMsV78RIOYdve1vf36z8LmnszlRWkwMQtomCAI0/mIE= -github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs= -github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15 h1:7Zwtt/lP3KNRkeZre7soMELMGNoBrutx8nobg1jKWmo= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15/go.mod h1:436h2adoHb57yd+8W+gYPrrA9U/R/SuAuOO42Ushzhw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/config v1.27.15 h1:uNnGLZ+DutuNEkuPh6fwqK7LpEiPmzb7MIMA1mNWEUc= +github.com/aws/aws-sdk-go-v2/config v1.27.15/go.mod h1:7j7Kxx9/7kTmL7z4LlhwQe63MYEE5vkVV6nWg4ZAI8M= +github.com/aws/aws-sdk-go-v2/credentials v1.17.15 h1:YDexlvDRCA8ems2T5IP1xkMtOZ1uLJOCJdTr0igs5zo= +github.com/aws/aws-sdk-go-v2/credentials v1.17.15/go.mod h1:vxHggqW6hFNaeNC0WyXS3VdyjcV0a4KMUY4dKJ96buU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 h1:dQLK4TjtnlRGb0czOht2CevZ5l6RSyRWAnKeGd7VAFE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3/go.mod h1:TL79f2P6+8Q7dTsILpiVST+AL9lkF6PPGI167Ny0Cjw= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.20 h1:NCM9wYaJCmlIWZSO/JwUEveKf0NCvsSgo9V9BwOAolo= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.20/go.mod h1:dmxIx3qriuepxqZgFeFMitFuftWPB94+MZv/6Btpth4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 h1:lf/8VTF2cM+N4SLzaYJERKEWAXq8MOMpZfU6wEPWsPk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7/go.mod h1:4SjkU7QiqK2M9oozyMzfZ/23LmUY+h3oFqhdeP5OMiI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 h1:4OYVp0705xu8yjdyoWix0r9wPIRXnIzzOoUpQVHIJ/g= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7/go.mod h1:vd7ESTEvI76T2Na050gODNmNU7+OyKrIKroYTu4ABiI= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 h1:81KE7vaZzrl7yHBYHVEzYB8sypz11NMOZ40YlWvPxsU= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5/go.mod h1:LIt2rg7Mcgn09Ygbdh/RdIm0rQ+3BNkbP1gyVMFtRK0= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 h1:/FUtT3xsoHO3cfh+I/kCbcMCN98QZRsiFet/V8QkWSs= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7/go.mod h1:MaCAgWpGooQoCWZnMur97rGn5dp350w2+CeiV5406wE= github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.26.7 h1:rLdKcienXrk+JFX1+DZg160ebG8lIF2nFvnEZL7dnII= github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.26.7/go.mod h1:cwqaWBOZXu8pqEE1ZC4Sw2ycZLjwKrRP5tOAJFgCbYc= github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6 h1:ePPaOVn92r5n8Neecdpy93hDmR0PBH6H6b7VQCE5vKE= @@ -841,10 +841,10 @@ github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 h1:XKO0BswTDeZMLDBd/b5pCEZ github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8/go.mod h1:N5tqZcYMM0N1PN7UQYJNWuGyO886OfnMhf/3MAbqMcI= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.155.1 h1:JBwnHlQvL39eeT03+vmBZuziutTKljmOKboKxQuIBck= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.155.1/go.mod h1:xejKuuRDjz6z5OqyeLsz01MlOqqW7CqpAB4PabNvpu8= -github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4 h1:Qr9W21mzWT3RhfYn9iAux7CeRIdbnTAqmiOlASqQgZI= -github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4/go.mod h1:if7ybzzjOmDB8pat9FE35AHTY6ZxlYSy3YviSmFZv8c= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.161.3 h1:l0mvKOGm25yo/Fy+Y/08Cm4aTA4XmnIuq4ppy+shfMI= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.161.3/go.mod h1:iJ2sQeUTkjNp3nL7kE/Bav0xXYhtiRCRP5ZXk4jFhCQ= +github.com/aws/aws-sdk-go-v2/service/ecr v1.28.2 h1:xUpMnRZonKfrHaNLC77IMpWZSUMRRXIi6IU5EhAPsrM= +github.com/aws/aws-sdk-go-v2/service/ecr v1.28.2/go.mod h1:X52zjAVRaXklEU1TE/wO8kyyJSr9cJx9ZsqliWbyRys= github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6 h1:Sc2mLjyA1R8z2l705AN7Wr7QOlnUxVnGPJeDIVyUSrs= github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6/go.mod h1:LzHcyOEvaLjbc5e+fP/KmPWBr+h/Ef+EHvnf1Pzo368= github.com/aws/aws-sdk-go-v2/service/efs v1.28.1 h1:dKtJBzCIew4/VDsYgrx6v140cIpQVoe93kCNniYATtE= @@ -863,14 +863,14 @@ github.com/aws/aws-sdk-go-v2/service/iam v1.28.7 h1:FKPRDYZOO0Eur19vWUL1B40Op0j8 github.com/aws/aws-sdk-go-v2/service/iam v1.28.7/go.mod h1:YzMYyQ7S4twfYzLjwP24G1RAxypozVZeNaG1r2jxRms= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 h1:ZMeFZ5yk+Ek+jNr1+uwCd2tG89t6oTS5yVWpa6yy2es= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7/go.mod h1:mxV05U+4JiHqIpGqqYXOHLPKUC6bDXC44bsUhNjOEwY= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 h1:UXqEWQI0n+q0QixzU0yUUQBZXRd5037qdInTIHFTl98= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9/go.mod h1:xP6Gq6fzGZT8w/ZN+XvGMZ2RU1LeEs7b2yUP5DN8NY4= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11 h1:e9AVb17H4x5FTE5KWIP5M1Du+9M86pS+Hw0lBUdN8EY= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11/go.mod h1:B90ZQJa36xo0ph9HsoteI1+r8owgQH/U1QNfqZQkj1Q= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 h1:f9RyWNtS8oH7cZlbn+/JNPpjUk5+5fLd5lM9M0i49Ys= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5/go.mod h1:h5CoMZV2VF297/VLhRhO1WF+XYWOzXo+4HsObA4HjBQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 h1:Wx0rlZoEJR7JwlSZcHnEa7CNjrSIyVxMFWGAaXy4fJY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9/go.mod h1:aVMHdE0aHO3v+f/iw01fmXV/5DbfQ3Bi9nN7nd9bE9Y= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 h1:uO5XR6QGBcmPyo2gxofYJLFkcVQ4izOoGDNenlZhTEk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7/go.mod h1:feeeAYfAcwTReM6vbwjEyDmiGho+YgBhaFULuXDW8kc= github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5 h1:yCkyZDGahaCaAkdpVx8Te05t6eW2FarBLunVC8S23nU= github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5/go.mod h1:/KmX+vXMPJGAB56reo95tnsXa6QPNx6qli4L1AmYb7E= github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6 h1:FO/aIHk86VePDUh/3Q/A5pnvu45miO1GZB8rIq2BUlA= @@ -887,20 +887,20 @@ github.com/aws/aws-sdk-go-v2/service/rds v1.66.1 h1:TafjIpDW/+l7s+f3EIONaFsNvNfw github.com/aws/aws-sdk-go-v2/service/rds v1.66.1/go.mod h1:MYzRMSdY70kcS8AFg0aHmk/xj6VAe0UfaCCoLrBWPow= github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7 h1:k4WaqQ7LHSGrSftCRXTRLv7WaozXu+fZ1jdisQSR2eU= github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7/go.mod h1:8hU0Ax6q6QA+jrMcWTE0A4YH594MQoWP3EzGO3GH5Dw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 h1:6cnno47Me9bRykw9AEv9zkXE+5or7jz8TsskTTccbgc= -github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1/go.mod h1:qmdkIIAC+GCLASF7R2whgNrJADz0QZPX+Seiw/i4S3o= +github.com/aws/aws-sdk-go-v2/service/s3 v1.54.2 h1:gYSJhNiOF6J9xaYxu2NFNstoiNELwt0T9w29FxSfN+Y= +github.com/aws/aws-sdk-go-v2/service/s3 v1.54.2/go.mod h1:739CllldowZiPPsDFcJHNF4FXrVxaSGVnZ9Ez9Iz9hc= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 h1:dPCRgAL4WD9tSMaDglRNGOiAtSTjkwNiUW5GDpWFfHA= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0/go.mod h1:4Ae1NCLK6ghmjzd45Tc33GgCKhUWD2ORAlULtMO1Cbs= github.com/aws/aws-sdk-go-v2/service/sns v1.26.6 h1:w2YwF8889ardGU3Y0qZbJ4Zzh+Q/QqKZ4kwkK7JFvnI= github.com/aws/aws-sdk-go-v2/service/sns v1.26.6/go.mod h1:IrcbquqMupzndZ20BXxDxjM7XenTRhbwBOetk4+Z5oc= github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6 h1:UdbDTllc7cmusTTMy1dcTrYKRl4utDEsmKh9ZjvhJCc= github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6/go.mod h1:mCUv04gd/7g+/HNzDB4X6dzJuygji0ckvB3Lg/TdG5Y= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 h1:vN8hEbpRnL7+Hopy9dzmRle1xmDc7o8tmY0klsr175w= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.5/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 h1:Jux+gDDyi1Lruk+KHF91tK2KCuY61kzoCpvtvJJBtOE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n2HZPkcKgPAi1phU= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 h1:Kv1hwNG6jHC/sxMTe5saMjH6t6ZLkgfvVxyEjfWL1ks= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.8/go.mod h1:c1qtZUWtygI6ZdvKppzCSXsDOq5I4luJPZ0Ud3juFCA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 h1:nWBZ1xHCF+A7vv9sDzJOq4NWIdzFYm0kH7Pr4OjHYsQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2/go.mod h1:9lmoVDVLz/yUZwLaQ676TK02fhCu4+PgRSmMaKR1ozk= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 h1:Qp6Boy0cGDloOE3zI6XhNLNZgjNS8YmiFQFHe71SaW0= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.9/go.mod h1:0Aqn1MnEuitqfsCNyKsdKLhDUOr4txD/g19EfiUqgws= github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1 h1:pqxn3fcZDgWmo8GMUjlxVBdakcGo0AeUb7mjX33pJIQ= github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1/go.mod h1:kP5rUlnqfno/obflnKX4KMBWkoVHLDI8oCka9U0opRo= github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= From c3e734f6594bebe77c5ad402e00da7178d7cac49 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 23 May 2024 06:50:01 +0200 Subject: [PATCH 095/352] ci(deps): fix tenv in ".*_test.go$" (#6748) Signed-off-by: Matthieu MOREL --- .golangci.yaml | 1 - pkg/dependency/parser/java/pom/artifact_test.go | 4 +--- pkg/fanal/image/daemon/podman_test.go | 2 +- pkg/iac/rego/scanner_test.go | 2 +- pkg/report/template_test.go | 3 +-- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 3df1d3809f53..09e72505b0c5 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -112,7 +112,6 @@ issues: - govet - ineffassign - misspell - - tenv - unused - linters: - gosec diff --git a/pkg/dependency/parser/java/pom/artifact_test.go b/pkg/dependency/parser/java/pom/artifact_test.go index 79715dbcca7c..cc8f7ad337de 100644 --- a/pkg/dependency/parser/java/pom/artifact_test.go +++ b/pkg/dependency/parser/java/pom/artifact_test.go @@ -1,7 +1,6 @@ package pom import ( - "os" "testing" "github.com/stretchr/testify/assert" @@ -87,8 +86,7 @@ func Test_evaluateVariable(t *testing.T) { } envName := "TEST_GO_DEP_PARSER" - os.Setenv(envName, "1.2.3") - defer os.Unsetenv(envName) + t.Setenv(envName, "1.2.3") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/fanal/image/daemon/podman_test.go b/pkg/fanal/image/daemon/podman_test.go index d1c0e20807b6..697895fa95e8 100644 --- a/pkg/fanal/image/daemon/podman_test.go +++ b/pkg/fanal/image/daemon/podman_test.go @@ -22,7 +22,7 @@ func setupPodmanSock(t *testing.T) *httptest.Server { runtimeDir, err := os.MkdirTemp("", "daemon") require.NoError(t, err) - os.Setenv("XDG_RUNTIME_DIR", runtimeDir) + t.Setenv("XDG_RUNTIME_DIR", runtimeDir) dir := filepath.Join(runtimeDir, "podman") err = os.MkdirAll(dir, os.ModePerm) diff --git a/pkg/iac/rego/scanner_test.go b/pkg/iac/rego/scanner_test.go index cc4bb785433a..d820fe805969 100644 --- a/pkg/iac/rego/scanner_test.go +++ b/pkg/iac/rego/scanner_test.go @@ -330,7 +330,7 @@ exception[rules] { func Test_RegoScanning_WithRuntimeValues(t *testing.T) { - _ = os.Setenv("DEFSEC_RUNTIME_VAL", "AOK") + t.Setenv("DEFSEC_RUNTIME_VAL", "AOK") srcFS := CreateFS(t, map[string]string{ "policies/test.rego": ` diff --git a/pkg/report/template_test.go b/pkg/report/template_test.go index ae1b139c8767..56a2d6df7610 100644 --- a/pkg/report/template_test.go +++ b/pkg/report/template_test.go @@ -3,7 +3,6 @@ package report_test import ( "bytes" "context" - "os" "testing" "time" @@ -168,7 +167,7 @@ func TestReportWriter_Template(t *testing.T) { t.Run(tc.name, func(t *testing.T) { ctx := clock.With(context.Background(), time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC)) - os.Setenv("AWS_ACCOUNT_ID", "123456789012") + t.Setenv("AWS_ACCOUNT_ID", "123456789012") got := bytes.Buffer{} inputReport := types.Report{ Results: types.Results{ From 49678aed2a9757d6475141028fe2e42cd15f4802 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 May 2024 11:30:25 +0400 Subject: [PATCH 096/352] chore(deps): bump the common group across 1 directory with 29 updates (#6756) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 84 +++++++++++++------------- go.sum | 183 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 134 insertions(+), 133 deletions(-) diff --git a/go.mod b/go.mod index db432ba848d1..1ee3895f492b 100644 --- a/go.mod +++ b/go.mod @@ -6,16 +6,16 @@ toolchain go1.22.2 require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 github.com/BurntSushi/toml v1.3.2 github.com/CycloneDX/cyclonedx-go v0.8.0 github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible github.com/Masterminds/sprig/v3 v3.2.3 github.com/NYTimes/gziphandler v1.1.1 github.com/alecthomas/chroma v0.10.0 - github.com/alicebob/miniredis/v2 v2.31.1 - github.com/antchfx/htmlquery v1.3.0 + github.com/alicebob/miniredis/v2 v2.32.1 + github.com/antchfx/htmlquery v1.3.1 github.com/apparentlymart/go-cidr v1.1.0 github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce @@ -43,13 +43,13 @@ require ( github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cenkalti/backoff v2.2.1+incompatible - github.com/cheggaaa/pb/v3 v3.1.4 - github.com/containerd/containerd v1.7.16 + github.com/cheggaaa/pb/v3 v3.1.5 + github.com/containerd/containerd v1.7.17 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 github.com/docker/docker v26.1.3+incompatible github.com/docker/go-connections v0.5.0 - github.com/fatih/color v1.16.0 - github.com/go-git/go-git/v5 v5.11.0 + github.com/fatih/color v1.17.0 + github.com/go-git/go-git/v5 v5.12.0 github.com/go-openapi/runtime v0.28.0 github.com/go-openapi/strfmt v0.23.0 github.com/go-redis/redis/v8 v8.11.5 @@ -60,18 +60,18 @@ require ( github.com/google/wire v0.6.0 github.com/hashicorp/go-getter v1.7.4 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-retryablehttp v0.7.5 + github.com/hashicorp/go-retryablehttp v0.7.6 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/golang-lru/v2 v2.0.7 - github.com/hashicorp/hc-install v0.6.3 - github.com/hashicorp/hcl/v2 v2.19.1 - github.com/hashicorp/terraform-exec v0.20.0 + github.com/hashicorp/hc-install v0.7.0 + github.com/hashicorp/hcl/v2 v2.20.1 + github.com/hashicorp/terraform-exec v0.21.0 github.com/in-toto/in-toto-golang v0.9.0 github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075 - github.com/knqyf263/go-rpmdb v0.0.0-20231008124120-ac49267ab4e1 + github.com/knqyf263/go-rpmdb v0.1.1 github.com/knqyf263/nested v0.0.1 github.com/kylelemons/godebug v1.1.0 github.com/liamg/iamgo v0.0.9 @@ -94,16 +94,16 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/openvex/go-vex v0.2.5 - github.com/owenrumney/go-sarif/v2 v2.3.0 + github.com/owenrumney/go-sarif/v2 v2.3.1 github.com/owenrumney/squealer v1.2.2 - github.com/package-url/packageurl-go v0.1.2 + github.com/package-url/packageurl-go v0.1.3 github.com/quasilyte/go-ruleguard/dsl v0.3.22 github.com/samber/lo v1.39.0 github.com/secure-systems-lab/go-securesystemslib v0.8.0 github.com/sigstore/rekor v1.3.6 github.com/sirupsen/logrus v1.9.3 github.com/sosedoff/gitkit v0.4.0 - github.com/spdx/tools-golang v0.5.4-0.20231108154018-0c0f394b5e1a // v0.5.3 with necessary changes. Can be upgraded to version 0.5.4 after release. + github.com/spdx/tools-golang v0.5.4 // v0.5.3 with necessary changes. Can be upgraded to version 0.5.4 after release. github.com/spf13/cast v1.6.0 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 @@ -111,27 +111,27 @@ require ( github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.31.0 github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0 - github.com/tetratelabs/wazero v1.7.0 - github.com/twitchtv/twirp v8.1.2+incompatible + github.com/tetratelabs/wazero v1.7.2 + github.com/twitchtv/twirp v8.1.3+incompatible github.com/xeipuuv/gojsonschema v1.2.0 github.com/xlab/treeprint v1.2.0 github.com/zclconf/go-cty v1.14.4 github.com/zclconf/go-cty-yaml v1.0.3 - go.etcd.io/bbolt v1.3.9 - golang.org/x/crypto v0.22.0 + go.etcd.io/bbolt v1.3.10 + golang.org/x/crypto v0.23.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa - golang.org/x/mod v0.16.0 - golang.org/x/net v0.24.0 - golang.org/x/sync v0.6.0 - golang.org/x/term v0.19.0 - golang.org/x/text v0.14.0 + golang.org/x/mod v0.17.0 + golang.org/x/net v0.25.0 + golang.org/x/sync v0.7.0 + golang.org/x/term v0.20.0 + golang.org/x/text v0.15.0 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 - google.golang.org/protobuf v1.34.0 + google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.14.2 - k8s.io/api v0.30.0 + helm.sh/helm/v3 v3.15.0 + k8s.io/api v0.30.1 k8s.io/utils v0.0.0-20231127182322-b307cd553661 - modernc.org/sqlite v1.29.7 + modernc.org/sqlite v1.29.10 sigs.k8s.io/yaml v1.4.0 ) @@ -159,16 +159,16 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.12.0 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect - github.com/ProtonMail/go-crypto v1.1.0-alpha.0 // indirect + github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/agnivade/levenshtein v1.1.1 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect - github.com/antchfx/xpath v1.2.3 // indirect + github.com/antchfx/xpath v1.3.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.53.0 // indirect @@ -230,7 +230,7 @@ require ( github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect - github.com/containerd/ttrpc v1.2.3 // indirect + github.com/containerd/ttrpc v1.2.4 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect @@ -295,7 +295,7 @@ require ( github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/terraform-json v0.19.0 // indirect + github.com/hashicorp/terraform-json v0.22.1 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -316,7 +316,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect @@ -360,12 +360,12 @@ require ( github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect - github.com/sergi/go-diff v1.3.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shirou/gopsutil/v3 v3.24.2 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.3.1 // indirect - github.com/skeema/knownhosts v1.2.1 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -379,7 +379,7 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect - github.com/yuin/gopher-lua v1.1.0 // indirect + github.com/yuin/gopher-lua v1.1.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect @@ -393,7 +393,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/sys v0.20.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.19.0 // indirect google.golang.org/api v0.172.0 // indirect @@ -407,9 +407,9 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiextensions-apiserver v0.29.0 // indirect - k8s.io/apimachinery v0.30.0 // indirect - k8s.io/apiserver v0.29.0 // indirect + k8s.io/apiextensions-apiserver v0.30.0 // indirect + k8s.io/apimachinery v0.30.1 // indirect + k8s.io/apiserver v0.30.0 // indirect k8s.io/cli-runtime v0.30.0 // indirect k8s.io/client-go v0.30.0 // indirect k8s.io/component-base v0.30.0 // indirect diff --git a/go.sum b/go.sum index c25280fbdb42..dfb5b4daca88 100644 --- a/go.sum +++ b/go.sum @@ -617,10 +617,10 @@ github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 h1:n1DH8TPV4qqPTje2RcUBYwtrTWlabVp4n46+74X2pn4= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0/go.mod h1:HDcZnuGbiyppErN6lB+idp4CKhjbc8gwjto6OPpyggM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 h1:FDif4R1+UUR+00q6wquyX90K7A8dN+R5E8GEadoP7sU= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJCAIKs92XiPyQfTaBWqvHujDwKb6CBU= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= @@ -658,7 +658,6 @@ github.com/CycloneDX/cyclonedx-go v0.8.0 h1:FyWVj6x6hoJrui5uRQdYZcSievw3Z32Z88uY github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= -github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0= github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible h1:juIaKLLVhqzP55d8x4cSVgwyQv76Z55/fRv/UBr2KkQ= github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs= github.com/Intevation/gval v1.3.0 h1:+Ze5sft5MmGbZrHj06NVUbcxCb67l9RaPTLMNr37mjw= @@ -687,8 +686,8 @@ github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JP github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= @@ -706,8 +705,8 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMo github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/ProtonMail/go-crypto v1.1.0-alpha.0 h1:nHGfwXmFvJrSR9xu8qL7BkO4DqTHXE9N5vPhgY2I+j0= -github.com/ProtonMail/go-crypto v1.1.0-alpha.0/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= @@ -731,17 +730,17 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= -github.com/alicebob/miniredis/v2 v2.31.1 h1:7XAt0uUg3DtwEKW5ZAGa+K7FZV2DdKQo5K/6TTnfX8Y= -github.com/alicebob/miniredis/v2 v2.31.1/go.mod h1:UB/T2Uztp7MlFSDakaX1sTXUv5CASoprx0wulRT6HBg= +github.com/alicebob/miniredis/v2 v2.32.1 h1:Bz7CciDnYSaa0mX5xODh6GUITRSx+cVhjNoOR4JssBo= +github.com/alicebob/miniredis/v2 v2.32.1/go.mod h1:AqkLNAfUm0K07J28hnAyyQKf/x0YkCY/g5DCtuL01Mw= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/antchfx/htmlquery v1.3.0 h1:5I5yNFOVI+egyia5F2s/5Do2nFWxJz41Tr3DyfKD25E= -github.com/antchfx/htmlquery v1.3.0/go.mod h1:zKPDVTMhfOmcwxheXUsx4rKJy8KEY/PU6eXr/2SebQ8= -github.com/antchfx/xpath v1.2.3 h1:CCZWOzv5bAqjVv0offZ2LVgVYFbeldKQVuLNbViZdes= -github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/antchfx/htmlquery v1.3.1 h1:wm0LxjLMsZhRHfQKKZscDf2COyH4vDYA3wyH+qZ+Ylc= +github.com/antchfx/htmlquery v1.3.1/go.mod h1:PTj+f1V2zksPlwNt7uVvZPsxpKNa7mlVliCRxLX6Nx8= +github.com/antchfx/xpath v1.3.0 h1:nTMlzGAK3IJ0bPpME2urTuFL76o4A96iYvoKFHRXJgc= +github.com/antchfx/xpath v1.3.0/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= @@ -956,8 +955,8 @@ github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNS github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= -github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo= -github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA= +github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk= +github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6jM60XI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -1018,8 +1017,8 @@ github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.7.16 h1:7Zsfe8Fkj4Wi2My6DXGQ87hiqIrmOXolm72ZEkFU5Mg= -github.com/containerd/containerd v1.7.16/go.mod h1:NL49g7A/Fui7ccmxV6zkBWwqMgmMxFWzujYCc+JLt7k= +github.com/containerd/containerd v1.7.17 h1:KjNnn0+tAVQHAoaWRjmdak9WlvnFR/8rU1CHHy8Rm2A= +github.com/containerd/containerd v1.7.17/go.mod h1:vK+hhT4TIv2uejlcDlbVIc8+h/BqtKLIyNrtCZol8lI= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -1063,8 +1062,8 @@ github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDG github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.2.3 h1:4jlhbXIGvijRtNC8F/5CpuJZ7yKOBFGFOOXg1bkISz0= -github.com/containerd/ttrpc v1.2.3/go.mod h1:ieWsXucbb8Mj9PH0rXCw1i8IunRbbAiDkpXkbfflWBM= +github.com/containerd/ttrpc v1.2.4 h1:eQCQK4h9dxDmpOb9QOOMh2NHTfzroH1IkmHiKZi05Oo= +github.com/containerd/ttrpc v1.2.4/go.mod h1:ojvb8SJBSch0XkqNO0L0YX/5NxR3UnVk2LzFKBK0upc= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= @@ -1211,8 +1210,8 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -1235,8 +1234,8 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4= github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= @@ -1250,8 +1249,8 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+ github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -1543,17 +1542,16 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-getter v1.7.4 h1:3yQjWuxICvSpYwqSayAdKRFcvBl1y/vogCxczWSmix0= github.com/hashicorp/go-getter v1.7.4/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= -github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.6 h1:TwRYfx2z2C4cLbXmT8I5PgP/xmuqASDyiVuGYfs9GZM= +github.com/hashicorp/go-retryablehttp v0.7.6/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= @@ -1572,20 +1570,20 @@ github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hc-install v0.6.3 h1:yE/r1yJvWbtrJ0STwScgEnCanb0U9v7zp0Gbkmcoxqs= -github.com/hashicorp/hc-install v0.6.3/go.mod h1:KamGdbodYzlufbWh4r9NRo8y6GLHWZP2GBtdnms1Ln0= +github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk= +github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= 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/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI= -github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= +github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= +github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8JyYF3vpnuEo= -github.com/hashicorp/terraform-exec v0.20.0/go.mod h1:ckKGkJWbsNqFKV1itgMnE0hY9IYf1HoiekpuN0eWoDw= -github.com/hashicorp/terraform-json v0.19.0 h1:e9DBKC5sxDfiJT7Zoi+yRIwqLVtFur/fwK/FuE6AWsA= -github.com/hashicorp/terraform-json v0.19.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk= +github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= +github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= +github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= +github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= @@ -1655,8 +1653,8 @@ github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 h1:PPPlUUq github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422/go.mod h1:ijAmSS4jErO6+KRzcK6ixsm3Vt96hMhJ+W+x+VmbrQA= github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075 h1:aC6MEAs3PE3lWD7lqrJfDxHd6hcced9R4JTZu85cJwU= github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0= -github.com/knqyf263/go-rpmdb v0.0.0-20231008124120-ac49267ab4e1 h1:lrciwn7tj0j7HS5DfpAFnFZEqxzPGIkVOVS89dLOkf0= -github.com/knqyf263/go-rpmdb v0.0.0-20231008124120-ac49267ab4e1/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww= +github.com/knqyf263/go-rpmdb v0.1.1 h1:oh68mTCvp1XzxdU7EfafcWzzfstUZAEa3MW0IJye584= +github.com/knqyf263/go-rpmdb v0.1.1/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+YF/0CVGDww= github.com/knqyf263/nested v0.0.1 h1:Sv26CegUMhjt19zqbBKntjwESdxe5hxVPSk0+AKjdUc= github.com/knqyf263/nested v0.0.1/go.mod h1:zwhsIhMkBg90DTOJQvxPkKIypEHPYkgWHs4gybdlUmk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -1749,15 +1747,15 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 h1:TLygBUBxikNJJfLwgm+Qwdgq1FtfV8Uh7bcxRyTzK8s= @@ -1899,12 +1897,12 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ= github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88IO/0Wo= github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= -github.com/owenrumney/go-sarif/v2 v2.3.0 h1:wP5yEpI53zr0v5cBmagXzLbHZp9Oylyo3AJDpfLBITs= -github.com/owenrumney/go-sarif/v2 v2.3.0/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= +github.com/owenrumney/go-sarif/v2 v2.3.1 h1:77opmuqxQZE1UF6TylFz5XllVEI72WijgwpwNw4JTmY= +github.com/owenrumney/go-sarif/v2 v2.3.1/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= github.com/owenrumney/squealer v1.2.2 h1:zsnZSwkWi8Y2lgwmg77b565vlHQovlvBrSBzmAs3oiE= github.com/owenrumney/squealer v1.2.2/go.mod h1:pDCW33bWJ2kDOuz7+2BSXDgY38qusVX0MtjPCSFtdSo= -github.com/package-url/packageurl-go v0.1.2 h1:0H2DQt6DHd/NeRlVwW4EZ4oEI6Bn40XlNPRqegcxuo4= -github.com/package-url/packageurl-go v0.1.2/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= +github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= +github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 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= @@ -2018,8 +2016,8 @@ github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvW github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= @@ -2044,8 +2042,8 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= -github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -2056,8 +2054,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= -github.com/spdx/tools-golang v0.5.4-0.20231108154018-0c0f394b5e1a h1:uuREJ3I15VLjYZuhxjTQnA2bTqzRQX1HKEphYBzqT9o= -github.com/spdx/tools-golang v0.5.4-0.20231108154018-0c0f394b5e1a/go.mod h1:BHs8QEhK6MbFGdyjxvuBtzJtCLrN5bwUBC9fzQlYBXs= +github.com/spdx/tools-golang v0.5.4 h1:fRW4iz16P1ZCUtWStFqS6YiMgnK7WgfTFU/lrsYlvqY= +github.com/spdx/tools-golang v0.5.4/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= 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.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= @@ -2126,8 +2124,8 @@ github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jX github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI= github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0 h1:pPz0J5Gbu7eAirpWP7QDT/v3s0zpNb/sNA8Ww/rjkoQ= github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0/go.mod h1:vqOXktUtHpTte9ilzE5enoUO8wt4FYDpZ3ARIAp28PM= -github.com/tetratelabs/wazero v1.7.0 h1:jg5qPydno59wqjpGrHph81lbtHzTrWzwwtD4cD88+hQ= -github.com/tetratelabs/wazero v1.7.0/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= +github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= +github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= @@ -2136,8 +2134,8 @@ github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/twitchtv/twirp v8.1.2+incompatible h1:0O6TfzZW09ZP5r+ORA90XQEE3PTgA6C7MBbl2KxvVgE= -github.com/twitchtv/twirp v8.1.2+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= +github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= +github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= @@ -2182,8 +2180,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= -github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= @@ -2195,6 +2193,8 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= github.com/zclconf/go-cty-yaml v1.0.3 h1:og/eOQ7lvA/WWhHGFETVWNduJM7Rjsv2RRpx1sdFMLc= github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= @@ -2202,8 +2202,8 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= -go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= +go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= @@ -2283,8 +2283,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2346,8 +2346,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2424,8 +2424,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2476,8 +2476,9 @@ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2609,8 +2610,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2624,8 +2625,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2643,8 +2644,9 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -3024,8 +3026,8 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= -google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -3078,8 +3080,8 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.14.2 h1:V71fv+NGZv0icBlr+in1MJXuUIHCiPG1hW9gEBISTIA= -helm.sh/helm/v3 v3.14.2/go.mod h1:2itvvDv2WSZXTllknfQo6j7u3VVgMAvm8POCDgYH424= +helm.sh/helm/v3 v3.15.0 h1:gcLxHeFp0Hfo7lYi6KIZ84ZyvlAnfFRSJ8lTL3zvG5U= +helm.sh/helm/v3 v3.15.0/go.mod h1:fvfoRcB8UKRUV5jrIfOTaN/pG1TPhuqSb56fjYdTKXg= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -3091,20 +3093,20 @@ honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= -k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= -k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= -k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= +k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= +k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= +k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= +k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= -k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= +k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/apiserver v0.29.0 h1:Y1xEMjJkP+BIi0GSEv1BBrf1jLU9UPfAnnGGbbDdp7o= -k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM= +k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M= +k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= k8s.io/cli-runtime v0.30.0 h1:0vn6/XhOvn1RJ2KJOC6IRR2CGqrpT6QQF4+8pYpWQ48= k8s.io/cli-runtime v0.30.0/go.mod h1:vATpDMATVTMA79sZ0YUCzlMelf6rUjoBzlp+RnoM+cg= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= @@ -3183,8 +3185,8 @@ modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/sqlite v1.29.7 h1:Puwf5TIYuOipbcRnpFnLlGlR03DKenw8ggf3ijnuNQ0= -modernc.org/sqlite v1.29.7/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk= +modernc.org/sqlite v1.29.10 h1:3u93dz83myFnMilBGCOLbr+HjklS6+5rJLx4q86RDAg= +modernc.org/sqlite v1.29.10/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= @@ -3216,6 +3218,5 @@ sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+s sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From ac7452009bf7ca0fa8ee1de8807c792eabad405a Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Fri, 24 May 2024 09:07:04 +0600 Subject: [PATCH 097/352] feat(misconf): resolve tf module from OpenTofu compatible registry (#6743) --- .../terraform/parser/module_retrieval.go | 14 ++++----- .../terraform/parser/resolvers/registry.go | 22 +++++++++++-- .../resolvers/registry_integration_test.go | 31 +++++++++++++++++++ 3 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 pkg/iac/scanners/terraform/parser/resolvers/registry_integration_test.go diff --git a/pkg/iac/scanners/terraform/parser/module_retrieval.go b/pkg/iac/scanners/terraform/parser/module_retrieval.go index cae84359498d..2ae6221afc73 100644 --- a/pkg/iac/scanners/terraform/parser/module_retrieval.go +++ b/pkg/iac/scanners/terraform/parser/module_retrieval.go @@ -5,21 +5,21 @@ import ( "fmt" "io/fs" - resolvers2 "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers" + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers" ) type ModuleResolver interface { - Resolve(context.Context, fs.FS, resolvers2.Options) (filesystem fs.FS, prefix string, downloadPath string, applies bool, err error) + Resolve(context.Context, fs.FS, resolvers.Options) (filesystem fs.FS, prefix string, downloadPath string, applies bool, err error) } var defaultResolvers = []ModuleResolver{ - resolvers2.Cache, - resolvers2.Local, - resolvers2.Remote, - resolvers2.Registry, + resolvers.Cache, + resolvers.Local, + resolvers.Remote, + resolvers.Registry, } -func resolveModule(ctx context.Context, current fs.FS, opt resolvers2.Options) (filesystem fs.FS, sourcePrefix, downloadPath string, err error) { +func resolveModule(ctx context.Context, current fs.FS, opt resolvers.Options) (filesystem fs.FS, sourcePrefix, downloadPath string, err error) { opt.Debug("Resolving module '%s' with source: '%s'...", opt.Name, opt.Source) for _, resolver := range defaultResolvers { if filesystem, prefix, path, applies, err := resolver.Resolve(ctx, current, opt); err != nil { diff --git a/pkg/iac/scanners/terraform/parser/resolvers/registry.go b/pkg/iac/scanners/terraform/parser/resolvers/registry.go index 22778cc10230..93d80fa0af86 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/registry.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/registry.go @@ -122,11 +122,29 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option return nil, "", "", true, err } defer func() { _ = resp.Body.Close() }() - if resp.StatusCode != http.StatusNoContent { + + // OpenTofu may return 200 with body + switch resp.StatusCode { + case http.StatusOK: + // https://opentofu.org/docs/internals/module-registry-protocol/#sample-response-1 + var downloadResponse struct { + Location string `json:"location"` + } + if err := json.NewDecoder(resp.Body).Decode(&downloadResponse); err != nil { + return nil, "", "", true, fmt.Errorf("failed to decode download response: %w", err) + } + + opt.Source = downloadResponse.Location + case http.StatusNoContent: + opt.Source = resp.Header.Get("X-Terraform-Get") + default: return nil, "", "", true, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } - opt.Source = resp.Header.Get("X-Terraform-Get") + if opt.Source == "" { + return nil, "", "", true, fmt.Errorf("no source was found for the registry at %s", hostname) + } + opt.Debug("Module '%s' resolved via registry to new source: '%s'", opt.Name, opt.Source) opt.RelativePath = relativePath filesystem, prefix, downloadPath, _, err = Remote.Resolve(ctx, target, opt) diff --git a/pkg/iac/scanners/terraform/parser/resolvers/registry_integration_test.go b/pkg/iac/scanners/terraform/parser/resolvers/registry_integration_test.go new file mode 100644 index 000000000000..e2d87104da2d --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/resolvers/registry_integration_test.go @@ -0,0 +1,31 @@ +package resolvers_test + +import ( + "context" + "io/fs" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers" +) + +func TestResolveModuleFromOpenTofuRegistry(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + + fsys, _, path, _, err := resolvers.Registry.Resolve(context.Background(), nil, resolvers.Options{ + Source: "registry.opentofu.org/terraform-aws-modules/s3-bucket/aws", + RelativePath: "test", + Name: "bucket", + Version: "4.1.2", + AllowDownloads: true, + SkipCache: true, + }) + require.NoError(t, err) + + _, err = fs.Stat(fsys, filepath.Join(path, "main.tf")) + require.NoError(t, err) +} From e3738333b45cd093e68a7451e2e69a942a53f104 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 24 May 2024 11:54:21 +0400 Subject: [PATCH 098/352] build: use main package instead of main.go (#6766) Signed-off-by: knqyf263 --- goreleaser.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/goreleaser.yml b/goreleaser.yml index 6e24883428dd..0a546162bed7 100644 --- a/goreleaser.yml +++ b/goreleaser.yml @@ -1,7 +1,7 @@ project_name: trivy builds: - id: build-linux - main: cmd/trivy/main.go + main: ./cmd/trivy/ binary: trivy ldflags: - -s -w @@ -21,7 +21,7 @@ builds: goarm: - 7 - id: build-bsd - main: cmd/trivy/main.go + main: ./cmd/trivy/ binary: trivy ldflags: - -s -w @@ -36,7 +36,7 @@ builds: - 386 - amd64 - id: build-macos - main: cmd/trivy/main.go + main: ./cmd/trivy/ binary: trivy ldflags: - -s -w @@ -52,7 +52,7 @@ builds: goarm: - 7 - id: build-windows - main: cmd/trivy/main.go + main: ./cmd/trivy/ binary: trivy ldflags: - -s -w From e86bacbd6d7bf3be985a85d6684824641bb78225 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 24 May 2024 11:03:26 +0200 Subject: [PATCH 099/352] ci(deps): fix gocritic in ".*_test.go$" (#6763) Signed-off-by: Matthieu MOREL --- .golangci.yaml | 13 ++++++++++++- .../parser/nodejs/packagejson/parse_test.go | 2 +- pkg/dependency/parser/nuget/lock/parse_test.go | 3 +-- .../parser/python/poetry/parse_test.go | 2 +- pkg/fanal/analyzer/imgconf/apk/apk_test.go | 4 ++-- pkg/fanal/handler/handler_test.go | 2 +- pkg/fanal/walker/walk_test.go | 16 ++++++++-------- pkg/iac/adapters/arm/compute/adapt_test.go | 2 +- pkg/iac/adapters/arm/storage/adapt_test.go | 2 +- .../terraform/google/gke/adapt_test.go | 2 +- pkg/iac/rego/convert/slice_test.go | 2 +- pkg/iac/rego/convert/struct_test.go | 2 +- .../arm/parser/armjson/parse_array_test.go | 4 ++-- .../azure/functions/create_object_test.go | 4 ++-- pkg/iac/scanners/azure/functions/empty_test.go | 2 +- .../cloudformation/parser/parser_test.go | 2 +- .../scanners/cloudformation/scanner_test.go | 2 +- pkg/iac/scanners/dockerfile/scanner_test.go | 4 ++-- pkg/iac/scanners/json/scanner_test.go | 2 +- pkg/iac/scanners/kubernetes/scanner_test.go | 4 ++-- .../scanners/terraform/parser/parser_test.go | 2 +- pkg/iac/scanners/terraform/setup_test.go | 4 ++-- pkg/iac/scanners/toml/scanner_test.go | 2 +- pkg/iac/scanners/yaml/scanner_test.go | 2 +- pkg/k8s/scanner/scanner_test.go | 10 +++++----- pkg/licensing/scanner_test.go | 2 +- pkg/log/handler_test.go | 2 +- pkg/misconf/scanner_test.go | 8 ++++---- pkg/module/module_test.go | 18 +++++++++--------- pkg/policy/policy_test.go | 6 +++--- pkg/purl/purl_test.go | 2 +- pkg/remote/remote_test.go | 4 ++-- pkg/rpc/server/listen_test.go | 2 +- pkg/rpc/server/server_test.go | 2 +- pkg/sbom/io/encode_test.go | 2 +- pkg/utils/fsutils/fs_test.go | 2 +- 36 files changed, 78 insertions(+), 68 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 09e72505b0c5..b3a9da0192e3 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -106,13 +106,24 @@ issues: - path: ".*_test.go$" linters: - bodyclose - - gocritic - goconst - gosec - govet - ineffassign - misspell - unused + - path: ".*_test.go$" + linters: + - gocritic + text: "commentFormatting:" + - path: ".*_test.go$" + linters: + - gocritic + text: "exitAfterDefer:" + - path: ".*_test.go$" + linters: + - gocritic + text: "importShadow:" - linters: - gosec text: "G304: Potential file inclusion" diff --git a/pkg/dependency/parser/nodejs/packagejson/parse_test.go b/pkg/dependency/parser/nodejs/packagejson/parse_test.go index 1896186129ae..56e0dd34c17b 100644 --- a/pkg/dependency/parser/nodejs/packagejson/parse_test.go +++ b/pkg/dependency/parser/nodejs/packagejson/parse_test.go @@ -59,7 +59,7 @@ func TestParse(t *testing.T) { Version: "4.1.2", Licenses: []string{"ISC"}, }, - Dependencies: map[string]string{}, + Dependencies: make(map[string]string), DevDependencies: map[string]string{ "@babel/cli": "^7.14.5", "@babel/core": "^7.14.6", diff --git a/pkg/dependency/parser/nuget/lock/parse_test.go b/pkg/dependency/parser/nuget/lock/parse_test.go index 561eed5dfc88..507cae4aab43 100644 --- a/pkg/dependency/parser/nuget/lock/parse_test.go +++ b/pkg/dependency/parser/nuget/lock/parse_test.go @@ -4,7 +4,6 @@ import ( "os" "path" "sort" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -65,7 +64,7 @@ func TestParse(t *testing.T) { func sortDeps(deps []ftypes.Dependency) { sort.Slice(deps, func(i, j int) bool { - return strings.Compare(deps[i].ID, deps[j].ID) < 0 + return deps[i].ID < deps[j].ID }) for i := range deps { diff --git a/pkg/dependency/parser/python/poetry/parse_test.go b/pkg/dependency/parser/python/poetry/parse_test.go index 7f42975713de..693bb5afcc79 100644 --- a/pkg/dependency/parser/python/poetry/parse_test.go +++ b/pkg/dependency/parser/python/poetry/parse_test.go @@ -109,7 +109,7 @@ func TestParseDependency(t *testing.T) { name: "pkgsVersions doesn't contain required version", packageName: "test", versionRange: ">=1.0.0", - pkgsVersions: map[string][]string{}, + pkgsVersions: make(map[string][]string), wantErr: "no version found", }, } diff --git a/pkg/fanal/analyzer/imgconf/apk/apk_test.go b/pkg/fanal/analyzer/imgconf/apk/apk_test.go index 8f3856e2fde4..9d6449c92808 100644 --- a/pkg/fanal/analyzer/imgconf/apk/apk_test.go +++ b/pkg/fanal/analyzer/imgconf/apk/apk_test.go @@ -1261,9 +1261,9 @@ func TestResolveDependency(t *testing.T) { if err = json.NewDecoder(f).Decode(&apkIndexArchive); err != nil { t.Fatalf("unexpected error: %s", err) } - circularDependencyCheck := map[string]struct{}{} + circularDependencyCheck := make(map[string]struct{}) pkgs := analyzer.resolveDependency(apkIndexArchive, v.pkgName, circularDependencyCheck) - actual := map[string]struct{}{} + actual := make(map[string]struct{}) for _, pkg := range pkgs { actual[pkg] = struct{}{} } diff --git a/pkg/fanal/handler/handler_test.go b/pkg/fanal/handler/handler_test.go index b53f885e24e0..a049fb9cb2bd 100644 --- a/pkg/fanal/handler/handler_test.go +++ b/pkg/fanal/handler/handler_test.go @@ -47,7 +47,7 @@ func TestManager_Versions(t *testing.T) { { name: "disable hooks", disable: []types.HandlerType{"fake"}, - want: map[string]int{}, + want: make(map[string]int), }, } diff --git a/pkg/fanal/walker/walk_test.go b/pkg/fanal/walker/walk_test.go index 82f71c875b81..0bac6009d04f 100644 --- a/pkg/fanal/walker/walk_test.go +++ b/pkg/fanal/walker/walk_test.go @@ -101,7 +101,7 @@ func TestSkipDir(t *testing.T) { }, { name: "two stars", - skipDirs: []string{filepath.Join("/etc/*/*")}, + skipDirs: []string{"/etc/*/*"}, wants: map[string]bool{ "/etc/foo": false, "/etc/foo/bar": true, @@ -109,11 +109,11 @@ func TestSkipDir(t *testing.T) { }, { name: "multiple dirs", - skipDirs: []string{filepath.Join("/etc/*/*"), filepath.Join("/var/log/*")}, + skipDirs: []string{"/etc/*/*", "/var/log/*"}, wants: map[string]bool{ - filepath.Join("/etc/foo"): false, - filepath.Join("/etc/foo/bar"): true, - filepath.Join("/var/log/bar"): true, + "/etc/foo": false, + "/etc/foo/bar": true, + "/var/log/bar": true, }, }, { @@ -126,10 +126,10 @@ func TestSkipDir(t *testing.T) { }, { name: "error bad pattern", - skipDirs: []string{filepath.Join(`[^etc`)}, // filepath.Match returns ErrBadPattern + skipDirs: []string{`[^etc`}, // filepath.Match returns ErrBadPattern wants: map[string]bool{ - filepath.Join("/etc/foo"): false, - filepath.Join("/etc/foo/bar"): false, + "/etc/foo": false, + "/etc/foo/bar": false, }, }, } diff --git a/pkg/iac/adapters/arm/compute/adapt_test.go b/pkg/iac/adapters/arm/compute/adapt_test.go index 819f79993e5e..983b4d489171 100644 --- a/pkg/iac/adapters/arm/compute/adapt_test.go +++ b/pkg/iac/adapters/arm/compute/adapt_test.go @@ -45,7 +45,7 @@ func Test_AdaptWindowsVM(t *testing.T) { Type: azure2.NewValue("Microsoft.Compute/virtualMachines", types.NewTestMetadata()), Properties: azure2.NewValue(map[string]azure2.Value{ "osProfile": azure2.NewValue(map[string]azure2.Value{ - "windowsConfiguration": azure2.NewValue(map[string]azure2.Value{}, types.NewTestMetadata()), + "windowsConfiguration": azure2.NewValue(make(map[string]azure2.Value), types.NewTestMetadata()), }, types.NewTestMetadata()), }, types.NewTestMetadata()), }, diff --git a/pkg/iac/adapters/arm/storage/adapt_test.go b/pkg/iac/adapters/arm/storage/adapt_test.go index ae2e497580bb..d1e124e2449e 100644 --- a/pkg/iac/adapters/arm/storage/adapt_test.go +++ b/pkg/iac/adapters/arm/storage/adapt_test.go @@ -16,7 +16,7 @@ func Test_AdaptStorageDefaults(t *testing.T) { Resources: []azure2.Resource{ { Type: azure2.NewValue("Microsoft.Storage/storageAccounts", types.NewTestMetadata()), - Properties: azure2.NewValue(map[string]azure2.Value{}, types.NewTestMetadata()), + Properties: azure2.NewValue(make(map[string]azure2.Value), types.NewTestMetadata()), }, }, } diff --git a/pkg/iac/adapters/terraform/google/gke/adapt_test.go b/pkg/iac/adapters/terraform/google/gke/adapt_test.go index 1309288dc42d..461a5169e7be 100644 --- a/pkg/iac/adapters/terraform/google/gke/adapt_test.go +++ b/pkg/iac/adapters/terraform/google/gke/adapt_test.go @@ -237,7 +237,7 @@ resource "google_container_cluster" "example" { }, EnableShieldedNodes: iacTypes.Bool(true, iacTypes.NewTestMetadata()), EnableLegacyABAC: iacTypes.Bool(false, iacTypes.NewTestMetadata()), - ResourceLabels: iacTypes.Map(map[string]string{}, iacTypes.NewTestMetadata()), + ResourceLabels: iacTypes.Map(make(map[string]string), iacTypes.NewTestMetadata()), RemoveDefaultNodePool: iacTypes.Bool(false, iacTypes.NewTestMetadata()), }, }, diff --git a/pkg/iac/rego/convert/slice_test.go b/pkg/iac/rego/convert/slice_test.go index ef0b6da6fa21..7d8125f96cd2 100644 --- a/pkg/iac/rego/convert/slice_test.go +++ b/pkg/iac/rego/convert/slice_test.go @@ -21,7 +21,7 @@ func Test_SliceConversion(t *testing.T) { } input[0].Z.A = 123 converted := SliceToRego(reflect.ValueOf(input)) - assert.Equal(t, []interface{}{map[string]interface{}{"z": map[string]interface{}{}}}, converted) + assert.Equal(t, []interface{}{map[string]interface{}{"z": make(map[string]interface{})}}, converted) } func Test_SliceTypesConversion(t *testing.T) { diff --git a/pkg/iac/rego/convert/struct_test.go b/pkg/iac/rego/convert/struct_test.go index ca72efabdedd..1d0774d48247 100644 --- a/pkg/iac/rego/convert/struct_test.go +++ b/pkg/iac/rego/convert/struct_test.go @@ -17,5 +17,5 @@ func Test_StructConversion(t *testing.T) { }{} input.Z.A = 123 converted := StructToRego(reflect.ValueOf(input)) - assert.Equal(t, map[string]interface{}{"z": map[string]interface{}{}}, converted) + assert.Equal(t, map[string]interface{}{"z": make(map[string]interface{})}, converted) } diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go index 6f25eb4d5aad..593d784bd084 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go @@ -11,7 +11,7 @@ import ( func Test_Array_Empty(t *testing.T) { example := []byte(`[]`) - target := []int{} + var target []int metadata := types.NewTestMetadata() require.NoError(t, Unmarshal(example, &target, &metadata)) assert.Empty(t, target) @@ -19,7 +19,7 @@ func Test_Array_Empty(t *testing.T) { func Test_Array_ToSlice(t *testing.T) { example := []byte(`[1, 2, 3]`) - target := []int{} + var target []int metadata := types.NewTestMetadata() require.NoError(t, Unmarshal(example, &target, &metadata)) assert.Len(t, target, 3) diff --git a/pkg/iac/scanners/azure/functions/create_object_test.go b/pkg/iac/scanners/azure/functions/create_object_test.go index f695e38410fe..1e72c4626a48 100644 --- a/pkg/iac/scanners/azure/functions/create_object_test.go +++ b/pkg/iac/scanners/azure/functions/create_object_test.go @@ -16,7 +16,7 @@ func Test_CreateObject(t *testing.T) { { name: "CreateObject with no args", args: []interface{}{}, - expected: map[string]interface{}{}, + expected: make(map[string]interface{}), }, { name: "CreateObject with one arg", @@ -36,7 +36,7 @@ func Test_CreateObject(t *testing.T) { { name: "CreateObject with odd number of args", args: []interface{}{"foo", "bar", "baz"}, - expected: map[string]interface{}{}, + expected: make(map[string]interface{}), }, { name: "CreateObject with odd number of args", diff --git a/pkg/iac/scanners/azure/functions/empty_test.go b/pkg/iac/scanners/azure/functions/empty_test.go index a21fb96cd8cd..9f1ead58e684 100644 --- a/pkg/iac/scanners/azure/functions/empty_test.go +++ b/pkg/iac/scanners/azure/functions/empty_test.go @@ -43,7 +43,7 @@ func Test_Empty(t *testing.T) { { name: "map is empty", args: []interface{}{ - map[string]interface{}{}, + make(map[string]interface{}), }, expected: true, }, diff --git a/pkg/iac/scanners/cloudformation/parser/parser_test.go b/pkg/iac/scanners/cloudformation/parser/parser_test.go index fc8a087bdfec..1c5688b7fa55 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser_test.go +++ b/pkg/iac/scanners/cloudformation/parser/parser_test.go @@ -13,7 +13,7 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" ) -func parseFile(t *testing.T, source string, name string) (FileContexts, error) { +func parseFile(t *testing.T, source, name string) (FileContexts, error) { tmp, err := os.MkdirTemp(os.TempDir(), "defsec") require.NoError(t, err) defer func() { _ = os.RemoveAll(tmp) }() diff --git a/pkg/iac/scanners/cloudformation/scanner_test.go b/pkg/iac/scanners/cloudformation/scanner_test.go index 0319a862ab74..baa8ed81ba59 100644 --- a/pkg/iac/scanners/cloudformation/scanner_test.go +++ b/pkg/iac/scanners/cloudformation/scanner_test.go @@ -82,7 +82,7 @@ deny[res] { Terraform: (*scan.TerraformCustomCheck)(nil), }, RegoPackage: "data.builtin.dockerfile.DS006", - Frameworks: map[framework.Framework][]string{}, + Frameworks: make(map[framework.Framework][]string), }, results.GetFailed()[0].Rule()) failure := results.GetFailed()[0] diff --git a/pkg/iac/scanners/dockerfile/scanner_test.go b/pkg/iac/scanners/dockerfile/scanner_test.go index 310f104affaa..72c48f6a9f5e 100644 --- a/pkg/iac/scanners/dockerfile/scanner_test.go +++ b/pkg/iac/scanners/dockerfile/scanner_test.go @@ -252,7 +252,7 @@ USER root CustomChecks: scan.CustomChecks{ Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.dockerfile.DS006", - Frameworks: map[framework.Framework][]string{}, + Frameworks: make(map[framework.Framework][]string), }, results.GetFailed()[0].Rule(), ) @@ -607,7 +607,7 @@ COPY --from=dep /binary /` CustomChecks: scan.CustomChecks{ Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.dockerfile.DS006", - Frameworks: map[framework.Framework][]string{}, + Frameworks: make(map[framework.Framework][]string), }, results.GetFailed()[0].Rule(), ) diff --git a/pkg/iac/scanners/json/scanner_test.go b/pkg/iac/scanners/json/scanner_test.go index bd24a09e055c..7e7f1c308186 100644 --- a/pkg/iac/scanners/json/scanner_test.go +++ b/pkg/iac/scanners/json/scanner_test.go @@ -73,6 +73,6 @@ deny[res] { Terraform: (*scan.TerraformCustomCheck)(nil), }, RegoPackage: "data.builtin.json.lol", - Frameworks: map[framework.Framework][]string{}, + Frameworks: make(map[framework.Framework][]string), }, results.GetFailed()[0].Rule()) } diff --git a/pkg/iac/scanners/kubernetes/scanner_test.go b/pkg/iac/scanners/kubernetes/scanner_test.go index 4745b442402d..a75173f67c0e 100644 --- a/pkg/iac/scanners/kubernetes/scanner_test.go +++ b/pkg/iac/scanners/kubernetes/scanner_test.go @@ -119,7 +119,7 @@ deny[res] { CloudFormation: &scan.EngineMetadata{}, CustomChecks: scan.CustomChecks{Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.kubernetes.KSV011", - Frameworks: map[framework.Framework][]string{}, + Frameworks: make(map[framework.Framework][]string), }, results.GetFailed()[0].Rule()) failure := results.GetFailed()[0] @@ -279,7 +279,7 @@ deny[res] { CloudFormation: &scan.EngineMetadata{}, CustomChecks: scan.CustomChecks{Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.kubernetes.KSV011", - Frameworks: map[framework.Framework][]string{}, + Frameworks: make(map[framework.Framework][]string), }, results.GetFailed()[0].Rule()) failure := results.GetFailed()[0] diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index b00b8c42ce56..3d25b5518b46 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -1510,7 +1510,7 @@ func parse(t *testing.T, files map[string]string) terraform.Modules { return modules } -func compareSets(a []int, b []int) bool { +func compareSets(a, b []int) bool { m := make(map[int]bool) for _, el := range a { m[el] = true diff --git a/pkg/iac/scanners/terraform/setup_test.go b/pkg/iac/scanners/terraform/setup_test.go index 06930e6ffda4..d7a8f0277a96 100644 --- a/pkg/iac/scanners/terraform/setup_test.go +++ b/pkg/iac/scanners/terraform/setup_test.go @@ -13,7 +13,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/terraform" ) -func createModulesFromSource(t *testing.T, source string, ext string) terraform.Modules { +func createModulesFromSource(t *testing.T, source, ext string) terraform.Modules { fs := testutil.CreateFS(t, map[string]string{ "source" + ext: source, }) @@ -29,7 +29,7 @@ func createModulesFromSource(t *testing.T, source string, ext string) terraform. return modules } -func scanHCLWithWorkspace(t *testing.T, source string, workspace string) scan.Results { +func scanHCLWithWorkspace(t *testing.T, source, workspace string) scan.Results { return scanHCL(t, source, ScannerWithWorkspaceName(workspace)) } diff --git a/pkg/iac/scanners/toml/scanner_test.go b/pkg/iac/scanners/toml/scanner_test.go index 15583428d1da..c2fdcda26faa 100644 --- a/pkg/iac/scanners/toml/scanner_test.go +++ b/pkg/iac/scanners/toml/scanner_test.go @@ -76,7 +76,7 @@ deny[res] { CustomChecks: scan.CustomChecks{ Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.toml.lol", - Frameworks: map[framework.Framework][]string{}, + Frameworks: make(map[framework.Framework][]string), }, results.GetFailed()[0].Rule(), ) diff --git a/pkg/iac/scanners/yaml/scanner_test.go b/pkg/iac/scanners/yaml/scanner_test.go index a9d124be6858..771bf45ef7d2 100644 --- a/pkg/iac/scanners/yaml/scanner_test.go +++ b/pkg/iac/scanners/yaml/scanner_test.go @@ -79,7 +79,7 @@ deny[res] { CustomChecks: scan.CustomChecks{ Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.yaml.lol", - Frameworks: map[framework.Framework][]string{}, + Frameworks: make(map[framework.Framework][]string), }, results.GetFailed()[0].Rule(), ) diff --git a/pkg/k8s/scanner/scanner_test.go b/pkg/k8s/scanner/scanner_test.go index 3364c4ce299c..b7eb8156a59c 100644 --- a/pkg/k8s/scanner/scanner_test.go +++ b/pkg/k8s/scanner/scanner_test.go @@ -507,18 +507,18 @@ func TestFindNodeName(t *testing.T) { Namespace: "kube-system", Kind: "Cluster", Name: "k8s.io/kubernetes", - RawResource: map[string]interface{}{}, + RawResource: make(map[string]interface{}), }, { Namespace: "kube-system", Kind: "ControlPlaneComponents", Name: "k8s.io/apiserver", - RawResource: map[string]interface{}{}, + RawResource: make(map[string]interface{}), }, { Kind: "NodeComponents", Name: "kind-control-plane", - RawResource: map[string]interface{}{}, + RawResource: make(map[string]interface{}), }, }, want: "kind-control-plane", @@ -530,13 +530,13 @@ func TestFindNodeName(t *testing.T) { Namespace: "kube-system", Kind: "Cluster", Name: "k8s.io/kubernetes", - RawResource: map[string]interface{}{}, + RawResource: make(map[string]interface{}), }, { Namespace: "kube-system", Kind: "ControlPlaneComponents", Name: "k8s.io/apiserver", - RawResource: map[string]interface{}{}, + RawResource: make(map[string]interface{}), }, }, want: "", diff --git a/pkg/licensing/scanner_test.go b/pkg/licensing/scanner_test.go index 11102e27a44a..fce93f3c5337 100644 --- a/pkg/licensing/scanner_test.go +++ b/pkg/licensing/scanner_test.go @@ -46,7 +46,7 @@ func TestScanner_Scan(t *testing.T) { }, { name: "unknown", - categories: map[types.LicenseCategory][]string{}, + categories: make(map[types.LicenseCategory][]string), licenseName: licensing.BSD3Clause, wantCategory: types.CategoryUnknown, wantSeverity: "UNKNOWN", diff --git a/pkg/log/handler_test.go b/pkg/log/handler_test.go index a122e46f5880..587c6243c1ec 100644 --- a/pkg/log/handler_test.go +++ b/pkg/log/handler_test.go @@ -197,7 +197,7 @@ func TestSlogtest(t *testing.T) { results := func(*testing.T) map[string]any { for _, line := range strings.Split(buf.String(), "\n") { - if len(line) == 0 { + if line == "" { continue } m, err := parseLogLine(line) diff --git a/pkg/misconf/scanner_test.go b/pkg/misconf/scanner_test.go index a3b412765706..6270b78b65e2 100644 --- a/pkg/misconf/scanner_test.go +++ b/pkg/misconf/scanner_test.go @@ -171,8 +171,8 @@ func TestScanner_Scan(t *testing.T) { func Test_createPolicyFS(t *testing.T) { t.Run("outside pwd", func(t *testing.T) { tmpDir := t.TempDir() - require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "subdir/testdir"), 0750)) - f, got, err := CreatePolicyFS([]string{filepath.Join(tmpDir, "subdir/testdir")}) + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "subdir", "testdir"), 0750)) + f, got, err := CreatePolicyFS([]string{filepath.Join(tmpDir, "subdir", "testdir")}) assertFS(t, tmpDir, f, got, err) }) } @@ -180,8 +180,8 @@ func Test_createPolicyFS(t *testing.T) { func Test_CreateDataFS(t *testing.T) { t.Run("outside pwd", func(t *testing.T) { tmpDir := t.TempDir() - require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "subdir/testdir"), 0750)) - f, got, err := CreateDataFS([]string{filepath.Join(tmpDir, "subdir/testdir")}) + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "subdir", "testdir"), 0750)) + f, got, err := CreateDataFS([]string{filepath.Join(tmpDir, "subdir", "testdir")}) assertFS(t, tmpDir, f, got, err) }) } diff --git a/pkg/module/module_test.go b/pkg/module/module_test.go index d12ffebaf0c9..d5d07621b6ac 100644 --- a/pkg/module/module_test.go +++ b/pkg/module/module_test.go @@ -35,7 +35,7 @@ func TestManager_Register(t *testing.T) { Analyzers: map[string]int{ "happy": 1, }, - PostAnalyzers: map[string]int{}, + PostAnalyzers: make(map[string]int), }, wantPostScannerVersions: map[string]int{ "happy": 1, @@ -48,16 +48,16 @@ func TestManager_Register(t *testing.T) { Analyzers: map[string]int{ "analyzer": 1, }, - PostAnalyzers: map[string]int{}, + PostAnalyzers: make(map[string]int), }, - wantPostScannerVersions: map[string]int{}, + wantPostScannerVersions: make(map[string]int), }, { name: "only post scanner", moduleDir: "testdata/scanner", wantAnalyzerVersions: analyzer.Versions{ - Analyzers: map[string]int{}, - PostAnalyzers: map[string]int{}, + Analyzers: make(map[string]int), + PostAnalyzers: make(map[string]int), }, wantPostScannerVersions: map[string]int{ "scanner": 2, @@ -67,10 +67,10 @@ func TestManager_Register(t *testing.T) { name: "no module dir", moduleDir: "no-such-dir", wantAnalyzerVersions: analyzer.Versions{ - Analyzers: map[string]int{}, - PostAnalyzers: map[string]int{}, + Analyzers: make(map[string]int), + PostAnalyzers: make(map[string]int), }, - wantPostScannerVersions: map[string]int{}, + wantPostScannerVersions: make(map[string]int), }, { name: "pass enabled modules", @@ -84,7 +84,7 @@ func TestManager_Register(t *testing.T) { "happy": 1, "analyzer": 1, }, - PostAnalyzers: map[string]int{}, + PostAnalyzers: make(map[string]int), }, wantPostScannerVersions: map[string]int{ "happy": 1, diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go index 9e741ffddb7c..e38517a8d3e2 100644 --- a/pkg/policy/policy_test.go +++ b/pkg/policy/policy_test.go @@ -70,15 +70,15 @@ func TestClient_LoadBuiltinPolicies(t *testing.T) { name: "happy path", cacheDir: "testdata/happy", want: []string{ - filepath.Join("testdata/happy/policy/content/kubernetes"), - filepath.Join("testdata/happy/policy/content/docker"), + filepath.Join("testdata", "happy", "policy", "content", "kubernetes"), + filepath.Join("testdata", "happy", "policy", "content", "docker"), }, }, { name: "empty roots", cacheDir: "testdata/empty", want: []string{ - filepath.Join("testdata/empty/policy/content"), + filepath.Join("testdata", "empty", "policy", "content"), }, }, { diff --git a/pkg/purl/purl_test.go b/pkg/purl/purl_test.go index e010f6c35636..ddcfc98222e6 100644 --- a/pkg/purl/purl_test.go +++ b/pkg/purl/purl_test.go @@ -780,7 +780,7 @@ func TestPackageURL_LangType(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := (purl.PackageURL)(tt.purl) + p := purl.PackageURL(tt.purl) assert.Equalf(t, tt.want, p.LangType(), "LangType()") }) } diff --git a/pkg/remote/remote_test.go b/pkg/remote/remote_test.go index c0fdb3087f31..468bc069177b 100644 --- a/pkg/remote/remote_test.go +++ b/pkg/remote/remote_test.go @@ -110,7 +110,7 @@ func TestGet(t *testing.T) { name: "keychain", args: args{ imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), - config: fmt.Sprintf(`{"auths": {"%s": {"auth": %q}}}`, serverAddr, encode("test", "testpass")), + config: fmt.Sprintf(`{"auths": {%q: {"auth": %q}}}`, serverAddr, encode("test", "testpass")), option: types.RegistryOptions{ Insecure: true, }, @@ -180,7 +180,7 @@ func TestGet(t *testing.T) { name: "bad keychain", args: args{ imageName: fmt.Sprintf("%s/library/alpine:3.10", serverAddr), - config: fmt.Sprintf(`{"auths": {"%s": {"auth": %q}}}`, serverAddr, encode("foo", "bar")), + config: fmt.Sprintf(`{"auths": {%q: {"auth": %q}}}`, serverAddr, encode("foo", "bar")), option: types.RegistryOptions{ Insecure: true, }, diff --git a/pkg/rpc/server/listen_test.go b/pkg/rpc/server/listen_test.go index b36abb81ff37..d0715e835a1a 100644 --- a/pkg/rpc/server/listen_test.go +++ b/pkg/rpc/server/listen_test.go @@ -263,7 +263,7 @@ func Test_newServeMux(t *testing.T) { if tt.header == nil { resp, err = http.Get(url) } else { - req, err := http.NewRequest(http.MethodPost, url, nil) + req, err := http.NewRequest(http.MethodPost, url, http.NoBody) require.NoError(t, err) req.Header = tt.header diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 07ddf09c9af6..956db45249e3 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -115,7 +115,7 @@ func TestScanServer_Scan(t *testing.T) { Severity: common.Severity_MEDIUM, SeveritySource: "nvd", Layer: &common.Layer{}, - Cvss: map[string]*common.CVSS{}, + Cvss: make(map[string]*common.CVSS), VendorSeverity: map[string]common.Severity{ string(vulnerability.NVD): common.Severity_MEDIUM, }, diff --git a/pkg/sbom/io/encode_test.go b/pkg/sbom/io/encode_test.go index 43c0d022556b..c6cc450da832 100644 --- a/pkg/sbom/io/encode_test.go +++ b/pkg/sbom/io/encode_test.go @@ -533,7 +533,7 @@ func TestEncoder_Encode(t *testing.T) { uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"): nil, uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000006"): nil, }, - wantVulns: map[uuid.UUID][]core.Vulnerability{}, + wantVulns: make(map[uuid.UUID][]core.Vulnerability), }, { name: "invalid digest", diff --git a/pkg/utils/fsutils/fs_test.go b/pkg/utils/fsutils/fs_test.go index cef8e7af723d..fef4b7f5a32c 100644 --- a/pkg/utils/fsutils/fs_test.go +++ b/pkg/utils/fsutils/fs_test.go @@ -18,7 +18,7 @@ func touch(t *testing.T, name string) { } } -func write(t *testing.T, name string, content string) { +func write(t *testing.T, name, content string) { err := os.WriteFile(name, []byte(content), 0666) if err != nil { t.Fatal(err) From 20781e5bc3333b8b40e00787f1ddaccb2e46447b Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 24 May 2024 15:17:48 +0600 Subject: [PATCH 100/352] refactor(go): add priority for gobinary module versions from `ldflags` (#6745) --- pkg/dependency/parser/golang/binary/parse.go | 62 ++++++++++++- .../parser/golang/binary/parse_test.go | 87 ++++++++++++++++++- 2 files changed, 144 insertions(+), 5 deletions(-) diff --git a/pkg/dependency/parser/golang/binary/parse.go b/pkg/dependency/parser/golang/binary/parse.go index 0354668c33cc..2ef2b4cf1c47 100644 --- a/pkg/dependency/parser/golang/binary/parse.go +++ b/pkg/dependency/parser/golang/binary/parse.go @@ -4,6 +4,7 @@ import ( "cmp" "debug/buildinfo" "runtime/debug" + "slices" "sort" "strings" @@ -142,6 +143,14 @@ func (p *Parser) ParseLDFlags(name string, flags []string) string { return "" } + // foundVersions contains discovered versions by type. + // foundVersions doesn't contain duplicates. Versions are filled into first corresponding category. + // Possible elements(categories): + // [0]: Versions using format `github.com///cmd/**/*.=x.x.x` + // [1]: Versions that use prefixes from `defaultPrefixes` + // [2]: Other versions + var foundVersions = make([][]string, 3) + defaultPrefixes := []string{"main", "common", "version", "cmd"} for key, val := range x { // It's valid to set the -X flags with quotes so we trim any that might // have been provided: Ex: @@ -154,16 +163,46 @@ func (p *Parser) ParseLDFlags(name string, flags []string) string { // -X "main.version=1.0.0" key = strings.TrimLeft(key, `'`) val = strings.TrimRight(val, `'`) - if isValidXKey(key) && isValidSemVer(val) { - return val + if isVersionXKey(key) && isValidSemVer(val) { + switch { + case strings.HasPrefix(key, name+"/cmd/"): + foundVersions[0] = append(foundVersions[0], val) + case slices.Contains(defaultPrefixes, strings.ToLower(versionPrefix(key))): + foundVersions[1] = append(foundVersions[1], val) + default: + foundVersions[2] = append(foundVersions[2], val) + } } } - p.logger.Debug("Unable to detect dependency version used in `-ldflags` build info settings. Empty version used.", log.String("dependency", name)) + return p.chooseVersion(name, foundVersions) +} + +// chooseVersion chooses version from found versions +// Categories order: +// module name with `cmd` => versions with default prefixes => other versions +// See more in https://github.com/aquasecurity/trivy/issues/6702#issuecomment-2122271427 +func (p *Parser) chooseVersion(moduleName string, vers [][]string) string { + for _, versions := range vers { + // Versions for this category was not found + if len(versions) == 0 { + continue + } + + // More than 1 version for one category. + // Use empty version. + if len(versions) > 1 { + p.logger.Debug("Unable to detect dependency version. `-ldflags` build info settings contain more than one version. Empty version used.", log.String("dependency", moduleName)) + return "" + } + return versions[0] + } + + p.logger.Debug("Unable to detect dependency version. `-ldflags` build info settings don't contain version flag. Empty version used.", log.String("dependency", moduleName)) return "" } -func isValidXKey(key string) bool { +func isVersionXKey(key string) bool { key = strings.ToLower(key) // The check for a 'ver' prefix enables the parser to pick up Trivy's own version value that's set. return strings.HasSuffix(key, ".version") || strings.HasSuffix(key, ".ver") @@ -175,3 +214,18 @@ func isValidSemVer(ver string) bool { // parse a valid semver version. return semver.IsValid(ver) || semver.IsValid("v"+ver) } + +// versionPrefix returns version prefix from `-ldflags` flag key +// e.g. +// - `github.com/aquasecurity/trivy/pkg/version.version` => `version` +// - `github.com/google/go-containerregistry/cmd/crane/common.ver` => `common` +func versionPrefix(s string) string { + // Trim module part. + // e.g. `github.com/aquasecurity/trivy/pkg/Version.version` => `Version.version` + if lastIndex := strings.LastIndex(s, "/"); lastIndex > 0 { + s = s[lastIndex+1:] + } + + s, _, _ = strings.Cut(s, ".") + return strings.ToLower(s) +} diff --git a/pkg/dependency/parser/golang/binary/parse_test.go b/pkg/dependency/parser/golang/binary/parse_test.go index 47e5ec0db7c6..fd500df4aaf5 100644 --- a/pkg/dependency/parser/golang/binary/parse_test.go +++ b/pkg/dependency/parser/golang/binary/parse_test.go @@ -238,6 +238,91 @@ func TestParser_ParseLDFlags(t *testing.T) { }, want: "0.50.1", }, + { + name: "with `cmd` + `default prefix` flags", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-X='github.com/aquasecurity/trivy/cmd/Any.Ver=0.50.0'", + "-X='github.com/aquasecurity/trivy/pkg/version.Ver=0.50.1'", + }, + }, + want: "0.50.0", + }, + { + name: "with `cmd` flag", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-X='github.com/aquasecurity/trivy/cmd/Any.Ver=0.50.0'", + }, + }, + want: "0.50.0", + }, + { + name: "with `cmd` + `other` flags", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-X='github.com/aquasecurity/trivy/cmd/Any.Ver=0.50.0'", + "-X='github.com/aquasecurity/trivy/pkg/Any.Ver=0.50.1'", + }, + }, + want: "0.50.0", + }, + { + name: "with `default prefix` flag", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-X='github.com/aquasecurity/trivy/pkg/Common.Ver=0.50.1'", + }, + }, + want: "0.50.1", + }, + { + name: "with `default prefix` + `other` flags", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-X='github.com/aquasecurity/trivy/pkg/Any.Ver=0.50.0'", + "-X='github.com/aquasecurity/trivy/pkg/Common.Ver=0.50.1'", + }, + }, + want: "0.50.1", + }, + { + name: "with `other` flag", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-X='github.com/aquasecurity/trivy/pkg/Any.Ver=0.50.1'", + }, + }, + want: "0.50.1", + }, + { + name: "with 2 flags using default prefixes", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-X='github.com/aquasecurity/trivy/pkg/Common.Ver=0.50.0'", + "-X='github.com/aquasecurity/trivy/pkg/Main.Ver=0.50.1'", + }, + }, + want: "", + }, + { + name: "with two `other` flags", + args: args{ + name: "github.com/aquasecurity/trivy", + flags: []string{ + "-X='github.com/aquasecurity/trivy/pkg/Any.Ver=0.50.1'", + "-X='github.com/aquasecurity/trivy/pkg/Any-pref.Ver=0.50.0'", + }, + }, + want: "", + }, { name: "with version with extra prefix", args: args{ @@ -262,7 +347,7 @@ func TestParser_ParseLDFlags(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := binary.NewParser() - assert.Equal(t, tt.want, p.ParseLDFlags(tt.args.name, tt.args.flags)) + require.Equal(t, tt.want, p.ParseLDFlags(tt.args.name, tt.args.flags)) }) } } From 9c3e895fcb0852c00ac03ed21338768f76b5273b Mon Sep 17 00:00:00 2001 From: guangwu Date: Fri, 24 May 2024 21:50:34 +0800 Subject: [PATCH 101/352] fix: close settings.xml (#6768) Signed-off-by: guoguangwu --- pkg/dependency/parser/java/pom/settings.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/dependency/parser/java/pom/settings.go b/pkg/dependency/parser/java/pom/settings.go index 42153155706a..7045f438beaf 100644 --- a/pkg/dependency/parser/java/pom/settings.go +++ b/pkg/dependency/parser/java/pom/settings.go @@ -68,6 +68,7 @@ func openSettings(filePath string) (settings, error) { if err != nil { return settings{}, err } + defer f.Close() s := settings{} decoder := xml.NewDecoder(f) From ebb123f37f7356954113786461f272241be884a7 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Mon, 27 May 2024 08:53:19 +0600 Subject: [PATCH 102/352] chore: replace interface{} with any (#6751) --- .golangci.yaml | 3 + integration/client_server_test.go | 4 +- internal/testutil/util.go | 32 +- pkg/attestation/attestation.go | 2 +- pkg/cloud/aws/config/config.go | 2 +- pkg/compliance/report/json.go | 2 +- .../xerrors/xerrors.go | 2 +- .../xerrors/xerrors.go | 2 +- .../xerrors/xerrors.go | 2 +- .../parser/nodejs/packagejson/parse.go | 14 +- pkg/dependency/parser/nodejs/pnpm/parse.go | 2 +- pkg/dependency/parser/python/poetry/parse.go | 20 +- .../parser/python/poetry/parse_test.go | 4 +- .../parser/python/pyproject/pyproject.go | 4 +- .../parser/python/pyproject/pyproject_test.go | 12 +- .../parser/swift/cocoapods/parse.go | 4 +- .../analyzer/language/dart/pub/pubspec.go | 6 +- .../analyzer/language/python/poetry/poetry.go | 2 +- .../analyzer/language/rust/cargo/cargo.go | 4 +- pkg/fanal/applier/docker.go | 2 +- pkg/fanal/artifact/repo/git_test.go | 10 +- pkg/fanal/artifact/vm/vm_test.go | 4 +- pkg/fanal/cache/s3.go | 2 +- pkg/fanal/types/artifact.go | 2 +- pkg/flag/cache_flags_test.go | 4 +- pkg/flag/db_flags_test.go | 4 +- pkg/iac/debug/debug.go | 2 +- pkg/iac/detection/detect.go | 18 +- pkg/iac/providers/aws/iam/actions.go | 10344 ++++++++-------- pkg/iac/providers/aws/iam/iam.go | 4 +- pkg/iac/providers/dockerfile/dockerfile.go | 12 +- pkg/iac/rego/build.go | 2 +- pkg/iac/rego/convert/anonymous.go | 2 +- pkg/iac/rego/convert/converter.go | 2 +- pkg/iac/rego/convert/slice.go | 4 +- pkg/iac/rego/convert/slice_test.go | 8 +- pkg/iac/rego/convert/struct.go | 4 +- pkg/iac/rego/convert/struct_test.go | 2 +- pkg/iac/rego/metadata.go | 24 +- pkg/iac/rego/metadata_test.go | 6 +- pkg/iac/rego/result.go | 24 +- pkg/iac/rego/result_test.go | 14 +- pkg/iac/rego/scanner.go | 16 +- pkg/iac/rego/scanner_test.go | 44 +- pkg/iac/rego/schemas/builder.go | 4 +- pkg/iac/scan/result.go | 16 +- .../azure/arm/parser/armjson/bench_test.go | 4 +- .../azure/arm/parser/armjson/decode.go | 2 +- .../azure/arm/parser/armjson/decode_array.go | 2 +- .../azure/arm/parser/armjson/decode_object.go | 2 +- .../scanners/azure/arm/parser/armjson/node.go | 4 +- .../azure/arm/parser/armjson/parse.go | 2 +- .../arm/parser/armjson/parse_array_test.go | 2 +- .../arm/parser/armjson/parse_boolean_test.go | 2 +- .../arm/parser/armjson/parse_complex_test.go | 8 +- .../arm/parser/armjson/parse_object_test.go | 8 +- .../arm/parser/armjson/parse_string_test.go | 2 +- .../azure/arm/parser/armjson/unmarshal.go | 4 +- pkg/iac/scanners/azure/deployment.go | 42 +- pkg/iac/scanners/azure/expressions/lex.go | 2 +- pkg/iac/scanners/azure/expressions/node.go | 10 +- pkg/iac/scanners/azure/functions/add.go | 2 +- pkg/iac/scanners/azure/functions/add_test.go | 8 +- pkg/iac/scanners/azure/functions/and.go | 2 +- pkg/iac/scanners/azure/functions/and_test.go | 8 +- pkg/iac/scanners/azure/functions/array.go | 12 +- .../scanners/azure/functions/array_test.go | 14 +- pkg/iac/scanners/azure/functions/base64.go | 8 +- .../scanners/azure/functions/base64_test.go | 12 +- pkg/iac/scanners/azure/functions/bool.go | 2 +- pkg/iac/scanners/azure/functions/bool_test.go | 18 +- pkg/iac/scanners/azure/functions/casing.go | 4 +- .../scanners/azure/functions/casing_test.go | 12 +- pkg/iac/scanners/azure/functions/coalesce.go | 2 +- .../scanners/azure/functions/coalesce_test.go | 16 +- pkg/iac/scanners/azure/functions/concat.go | 8 +- .../scanners/azure/functions/concat_test.go | 34 +- pkg/iac/scanners/azure/functions/contains.go | 6 +- .../scanners/azure/functions/contains_test.go | 26 +- .../scanners/azure/functions/copy_index.go | 2 +- .../azure/functions/copy_index_test.go | 14 +- .../scanners/azure/functions/create_array.go | 4 +- .../azure/functions/create_array_test.go | 32 +- .../scanners/azure/functions/create_object.go | 4 +- .../azure/functions/create_object_test.go | 28 +- pkg/iac/scanners/azure/functions/data_uri.go | 4 +- .../scanners/azure/functions/data_uri_test.go | 8 +- .../scanners/azure/functions/date_time_add.go | 2 +- .../azure/functions/date_time_epoch.go | 4 +- .../azure/functions/date_time_epoch_test.go | 12 +- .../azure/functions/datetime_add_test.go | 8 +- .../scanners/azure/functions/deployment.go | 14 +- pkg/iac/scanners/azure/functions/div.go | 2 +- pkg/iac/scanners/azure/functions/div_test.go | 8 +- pkg/iac/scanners/azure/functions/empty.go | 8 +- .../scanners/azure/functions/empty_test.go | 18 +- pkg/iac/scanners/azure/functions/ends_with.go | 2 +- .../azure/functions/ends_with_test.go | 6 +- pkg/iac/scanners/azure/functions/equals.go | 6 +- .../scanners/azure/functions/equals_test.go | 36 +- pkg/iac/scanners/azure/functions/false.go | 2 +- pkg/iac/scanners/azure/functions/first.go | 4 +- .../scanners/azure/functions/first_test.go | 12 +- pkg/iac/scanners/azure/functions/float.go | 2 +- .../scanners/azure/functions/float_test.go | 8 +- pkg/iac/scanners/azure/functions/format.go | 4 +- .../scanners/azure/functions/format_test.go | 6 +- pkg/iac/scanners/azure/functions/functions.go | 6 +- pkg/iac/scanners/azure/functions/greater.go | 4 +- .../scanners/azure/functions/greater_test.go | 28 +- pkg/iac/scanners/azure/functions/guid.go | 4 +- pkg/iac/scanners/azure/functions/guid_test.go | 6 +- pkg/iac/scanners/azure/functions/if.go | 2 +- pkg/iac/scanners/azure/functions/if_test.go | 16 +- pkg/iac/scanners/azure/functions/index_of.go | 2 +- .../scanners/azure/functions/index_of_test.go | 8 +- pkg/iac/scanners/azure/functions/int.go | 2 +- pkg/iac/scanners/azure/functions/int_test.go | 8 +- .../scanners/azure/functions/intersection.go | 32 +- .../azure/functions/intersection_test.go | 60 +- pkg/iac/scanners/azure/functions/items.go | 2 +- pkg/iac/scanners/azure/functions/join.go | 2 +- pkg/iac/scanners/azure/functions/join_test.go | 6 +- pkg/iac/scanners/azure/functions/json.go | 4 +- pkg/iac/scanners/azure/functions/json_test.go | 14 +- pkg/iac/scanners/azure/functions/last.go | 4 +- .../scanners/azure/functions/last_index_of.go | 2 +- .../azure/functions/last_index_of_test.go | 8 +- pkg/iac/scanners/azure/functions/last_test.go | 12 +- pkg/iac/scanners/azure/functions/length.go | 8 +- .../scanners/azure/functions/length_test.go | 10 +- pkg/iac/scanners/azure/functions/less.go | 4 +- pkg/iac/scanners/azure/functions/less_test.go | 28 +- pkg/iac/scanners/azure/functions/max.go | 4 +- pkg/iac/scanners/azure/functions/max_test.go | 12 +- pkg/iac/scanners/azure/functions/min.go | 4 +- pkg/iac/scanners/azure/functions/min_test.go | 12 +- pkg/iac/scanners/azure/functions/mod.go | 2 +- pkg/iac/scanners/azure/functions/mod_test.go | 10 +- pkg/iac/scanners/azure/functions/mul.go | 2 +- pkg/iac/scanners/azure/functions/mul_test.go | 8 +- pkg/iac/scanners/azure/functions/not.go | 2 +- pkg/iac/scanners/azure/functions/not_test.go | 6 +- pkg/iac/scanners/azure/functions/null.go | 2 +- pkg/iac/scanners/azure/functions/or.go | 2 +- pkg/iac/scanners/azure/functions/or_test.go | 10 +- pkg/iac/scanners/azure/functions/pad.go | 2 +- pkg/iac/scanners/azure/functions/pad_test.go | 10 +- .../scanners/azure/functions/pick_zones.go | 2 +- pkg/iac/scanners/azure/functions/range.go | 4 +- .../scanners/azure/functions/range_test.go | 10 +- pkg/iac/scanners/azure/functions/reference.go | 2 +- pkg/iac/scanners/azure/functions/replace.go | 2 +- .../scanners/azure/functions/replace_test.go | 6 +- pkg/iac/scanners/azure/functions/resource.go | 6 +- pkg/iac/scanners/azure/functions/scope.go | 12 +- .../scanners/azure/functions/scope_test.go | 4 +- pkg/iac/scanners/azure/functions/skip.go | 6 +- pkg/iac/scanners/azure/functions/skip_test.go | 14 +- pkg/iac/scanners/azure/functions/split.go | 4 +- .../scanners/azure/functions/split_test.go | 6 +- .../scanners/azure/functions/starts_with.go | 2 +- .../azure/functions/starts_with_test.go | 6 +- pkg/iac/scanners/azure/functions/string.go | 2 +- .../scanners/azure/functions/string_test.go | 8 +- pkg/iac/scanners/azure/functions/sub.go | 2 +- pkg/iac/scanners/azure/functions/sub_test.go | 10 +- pkg/iac/scanners/azure/functions/substring.go | 2 +- .../azure/functions/substring_test.go | 8 +- pkg/iac/scanners/azure/functions/take.go | 6 +- pkg/iac/scanners/azure/functions/take_test.go | 14 +- pkg/iac/scanners/azure/functions/trim.go | 2 +- pkg/iac/scanners/azure/functions/trim_test.go | 14 +- pkg/iac/scanners/azure/functions/true.go | 2 +- pkg/iac/scanners/azure/functions/union.go | 24 +- .../scanners/azure/functions/union_test.go | 52 +- .../scanners/azure/functions/unique_string.go | 2 +- .../azure/functions/unique_string_test.go | 6 +- pkg/iac/scanners/azure/functions/uri.go | 2 +- pkg/iac/scanners/azure/functions/uri_test.go | 8 +- pkg/iac/scanners/azure/functions/utc_now.go | 2 +- .../scanners/azure/functions/utc_now_test.go | 6 +- pkg/iac/scanners/azure/value.go | 12 +- .../scanners/cloudformation/cftypes/types.go | 2 +- .../cloudformation/parser/file_context.go | 10 +- .../cloudformation/parser/fn_find_in_map.go | 4 +- .../cloudformation/parser/parameter.go | 8 +- .../cloudformation/parser/property.go | 16 +- .../cloudformation/parser/property_helpers.go | 4 +- .../parser/property_helpers_test.go | 2 +- .../parser/pseudo_parameters.go | 6 +- .../parser/pseudo_parameters_test.go | 2 +- pkg/iac/scanners/helm/parser/parser.go | 2 +- pkg/iac/scanners/helm/parser/vals.go | 16 +- pkg/iac/scanners/json/parser/parser.go | 8 +- pkg/iac/scanners/json/parser/parser_test.go | 6 +- .../scanners/kubernetes/parser/manifest.go | 2 +- .../kubernetes/parser/manifest_node.go | 10 +- pkg/iac/scanners/kubernetes/parser/parser.go | 12 +- pkg/iac/scanners/terraform/attribute_test.go | 18 +- pkg/iac/scanners/terraform/executor/pool.go | 2 +- .../scanners/terraform/parser/funcs/redact.go | 2 +- .../terraform/parser/resolvers/options.go | 2 +- .../terraformplan/tfjson/parser/parser.go | 4 +- .../terraformplan/tfjson/parser/plan_file.go | 6 +- pkg/iac/scanners/toml/parser/parser.go | 8 +- pkg/iac/scanners/toml/parser/parser_test.go | 6 +- pkg/iac/scanners/yaml/parser/parser.go | 10 +- pkg/iac/scanners/yaml/parser/parser_test.go | 18 +- pkg/iac/state/state.go | 2 +- pkg/iac/state/state_test.go | 14 +- pkg/iac/terraform/attribute.go | 42 +- pkg/iac/terraform/block.go | 2 +- pkg/iac/terraform/reference.go | 2 +- pkg/iac/terraform/resource_block.go | 34 +- pkg/iac/terraform/value_functions.go | 18 +- pkg/iac/types/bool.go | 10 +- pkg/iac/types/bytes.go | 10 +- pkg/iac/types/int.go | 10 +- pkg/iac/types/map.go | 10 +- pkg/iac/types/metadata.go | 18 +- pkg/iac/types/metadata_test.go | 4 +- pkg/iac/types/range.go | 4 +- pkg/iac/types/string.go | 10 +- pkg/iac/types/time.go | 10 +- pkg/k8s/report/summary.go | 2 +- pkg/k8s/scanner/scanner_test.go | 20 +- pkg/module/memfs.go | 2 +- pkg/module/serialize/types.go | 4 +- pkg/policy/policy_test.go | 2 +- pkg/report/github/github.go | 2 +- pkg/report/predicate/vuln.go | 8 +- pkg/report/sarif_test.go | 28 +- pkg/report/table/licensing.go | 4 +- pkg/report/table/misconfig.go | 2 +- pkg/report/table/secret.go | 2 +- pkg/report/table/table.go | 2 +- pkg/report/table/vulnerability.go | 2 +- pkg/report/template.go | 2 +- pkg/result/filter.go | 2 +- pkg/rpc/client/client_test.go | 2 +- pkg/sbom/sbom.go | 4 +- pkg/sbom/spdx/marshal.go | 4 +- pkg/sbom/spdx/unmarshal.go | 2 +- pkg/types/vulnerability.go | 2 +- pkg/x/sync/sync.go | 2 +- 246 files changed, 6191 insertions(+), 6188 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index b3a9da0192e3..4be442219788 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -6,6 +6,9 @@ linters-settings: check-shadowing: false gofmt: simplify: false + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' revive: ignore-generated-header: true gocyclo: diff --git a/integration/client_server_test.go b/integration/client_server_test.go index 8a5b62f05054..32ef4a972d26 100644 --- a/integration/client_server_test.go +++ b/integration/client_server_test.go @@ -371,7 +371,7 @@ func TestClientServerWithFormat(t *testing.T) { } fakeTime := time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC) - report.CustomTemplateFuncMap = map[string]interface{}{ + report.CustomTemplateFuncMap = map[string]any{ "now": func() time.Time { return fakeTime }, @@ -388,7 +388,7 @@ func TestClientServerWithFormat(t *testing.T) { t.Setenv("GITHUB_WORKFLOW", "workflow-name") t.Cleanup(func() { - report.CustomTemplateFuncMap = map[string]interface{}{} + report.CustomTemplateFuncMap = map[string]any{} }) addr, cacheDir := setup(t, setupOptions{}) diff --git a/internal/testutil/util.go b/internal/testutil/util.go index f16b95acaae3..4b2252db0891 100644 --- a/internal/testutil/util.go +++ b/internal/testutil/util.go @@ -14,9 +14,9 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scan" ) -func AssertRuleFound(t *testing.T, ruleID string, results scan.Results, message string, args ...interface{}) { +func AssertRuleFound(t *testing.T, ruleID string, results scan.Results, message string, args ...any) { found := ruleIDInResults(ruleID, results.GetFailed()) - assert.True(t, found, append([]interface{}{message}, args...)...) + assert.True(t, found, append([]any{message}, args...)...) for _, result := range results.GetFailed() { if result.Rule().LongID() == ruleID { m := result.Metadata() @@ -31,9 +31,9 @@ func AssertRuleFound(t *testing.T, ruleID string, results scan.Results, message } } -func AssertRuleNotFound(t *testing.T, ruleID string, results scan.Results, message string, args ...interface{}) { +func AssertRuleNotFound(t *testing.T, ruleID string, results scan.Results, message string, args ...any) { found := ruleIDInResults(ruleID, results.GetFailed()) - assert.False(t, found, append([]interface{}{message}, args...)...) + assert.False(t, found, append([]any{message}, args...)...) } func ruleIDInResults(ruleID string, results scan.Results) bool { @@ -57,24 +57,24 @@ func CreateFS(t *testing.T, files map[string]string) fs.FS { return memfs } -func AssertDefsecEqual(t *testing.T, expected, actual interface{}) { +func AssertDefsecEqual(t *testing.T, expected, actual any) { expectedJson, err := json.MarshalIndent(expected, "", "\t") require.NoError(t, err) actualJson, err := json.MarshalIndent(actual, "", "\t") require.NoError(t, err) if expectedJson[0] == '[' { - var expectedSlice []map[string]interface{} + var expectedSlice []map[string]any require.NoError(t, json.Unmarshal(expectedJson, &expectedSlice)) - var actualSlice []map[string]interface{} + var actualSlice []map[string]any require.NoError(t, json.Unmarshal(actualJson, &actualSlice)) expectedSlice = purgeMetadataSlice(expectedSlice) actualSlice = purgeMetadataSlice(actualSlice) assert.Equal(t, expectedSlice, actualSlice, "defsec adapted and expected values do not match") } else { - var expectedMap map[string]interface{} + var expectedMap map[string]any require.NoError(t, json.Unmarshal(expectedJson, &expectedMap)) - var actualMap map[string]interface{} + var actualMap map[string]any require.NoError(t, json.Unmarshal(actualJson, &actualMap)) expectedMap = purgeMetadata(expectedMap) actualMap = purgeMetadata(actualMap) @@ -82,21 +82,21 @@ func AssertDefsecEqual(t *testing.T, expected, actual interface{}) { } } -func purgeMetadata(input map[string]interface{}) map[string]interface{} { +func purgeMetadata(input map[string]any) map[string]any { for k, v := range input { if k == "metadata" || k == "Metadata" { delete(input, k) continue } - if v, ok := v.(map[string]interface{}); ok { + if v, ok := v.(map[string]any); ok { input[k] = purgeMetadata(v) } - if v, ok := v.([]interface{}); ok { + if v, ok := v.([]any); ok { if len(v) > 0 { - if _, ok := v[0].(map[string]interface{}); ok { - maps := make([]map[string]interface{}, len(v)) + if _, ok := v[0].(map[string]any); ok { + maps := make([]map[string]any, len(v)) for i := range v { - maps[i] = v[i].(map[string]interface{}) + maps[i] = v[i].(map[string]any) } input[k] = purgeMetadataSlice(maps) } @@ -106,7 +106,7 @@ func purgeMetadata(input map[string]interface{}) map[string]interface{} { return input } -func purgeMetadataSlice(input []map[string]interface{}) []map[string]interface{} { +func purgeMetadataSlice(input []map[string]any) []map[string]any { for i := range input { input[i] = purgeMetadata(input[i]) } diff --git a/pkg/attestation/attestation.go b/pkg/attestation/attestation.go index 8f4cb8ca54aa..141752e83420 100644 --- a/pkg/attestation/attestation.go +++ b/pkg/attestation/attestation.go @@ -14,7 +14,7 @@ import ( // Cosign uses this structure when creating an SBOM attestation. // cf. https://github.com/sigstore/cosign/blob/e0547cff64f98585a837a524ff77ff6b47ff5609/pkg/cosign/attestation/attestation.go#L39-L43 type CosignPredicate struct { - Data interface{} + Data any } // Statement holds in-toto statement headers and the predicate. diff --git a/pkg/cloud/aws/config/config.go b/pkg/cloud/aws/config/config.go index a2c9be5916bf..e4ef5f2f70dc 100644 --- a/pkg/cloud/aws/config/config.go +++ b/pkg/cloud/aws/config/config.go @@ -9,7 +9,7 @@ import ( ) func EndpointResolver(endpoint string) aws.EndpointResolverWithOptionsFunc { - return aws.EndpointResolverWithOptionsFunc(func(_, reg string, options ...interface{}) (aws.Endpoint, error) { + return aws.EndpointResolverWithOptionsFunc(func(_, reg string, options ...any) (aws.Endpoint, error) { return aws.Endpoint{ PartitionID: "aws", URL: endpoint, diff --git a/pkg/compliance/report/json.go b/pkg/compliance/report/json.go index 8d58f25106b5..6dd33fc4a8a3 100644 --- a/pkg/compliance/report/json.go +++ b/pkg/compliance/report/json.go @@ -18,7 +18,7 @@ func (jw JSONWriter) Write(report *ComplianceReport) error { var output []byte var err error - var v interface{} + var v any switch jw.Report { case allReport: v = report diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/xerrors/xerrors.go b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/xerrors/xerrors.go index c92105140956..715777453212 100644 --- a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/xerrors/xerrors.go +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version-mismatch/xerrors/xerrors.go @@ -1,5 +1,5 @@ package xerrors -func Errorf(format string, a ...interface{}) error { +func Errorf(format string, a ...any) error { return nil } diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/xerrors/xerrors.go b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/xerrors/xerrors.go index c92105140956..715777453212 100644 --- a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/xerrors/xerrors.go +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path-and-version/xerrors/xerrors.go @@ -1,5 +1,5 @@ package xerrors -func Errorf(format string, a ...interface{}) error { +func Errorf(format string, a ...any) error { return nil } diff --git a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/xerrors/xerrors.go b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/xerrors/xerrors.go index c92105140956..715777453212 100644 --- a/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/xerrors/xerrors.go +++ b/pkg/dependency/parser/golang/mod/testdata/replaced-with-local-path/xerrors/xerrors.go @@ -1,5 +1,5 @@ package xerrors -func Errorf(format string, a ...interface{}) error { +func Errorf(format string, a ...any) error { return nil } diff --git a/pkg/dependency/parser/nodejs/packagejson/parse.go b/pkg/dependency/parser/nodejs/packagejson/parse.go index 80d11e2193bf..c78199265815 100644 --- a/pkg/dependency/parser/nodejs/packagejson/parse.go +++ b/pkg/dependency/parser/nodejs/packagejson/parse.go @@ -17,7 +17,7 @@ var nameRegexp = regexp.MustCompile(`^(@[A-Za-z0-9-._]+/)?[A-Za-z0-9-._]+$`) type packageJSON struct { Name string `json:"name"` Version string `json:"version"` - License interface{} `json:"license"` + License any `json:"license"` Dependencies map[string]string `json:"dependencies"` OptionalDependencies map[string]string `json:"optionalDependencies"` DevDependencies map[string]string `json:"devDependencies"` @@ -69,14 +69,14 @@ func (p *Parser) Parse(r io.Reader) (Package, error) { }, nil } -func parseLicense(val interface{}) []string { +func parseLicense(val any) []string { // the license isn't always a string, check for legacy struct if not string switch v := val.(type) { case string: if v != "" { return []string{v} } - case map[string]interface{}: + case map[string]any: if license, ok := v["type"]; ok { if s, ok := license.(string); ok && s != "" { return []string{s} @@ -92,17 +92,17 @@ func parseWorkspaces(val any) []string { switch ws := val.(type) { // Workspace as object (map[string][]string) // e.g. "workspaces": {"packages": ["packages/*", "plugins/*"]}, - case map[string]interface{}: + case map[string]any: // Take only workspaces for `packages` - https://classic.yarnpkg.com/blog/2018/02/15/nohoist/ if pkgsWorkspaces, ok := ws["packages"]; ok { - return lo.Map(pkgsWorkspaces.([]interface{}), func(workspace interface{}, _ int) string { + return lo.Map(pkgsWorkspaces.([]any), func(workspace any, _ int) string { return workspace.(string) }) } // Workspace as string array // e.g. "workspaces": ["packages/*", "backend"], - case []interface{}: - return lo.Map(ws, func(workspace interface{}, _ int) string { + case []any: + return lo.Map(ws, func(workspace any, _ int) string { return workspace.(string) }) } diff --git a/pkg/dependency/parser/nodejs/pnpm/parse.go b/pkg/dependency/parser/nodejs/pnpm/parse.go index 00c573a4c4db..aad4e138b9fe 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse.go @@ -360,7 +360,7 @@ func (p *Parser) parseVersion(depPath, ver string, lockVer float64) string { return ver } -func isDirectPkg(name string, directDeps map[string]interface{}) bool { +func isDirectPkg(name string, directDeps map[string]any) bool { _, ok := directDeps[name] return ok } diff --git a/pkg/dependency/parser/python/poetry/parse.go b/pkg/dependency/parser/python/poetry/parse.go index b7a365a17488..e691a0cb2532 100644 --- a/pkg/dependency/parser/python/poetry/parse.go +++ b/pkg/dependency/parser/python/poetry/parse.go @@ -16,15 +16,15 @@ import ( type Lockfile struct { Packages []struct { - Category string `toml:"category"` - Description string `toml:"description"` - Marker string `toml:"marker,omitempty"` - Name string `toml:"name"` - Optional bool `toml:"optional"` - PythonVersions string `toml:"python-versions"` - Version string `toml:"version"` - Dependencies map[string]interface{} `toml:"dependencies"` - Metadata interface{} + Category string `toml:"category"` + Description string `toml:"description"` + Marker string `toml:"marker,omitempty"` + Name string `toml:"name"` + Optional bool `toml:"optional"` + PythonVersions string `toml:"python-versions"` + Version string `toml:"version"` + Dependencies map[string]any `toml:"dependencies"` + Metadata any } `toml:"package"` } @@ -117,7 +117,7 @@ func (p *Parser) parseDependency(name string, versRange any, pkgVersions map[str switch r := versRange.(type) { case string: vRange = r - case map[string]interface{}: + case map[string]any: for k, v := range r { if k == "version" { vRange = v.(string) diff --git a/pkg/dependency/parser/python/poetry/parse_test.go b/pkg/dependency/parser/python/poetry/parse_test.go index 693bb5afcc79..6bb70c39e5db 100644 --- a/pkg/dependency/parser/python/poetry/parse_test.go +++ b/pkg/dependency/parser/python/poetry/parse_test.go @@ -61,7 +61,7 @@ func TestParseDependency(t *testing.T) { tests := []struct { name string packageName string - versionRange interface{} + versionRange any pkgsVersions map[string][]string want string wantErr string @@ -96,7 +96,7 @@ func TestParseDependency(t *testing.T) { { name: "version range as json", packageName: "test", - versionRange: map[string]interface{}{ + versionRange: map[string]any{ "version": ">=4.8.3", "markers": "python_version < \"3.8\"", }, diff --git a/pkg/dependency/parser/python/pyproject/pyproject.go b/pkg/dependency/parser/python/pyproject/pyproject.go index 9a1532027a0c..f5d6c748f1e4 100644 --- a/pkg/dependency/parser/python/pyproject/pyproject.go +++ b/pkg/dependency/parser/python/pyproject/pyproject.go @@ -16,7 +16,7 @@ type Tool struct { } type Poetry struct { - Dependencies map[string]interface{} `toml:"dependencies"` + Dependencies map[string]any `toml:"dependencies"` } // Parser parses pyproject.toml defined in PEP518. @@ -28,7 +28,7 @@ func NewParser() *Parser { return &Parser{} } -func (p *Parser) Parse(r io.Reader) (map[string]interface{}, error) { +func (p *Parser) Parse(r io.Reader) (map[string]any, error) { var conf PyProject if _, err := toml.NewDecoder(r).Decode(&conf); err != nil { return nil, xerrors.Errorf("toml decode error: %w", err) diff --git a/pkg/dependency/parser/python/pyproject/pyproject_test.go b/pkg/dependency/parser/python/pyproject/pyproject_test.go index ac72bad81211..6d125f1cc61f 100644 --- a/pkg/dependency/parser/python/pyproject/pyproject_test.go +++ b/pkg/dependency/parser/python/pyproject/pyproject_test.go @@ -15,24 +15,24 @@ func TestParser_Parse(t *testing.T) { tests := []struct { name string file string - want map[string]interface{} + want map[string]any wantErr assert.ErrorAssertionFunc }{ { name: "happy path", file: "testdata/happy.toml", - want: map[string]interface{}{ + want: map[string]any{ "flask": "^1.0", "python": "^3.9", - "requests": map[string]interface{}{ + "requests": map[string]any{ "version": "2.28.1", "optional": true, }, - "virtualenv": []interface{}{ - map[string]interface{}{ + "virtualenv": []any{ + map[string]any{ "version": "^20.4.3,!=20.4.5,!=20.4.6", }, - map[string]interface{}{ + map[string]any{ "version": "<20.16.6", "markers": "sys_platform == 'win32' and python_version == '3.9'", }, diff --git a/pkg/dependency/parser/swift/cocoapods/parse.go b/pkg/dependency/parser/swift/cocoapods/parse.go index eb5a960679f7..27438c86e17f 100644 --- a/pkg/dependency/parser/swift/cocoapods/parse.go +++ b/pkg/dependency/parser/swift/cocoapods/parse.go @@ -47,7 +47,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc continue } parsedDeps[pkg.Name] = pkg - case map[string]interface{}: // dependency with its child dependencies + case map[string]any: for dep, childDeps := range dep { pkg, err := parseDep(dep) if err != nil { @@ -56,7 +56,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc } parsedDeps[pkg.Name] = pkg - children, ok := childDeps.([]interface{}) + children, ok := childDeps.([]any) if !ok { return nil, nil, xerrors.Errorf("invalid value of cocoapods direct dependency: %q", childDeps) } diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec.go b/pkg/fanal/analyzer/language/dart/pub/pubspec.go index ad8a5396e255..9f7fc02b26d7 100644 --- a/pkg/fanal/analyzer/language/dart/pub/pubspec.go +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec.go @@ -145,9 +145,9 @@ func cacheDir() string { } type pubSpecYaml struct { - Name string `yaml:"name"` - Version string `yaml:"version,omitempty"` - Dependencies map[string]interface{} `yaml:"dependencies,omitempty"` + Name string `yaml:"name"` + Version string `yaml:"version,omitempty"` + Dependencies map[string]any `yaml:"dependencies,omitempty"` } func parsePubSpecYaml(r io.Reader) (string, []string, error) { diff --git a/pkg/fanal/analyzer/language/python/poetry/poetry.go b/pkg/fanal/analyzer/language/python/poetry/poetry.go index 84af54c587df..600f53bafc92 100644 --- a/pkg/fanal/analyzer/language/python/poetry/poetry.go +++ b/pkg/fanal/analyzer/language/python/poetry/poetry.go @@ -115,7 +115,7 @@ func (a poetryAnalyzer) mergePyProject(fsys fs.FS, dir string, app *types.Applic return nil } -func (a poetryAnalyzer) parsePyProject(fsys fs.FS, path string) (map[string]interface{}, error) { +func (a poetryAnalyzer) parsePyProject(fsys fs.FS, path string) (map[string]any, error) { // Parse pyproject.toml f, err := fsys.Open(path) if err != nil { diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo.go b/pkg/fanal/analyzer/language/rust/cargo/cargo.go index 5795586ab33a..88c8a005595e 100644 --- a/pkg/fanal/analyzer/language/rust/cargo/cargo.go +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo.go @@ -167,7 +167,7 @@ type cargoTomlWorkspace struct { Members []string `toml:"members"` } -type Dependencies map[string]interface{} +type Dependencies map[string]any // parseRootCargoTOML parses top-level Cargo.toml and returns dependencies. // It also parses workspace members and their dependencies. @@ -196,7 +196,7 @@ func (a cargoAnalyzer) parseRootCargoTOML(fsys fs.FS, filePath string) (map[stri case string: // e.g. regex = "1.5" deps[name] = ver - case map[string]interface{}: + case map[string]any: // e.g. serde = { version = "1.0", features = ["derive"] } for k, v := range ver { if k == "version" { diff --git a/pkg/fanal/applier/docker.go b/pkg/fanal/applier/docker.go index 41487819b4ae..706da683066d 100644 --- a/pkg/fanal/applier/docker.go +++ b/pkg/fanal/applier/docker.go @@ -166,7 +166,7 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { } // nolint - _ = nestedMap.Walk(func(keys []string, value interface{}) error { + _ = nestedMap.Walk(func(keys []string, value any) error { switch v := value.(type) { case ftypes.PackageInfo: mergedLayer.Packages = append(mergedLayer.Packages, v.Packages...) diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go index 4914588540d5..00ea53ef58d3 100644 --- a/pkg/fanal/artifact/repo/git_test.go +++ b/pkg/fanal/artifact/repo/git_test.go @@ -113,7 +113,7 @@ func TestNewArtifact(t *testing.T) { c: nil, noProgress: false, }, - assertion: func(t assert.TestingT, err error, args ...interface{}) bool { + assertion: func(t assert.TestingT, err error, args ...any) bool { return assert.ErrorContains(t, err, "repository not found") }, }, @@ -124,7 +124,7 @@ func TestNewArtifact(t *testing.T) { c: nil, noProgress: false, }, - assertion: func(t assert.TestingT, err error, args ...interface{}) bool { + assertion: func(t assert.TestingT, err error, args ...any) bool { return assert.ErrorContains(t, err, "url parse error") }, }, @@ -135,7 +135,7 @@ func TestNewArtifact(t *testing.T) { c: nil, repoBranch: "invalid-branch", }, - assertion: func(t assert.TestingT, err error, args ...interface{}) bool { + assertion: func(t assert.TestingT, err error, args ...any) bool { return assert.ErrorContains(t, err, `couldn't find remote ref "refs/heads/invalid-branch"`) }, }, @@ -146,7 +146,7 @@ func TestNewArtifact(t *testing.T) { c: nil, repoTag: "v1.0.9", }, - assertion: func(t assert.TestingT, err error, args ...interface{}) bool { + assertion: func(t assert.TestingT, err error, args ...any) bool { return assert.ErrorContains(t, err, `couldn't find remote ref "refs/tags/v1.0.9"`) }, }, @@ -157,7 +157,7 @@ func TestNewArtifact(t *testing.T) { c: nil, repoCommit: "6ac152fe2b87cb5e243414df71790a32912e778e", }, - assertion: func(t assert.TestingT, err error, args ...interface{}) bool { + assertion: func(t assert.TestingT, err error, args ...any) bool { return assert.ErrorContains(t, err, "git checkout error: object not found") }, }, diff --git a/pkg/fanal/artifact/vm/vm_test.go b/pkg/fanal/artifact/vm/vm_test.go index bbe04d6eba84..d1becf0f7067 100644 --- a/pkg/fanal/artifact/vm/vm_test.go +++ b/pkg/fanal/artifact/vm/vm_test.go @@ -79,14 +79,14 @@ func TestNewArtifact(t *testing.T) { { name: "sad path unsupported vm format", target: "testdata/monolithicSparse.vmdk", - wantErr: func(t assert.TestingT, err error, args ...interface{}) bool { + wantErr: func(t assert.TestingT, err error, args ...any) bool { return assert.ErrorContains(t, err, "unsupported type error") }, }, { name: "sad path file not found", target: "testdata/no-file", - wantErr: func(t assert.TestingT, err error, args ...interface{}) bool { + wantErr: func(t assert.TestingT, err error, args ...any) bool { return assert.ErrorContains(t, err, "file open error") }, }, diff --git a/pkg/fanal/cache/s3.go b/pkg/fanal/cache/s3.go index b11aed42a476..0176d2cf80d8 100644 --- a/pkg/fanal/cache/s3.go +++ b/pkg/fanal/cache/s3.go @@ -67,7 +67,7 @@ func (c S3Cache) PutBlob(blobID string, blobInfo types.BlobInfo) error { return nil } -func (c S3Cache) put(key string, body interface{}) (err error) { +func (c S3Cache) put(key string, body any) (err error) { b, err := json.Marshal(body) if err != nil { return err diff --git a/pkg/fanal/types/artifact.go b/pkg/fanal/types/artifact.go index 3b3d9b2472a0..b25aaa954188 100644 --- a/pkg/fanal/types/artifact.go +++ b/pkg/fanal/types/artifact.go @@ -184,5 +184,5 @@ type CustomResource struct { Type string FilePath string Layer Layer - Data interface{} + Data any } diff --git a/pkg/flag/cache_flags_test.go b/pkg/flag/cache_flags_test.go index db54c75b13e8..c795cdbd6715 100644 --- a/pkg/flag/cache_flags_test.go +++ b/pkg/flag/cache_flags_test.go @@ -82,7 +82,7 @@ func TestCacheFlagGroup_ToOptions(t *testing.T) { fields: fields{ CacheBackend: "unknown", }, - assertion: func(t require.TestingT, err error, msgs ...interface{}) { + assertion: func(t require.TestingT, err error, msgs ...any) { require.ErrorContains(t, err, "unsupported cache backend") }, }, @@ -92,7 +92,7 @@ func TestCacheFlagGroup_ToOptions(t *testing.T) { CacheBackend: "redis://localhost:6379", RedisCACert: "ca-cert.pem", }, - assertion: func(t require.TestingT, err error, msgs ...interface{}) { + assertion: func(t require.TestingT, err error, msgs ...any) { require.ErrorContains(t, err, "you must provide Redis CA") }, }, diff --git a/pkg/flag/db_flags_test.go b/pkg/flag/db_flags_test.go index 8a9960f91c24..fb6b31effaf4 100644 --- a/pkg/flag/db_flags_test.go +++ b/pkg/flag/db_flags_test.go @@ -66,7 +66,7 @@ func TestDBFlagGroup_ToOptions(t *testing.T) { SkipDBUpdate: true, DownloadDBOnly: true, }, - assertion: func(t require.TestingT, err error, msgs ...interface{}) { + assertion: func(t require.TestingT, err error, msgs ...any) { require.ErrorContains(t, err, "--skip-db-update and --download-db-only options can not be specified both") }, }, @@ -77,7 +77,7 @@ func TestDBFlagGroup_ToOptions(t *testing.T) { DownloadDBOnly: false, DBRepository: "foo:bar:baz", }, - assertion: func(t require.TestingT, err error, msgs ...interface{}) { + assertion: func(t require.TestingT, err error, msgs ...any) { require.ErrorContains(t, err, "invalid db repository") }, }, diff --git a/pkg/iac/debug/debug.go b/pkg/iac/debug/debug.go index 2b43e7dd9bde..489c777dd205 100644 --- a/pkg/iac/debug/debug.go +++ b/pkg/iac/debug/debug.go @@ -28,7 +28,7 @@ func (l *Logger) Extend(parts ...string) Logger { } } -func (l *Logger) Log(format string, args ...interface{}) { +func (l *Logger) Log(format string, args ...any) { if l.writer == nil { return } diff --git a/pkg/iac/detection/detect.go b/pkg/iac/detection/detect.go index 87c16582ca6c..596d8da1851d 100644 --- a/pkg/iac/detection/detect.go +++ b/pkg/iac/detection/detect.go @@ -45,7 +45,7 @@ func init() { return true } - var content interface{} + var content any return json.NewDecoder(r).Decode(&content) == nil } @@ -58,7 +58,7 @@ func init() { return true } - var content interface{} + var content any return yaml.NewDecoder(r).Decode(&content) == nil } @@ -85,7 +85,7 @@ func init() { return false } - contents := make(map[string]interface{}) + contents := make(map[string]any) err := json.NewDecoder(r).Decode(&contents) if err == nil { if _, ok := contents["terraform_version"]; ok { @@ -103,7 +103,7 @@ func init() { matchers[FileTypeCloudFormation] = func(name string, r io.ReadSeeker) bool { sniff := struct { - Resources map[string]map[string]interface{} `json:"Resources" yaml:"Resources"` + Resources map[string]map[string]any `json:"Resources" yaml:"Resources"` }{} switch { @@ -135,9 +135,9 @@ func init() { } sniff := struct { - ContentType string `json:"contentType"` - Parameters map[string]interface{} `json:"parameters"` - Resources []interface{} `json:"resources"` + ContentType string `json:"contentType"` + Parameters map[string]any `json:"parameters"` + Resources []any `json:"resources"` }{} metadata := types.NewUnmanagedMetadata() if err := armjson.UnmarshalFromReader(r, &sniff, &metadata); err != nil { @@ -196,7 +196,7 @@ func init() { return false } - var result map[string]interface{} + var result map[string]any if err := json.NewDecoder(r).Decode(&result); err != nil { return false } @@ -223,7 +223,7 @@ func init() { } for _, partial := range strings.Split(string(data), marker) { - var result map[string]interface{} + var result map[string]any if err := yaml.Unmarshal([]byte(partial), &result); err != nil { continue } diff --git a/pkg/iac/providers/aws/iam/actions.go b/pkg/iac/providers/aws/iam/actions.go index 125d32c521da..a58ad07f2097 100644 --- a/pkg/iac/providers/aws/iam/actions.go +++ b/pkg/iac/providers/aws/iam/actions.go @@ -3,5178 +3,5178 @@ package iam var allowedActionsForResourceWildcardsMap = map[string]struct{}{ - "a2c:GetContainerizationJobDetails": {}, - "a2c:GetDeploymentJobDetails": {}, - "a2c:StartContainerizationJob": {}, - "a2c:StartDeploymentJob": {}, - "a4b:ApproveSkill": {}, - "a4b:AssociateSkillWithUsers": {}, - "a4b:CompleteRegistration": {}, - "a4b:CreateAddressBook": {}, - "a4b:CreateBusinessReportSchedule": {}, - "a4b:CreateConferenceProvider": {}, - "a4b:CreateContact": {}, - "a4b:CreateGatewayGroup": {}, - "a4b:CreateNetworkProfile": {}, - "a4b:CreateProfile": {}, - "a4b:CreateSkillGroup": {}, - "a4b:GetConferencePreference": {}, - "a4b:GetInvitationConfiguration": {}, - "a4b:ListBusinessReportSchedules": {}, - "a4b:ListConferenceProviders": {}, - "a4b:ListGatewayGroups": {}, - "a4b:ListSkills": {}, - "a4b:ListSkillsStoreCategories": {}, - "a4b:ListSkillsStoreSkillsByCategory": {}, - "a4b:PutConferencePreference": {}, - "a4b:PutDeviceSetupEvents": {}, - "a4b:PutInvitationConfiguration": {}, - "a4b:RegisterAVSDevice": {}, - "a4b:RegisterDevice": {}, - "a4b:RejectSkill": {}, - "a4b:ResolveRoom": {}, - "a4b:SearchAddressBooks": {}, - "a4b:SearchContacts": {}, - "a4b:SearchDevices": {}, - "a4b:SearchNetworkProfiles": {}, - "a4b:SearchProfiles": {}, - "a4b:SearchRooms": {}, - "a4b:SearchSkillGroups": {}, - "a4b:SearchUsers": {}, - "a4b:SendAnnouncement": {}, - "a4b:StartDeviceSync": {}, - "access-analyzer:CancelPolicyGeneration": {}, - "access-analyzer:CheckAccessNotGranted": {}, - "access-analyzer:CheckNoNewAccess": {}, - "access-analyzer:GetGeneratedPolicy": {}, - "access-analyzer:ListAnalyzers": {}, - "access-analyzer:ListPolicyGenerations": {}, - "access-analyzer:StartPolicyGeneration": {}, - "access-analyzer:ValidatePolicy": {}, - "acm-pca:CreateCertificateAuthority": {}, - "acm-pca:ListCertificateAuthorities": {}, - "acm:GetAccountConfiguration": {}, - "acm:ListCertificates": {}, - "acm:PutAccountConfiguration": {}, - "acm:RequestCertificate": {}, - "activate:CreateForm": {}, - "activate:GetAccountContact": {}, - "activate:GetContentInfo": {}, - "activate:GetCosts": {}, - "activate:GetCredits": {}, - "activate:GetMemberInfo": {}, - "activate:GetProgram": {}, - "activate:PutMemberInfo": {}, - "airflow:ListEnvironments": {}, - "amplify:ListApps": {}, - "amplifybackend:ListS3Buckets": {}, - "amplifyuibuilder:CreateComponent": {}, - "amplifyuibuilder:CreateForm": {}, - "amplifyuibuilder:CreateTheme": {}, - "amplifyuibuilder:ExchangeCodeForToken": {}, - "amplifyuibuilder:ExportComponents": {}, - "amplifyuibuilder:ExportForms": {}, - "amplifyuibuilder:ExportThemes": {}, - "amplifyuibuilder:GetMetadata": {}, - "amplifyuibuilder:ListCodegenJobs": {}, - "amplifyuibuilder:ListComponents": {}, - "amplifyuibuilder:ListForms": {}, - "amplifyuibuilder:ListThemes": {}, - "amplifyuibuilder:PutMetadataFlag": {}, - "amplifyuibuilder:RefreshToken": {}, - "amplifyuibuilder:ResetMetadataFlag": {}, - "amplifyuibuilder:StartCodegenJob": {}, - "aoss:BatchGetCollection": {}, - "aoss:BatchGetEffectiveLifecyclePolicy": {}, - "aoss:BatchGetLifecyclePolicy": {}, - "aoss:BatchGetVpcEndpoint": {}, - "aoss:CreateAccessPolicy": {}, - "aoss:CreateCollection": {}, - "aoss:CreateLifecyclePolicy": {}, - "aoss:CreateSecurityConfig": {}, - "aoss:CreateSecurityPolicy": {}, - "aoss:CreateVpcEndpoint": {}, - "aoss:DeleteAccessPolicy": {}, - "aoss:DeleteLifecyclePolicy": {}, - "aoss:DeleteSecurityConfig": {}, - "aoss:DeleteSecurityPolicy": {}, - "aoss:DeleteVpcEndpoint": {}, - "aoss:GetAccessPolicy": {}, - "aoss:GetAccountSettings": {}, - "aoss:GetPoliciesStats": {}, - "aoss:GetSecurityConfig": {}, - "aoss:GetSecurityPolicy": {}, - "aoss:ListAccessPolicies": {}, - "aoss:ListCollections": {}, - "aoss:ListLifecyclePolicies": {}, - "aoss:ListSecurityConfigs": {}, - "aoss:ListSecurityPolicies": {}, - "aoss:ListTagsForResource": {}, - "aoss:ListVpcEndpoints": {}, - "aoss:TagResource": {}, - "aoss:UntagResource": {}, - "aoss:UpdateAccessPolicy": {}, - "aoss:UpdateAccountSettings": {}, - "aoss:UpdateLifecyclePolicy": {}, - "aoss:UpdateSecurityConfig": {}, - "aoss:UpdateSecurityPolicy": {}, - "aoss:UpdateVpcEndpoint": {}, - "app-integrations:ListApplicationAssociations": {}, - "app-integrations:ListApplications": {}, - "app-integrations:ListDataIntegrationAssociations": {}, - "app-integrations:ListDataIntegrations": {}, - "app-integrations:ListEventIntegrationAssociations": {}, - "app-integrations:ListEventIntegrations": {}, - "appconfig:CreateApplication": {}, - "appconfig:CreateDeploymentStrategy": {}, - "appconfig:CreateExtension": {}, - "appconfig:CreateExtensionAssociation": {}, - "appconfig:ListApplications": {}, - "appconfig:ListDeploymentStrategies": {}, - "appconfig:ListExtensionAssociations": {}, - "appconfig:ListExtensions": {}, - "appfabric:ListAppBundles": {}, - "appflow:CreateConnectorProfile": {}, - "appflow:CreateFlow": {}, - "appflow:DescribeConnectorProfiles": {}, - "appflow:DescribeConnectors": {}, - "appflow:DescribeFlows": {}, - "appflow:RegisterConnector": {}, - "application-autoscaling:DescribeScalableTargets": {}, - "application-autoscaling:DescribeScalingActivities": {}, - "application-autoscaling:DescribeScalingPolicies": {}, - "application-autoscaling:DescribeScheduledActions": {}, - "application-cost-profiler:DeleteReportDefinition": {}, - "application-cost-profiler:GetReportDefinition": {}, - "application-cost-profiler:ImportApplicationUsage": {}, - "application-cost-profiler:ListReportDefinitions": {}, - "application-cost-profiler:PutReportDefinition": {}, - "application-cost-profiler:UpdateReportDefinition": {}, - "application-transformation:GetContainerization": {}, - "application-transformation:GetDeployment": {}, - "application-transformation:GetGroupingAssessment": {}, - "application-transformation:GetPortingCompatibilityAssessment": {}, - "application-transformation:GetPortingRecommendationAssessment": {}, - "application-transformation:GetRuntimeAssessment": {}, - "application-transformation:PutLogData": {}, - "application-transformation:PutMetricData": {}, - "application-transformation:StartContainerization": {}, - "application-transformation:StartDeployment": {}, - "application-transformation:StartGroupingAssessment": {}, - "application-transformation:StartPortingCompatibilityAssessment": {}, - "application-transformation:StartPortingRecommendationAssessment": {}, - "application-transformation:StartRuntimeAssessment": {}, - "applicationinsights:AddWorkload": {}, - "applicationinsights:CreateApplication": {}, - "applicationinsights:CreateComponent": {}, - "applicationinsights:CreateLogPattern": {}, - "applicationinsights:DeleteApplication": {}, - "applicationinsights:DeleteComponent": {}, - "applicationinsights:DeleteLogPattern": {}, - "applicationinsights:DescribeApplication": {}, - "applicationinsights:DescribeComponent": {}, - "applicationinsights:DescribeComponentConfiguration": {}, - "applicationinsights:DescribeComponentConfigurationRecommendation": {}, - "applicationinsights:DescribeLogPattern": {}, - "applicationinsights:DescribeObservation": {}, - "applicationinsights:DescribeProblem": {}, - "applicationinsights:DescribeProblemObservations": {}, - "applicationinsights:DescribeWorkload": {}, - "applicationinsights:Link": {}, - "applicationinsights:ListApplications": {}, - "applicationinsights:ListComponents": {}, - "applicationinsights:ListConfigurationHistory": {}, - "applicationinsights:ListLogPatternSets": {}, - "applicationinsights:ListLogPatterns": {}, - "applicationinsights:ListProblems": {}, - "applicationinsights:ListTagsForResource": {}, - "applicationinsights:ListWorkloads": {}, - "applicationinsights:RemoveWorkload": {}, - "applicationinsights:TagResource": {}, - "applicationinsights:UntagResource": {}, - "applicationinsights:UpdateApplication": {}, - "applicationinsights:UpdateComponent": {}, - "applicationinsights:UpdateComponentConfiguration": {}, - "applicationinsights:UpdateLogPattern": {}, - "applicationinsights:UpdateProblem": {}, - "applicationinsights:UpdateWorkload": {}, - "appmesh-preview:ListMeshes": {}, - "appmesh:ListMeshes": {}, - "apprunner:ListAutoScalingConfigurations": {}, - "apprunner:ListConnections": {}, - "apprunner:ListObservabilityConfigurations": {}, - "apprunner:ListServices": {}, - "apprunner:ListVpcConnectors": {}, - "apprunner:ListVpcIngressConnections": {}, - "appstream:CreateAppBlock": {}, - "appstream:CreateDirectoryConfig": {}, - "appstream:CreateUsageReportSubscription": {}, - "appstream:CreateUser": {}, - "appstream:DeleteDirectoryConfig": {}, - "appstream:DeleteUsageReportSubscription": {}, - "appstream:DeleteUser": {}, - "appstream:DescribeDirectoryConfigs": {}, - "appstream:DescribeUsageReportSubscriptions": {}, - "appstream:DescribeUsers": {}, - "appstream:DisableUser": {}, - "appstream:EnableUser": {}, - "appstream:ExpireSession": {}, - "appstream:ListTagsForResource": {}, - "appstream:UpdateDirectoryConfig": {}, - "appsync:CreateApiCache": {}, - "appsync:CreateApiKey": {}, - "appsync:CreateDataSource": {}, - "appsync:CreateDomainName": {}, - "appsync:CreateFunction": {}, - "appsync:CreateGraphqlApi": {}, - "appsync:CreateResolver": {}, - "appsync:CreateType": {}, - "appsync:DeleteApiCache": {}, - "appsync:DeleteApiKey": {}, - "appsync:DeleteDataSource": {}, - "appsync:DeleteFunction": {}, - "appsync:DeleteResolver": {}, - "appsync:DeleteResourcePolicy": {}, - "appsync:DeleteType": {}, - "appsync:EvaluateCode": {}, - "appsync:EvaluateMappingTemplate": {}, - "appsync:FlushApiCache": {}, - "appsync:GetApiCache": {}, - "appsync:GetDataSource": {}, - "appsync:GetDataSourceIntrospection": {}, - "appsync:GetFunction": {}, - "appsync:GetGraphqlApiEnvironmentVariables": {}, - "appsync:GetIntrospectionSchema": {}, - "appsync:GetResolver": {}, - "appsync:GetResourcePolicy": {}, - "appsync:GetSchemaCreationStatus": {}, - "appsync:GetType": {}, - "appsync:ListApiKeys": {}, - "appsync:ListDataSources": {}, - "appsync:ListDomainNames": {}, - "appsync:ListFunctions": {}, - "appsync:ListGraphqlApis": {}, - "appsync:ListResolvers": {}, - "appsync:ListResolversByFunction": {}, - "appsync:ListSourceApiAssociations": {}, - "appsync:ListTypes": {}, - "appsync:ListTypesByAssociation": {}, - "appsync:PutGraphqlApiEnvironmentVariables": {}, - "appsync:PutResourcePolicy": {}, - "appsync:SetWebACL": {}, - "appsync:StartDataSourceIntrospection": {}, - "appsync:StartSchemaCreation": {}, - "appsync:UpdateApiCache": {}, - "appsync:UpdateApiKey": {}, - "appsync:UpdateDataSource": {}, - "appsync:UpdateFunction": {}, - "appsync:UpdateResolver": {}, - "appsync:UpdateType": {}, - "aps:CreateWorkspace": {}, - "aps:GetDefaultScraperConfiguration": {}, - "aps:ListScrapers": {}, - "aps:ListWorkspaces": {}, - "arc-zonal-shift:ListAutoshifts": {}, - "arc-zonal-shift:ListManagedResources": {}, - "arc-zonal-shift:ListZonalShifts": {}, - "arsenal:RegisterOnPremisesAgent": {}, - "artifact:GetAccountSettings": {}, - "artifact:ListReports": {}, - "artifact:PutAccountSettings": {}, - "athena:GetCatalogs": {}, - "athena:GetExecutionEngine": {}, - "athena:GetExecutionEngines": {}, - "athena:GetNamespace": {}, - "athena:GetNamespaces": {}, - "athena:GetQueryExecutions": {}, - "athena:GetTable": {}, - "athena:GetTables": {}, - "athena:ListApplicationDPUSizes": {}, - "athena:ListCapacityReservations": {}, - "athena:ListDataCatalogs": {}, - "athena:ListEngineVersions": {}, - "athena:ListExecutors": {}, - "athena:ListWorkGroups": {}, - "athena:RunQuery": {}, - "auditmanager:CreateAssessment": {}, - "auditmanager:CreateAssessmentFramework": {}, - "auditmanager:CreateControl": {}, - "auditmanager:DeleteAssessmentFrameworkShare": {}, - "auditmanager:DeregisterAccount": {}, - "auditmanager:DeregisterOrganizationAdminAccount": {}, - "auditmanager:GetAccountStatus": {}, - "auditmanager:GetDelegations": {}, - "auditmanager:GetEvidenceFileUploadUrl": {}, - "auditmanager:GetInsights": {}, - "auditmanager:GetInsightsByAssessment": {}, - "auditmanager:GetOrganizationAdminAccount": {}, - "auditmanager:GetServicesInScope": {}, - "auditmanager:GetSettings": {}, - "auditmanager:ListAssessmentControlInsightsByControlDomain": {}, - "auditmanager:ListAssessmentFrameworkShareRequests": {}, - "auditmanager:ListAssessmentFrameworks": {}, - "auditmanager:ListAssessmentReports": {}, - "auditmanager:ListAssessments": {}, - "auditmanager:ListControlDomainInsights": {}, - "auditmanager:ListControlDomainInsightsByAssessment": {}, - "auditmanager:ListControlInsightsByControlDomain": {}, - "auditmanager:ListControls": {}, - "auditmanager:ListKeywordsForDataSource": {}, - "auditmanager:ListNotifications": {}, - "auditmanager:RegisterAccount": {}, - "auditmanager:RegisterOrganizationAdminAccount": {}, - "auditmanager:UpdateAssessmentFrameworkShare": {}, - "auditmanager:UpdateSettings": {}, - "auditmanager:ValidateAssessmentReportIntegrity": {}, - "autoscaling-plans:CreateScalingPlan": {}, - "autoscaling-plans:DeleteScalingPlan": {}, - "autoscaling-plans:DescribeScalingPlanResources": {}, - "autoscaling-plans:DescribeScalingPlans": {}, - "autoscaling-plans:GetScalingPlanResourceForecastData": {}, - "autoscaling-plans:UpdateScalingPlan": {}, - "autoscaling:DescribeAccountLimits": {}, - "autoscaling:DescribeAdjustmentTypes": {}, - "autoscaling:DescribeAutoScalingGroups": {}, - "autoscaling:DescribeAutoScalingInstances": {}, - "autoscaling:DescribeAutoScalingNotificationTypes": {}, - "autoscaling:DescribeInstanceRefreshes": {}, - "autoscaling:DescribeLaunchConfigurations": {}, - "autoscaling:DescribeLifecycleHookTypes": {}, - "autoscaling:DescribeLifecycleHooks": {}, - "autoscaling:DescribeLoadBalancerTargetGroups": {}, - "autoscaling:DescribeLoadBalancers": {}, - "autoscaling:DescribeMetricCollectionTypes": {}, - "autoscaling:DescribeNotificationConfigurations": {}, - "autoscaling:DescribePolicies": {}, - "autoscaling:DescribeScalingActivities": {}, - "autoscaling:DescribeScalingProcessTypes": {}, - "autoscaling:DescribeScheduledActions": {}, - "autoscaling:DescribeTags": {}, - "autoscaling:DescribeTerminationPolicyTypes": {}, - "autoscaling:DescribeTrafficSources": {}, - "autoscaling:DescribeWarmPool": {}, - "autoscaling:GetPredictiveScalingForecast": {}, + "a2c:GetContainerizationJobDetails": {}, + "a2c:GetDeploymentJobDetails": {}, + "a2c:StartContainerizationJob": {}, + "a2c:StartDeploymentJob": {}, + "a4b:ApproveSkill": {}, + "a4b:AssociateSkillWithUsers": {}, + "a4b:CompleteRegistration": {}, + "a4b:CreateAddressBook": {}, + "a4b:CreateBusinessReportSchedule": {}, + "a4b:CreateConferenceProvider": {}, + "a4b:CreateContact": {}, + "a4b:CreateGatewayGroup": {}, + "a4b:CreateNetworkProfile": {}, + "a4b:CreateProfile": {}, + "a4b:CreateSkillGroup": {}, + "a4b:GetConferencePreference": {}, + "a4b:GetInvitationConfiguration": {}, + "a4b:ListBusinessReportSchedules": {}, + "a4b:ListConferenceProviders": {}, + "a4b:ListGatewayGroups": {}, + "a4b:ListSkills": {}, + "a4b:ListSkillsStoreCategories": {}, + "a4b:ListSkillsStoreSkillsByCategory": {}, + "a4b:PutConferencePreference": {}, + "a4b:PutDeviceSetupEvents": {}, + "a4b:PutInvitationConfiguration": {}, + "a4b:RegisterAVSDevice": {}, + "a4b:RegisterDevice": {}, + "a4b:RejectSkill": {}, + "a4b:ResolveRoom": {}, + "a4b:SearchAddressBooks": {}, + "a4b:SearchContacts": {}, + "a4b:SearchDevices": {}, + "a4b:SearchNetworkProfiles": {}, + "a4b:SearchProfiles": {}, + "a4b:SearchRooms": {}, + "a4b:SearchSkillGroups": {}, + "a4b:SearchUsers": {}, + "a4b:SendAnnouncement": {}, + "a4b:StartDeviceSync": {}, + "access-analyzer:CancelPolicyGeneration": {}, + "access-analyzer:CheckAccessNotGranted": {}, + "access-analyzer:CheckNoNewAccess": {}, + "access-analyzer:GetGeneratedPolicy": {}, + "access-analyzer:ListAnalyzers": {}, + "access-analyzer:ListPolicyGenerations": {}, + "access-analyzer:StartPolicyGeneration": {}, + "access-analyzer:ValidatePolicy": {}, + "acm-pca:CreateCertificateAuthority": {}, + "acm-pca:ListCertificateAuthorities": {}, + "acm:GetAccountConfiguration": {}, + "acm:ListCertificates": {}, + "acm:PutAccountConfiguration": {}, + "acm:RequestCertificate": {}, + "activate:CreateForm": {}, + "activate:GetAccountContact": {}, + "activate:GetContentInfo": {}, + "activate:GetCosts": {}, + "activate:GetCredits": {}, + "activate:GetMemberInfo": {}, + "activate:GetProgram": {}, + "activate:PutMemberInfo": {}, + "airflow:ListEnvironments": {}, + "amplify:ListApps": {}, + "amplifybackend:ListS3Buckets": {}, + "amplifyuibuilder:CreateComponent": {}, + "amplifyuibuilder:CreateForm": {}, + "amplifyuibuilder:CreateTheme": {}, + "amplifyuibuilder:ExchangeCodeForToken": {}, + "amplifyuibuilder:ExportComponents": {}, + "amplifyuibuilder:ExportForms": {}, + "amplifyuibuilder:ExportThemes": {}, + "amplifyuibuilder:GetMetadata": {}, + "amplifyuibuilder:ListCodegenJobs": {}, + "amplifyuibuilder:ListComponents": {}, + "amplifyuibuilder:ListForms": {}, + "amplifyuibuilder:ListThemes": {}, + "amplifyuibuilder:PutMetadataFlag": {}, + "amplifyuibuilder:RefreshToken": {}, + "amplifyuibuilder:ResetMetadataFlag": {}, + "amplifyuibuilder:StartCodegenJob": {}, + "aoss:BatchGetCollection": {}, + "aoss:BatchGetEffectiveLifecyclePolicy": {}, + "aoss:BatchGetLifecyclePolicy": {}, + "aoss:BatchGetVpcEndpoint": {}, + "aoss:CreateAccessPolicy": {}, + "aoss:CreateCollection": {}, + "aoss:CreateLifecyclePolicy": {}, + "aoss:CreateSecurityConfig": {}, + "aoss:CreateSecurityPolicy": {}, + "aoss:CreateVpcEndpoint": {}, + "aoss:DeleteAccessPolicy": {}, + "aoss:DeleteLifecyclePolicy": {}, + "aoss:DeleteSecurityConfig": {}, + "aoss:DeleteSecurityPolicy": {}, + "aoss:DeleteVpcEndpoint": {}, + "aoss:GetAccessPolicy": {}, + "aoss:GetAccountSettings": {}, + "aoss:GetPoliciesStats": {}, + "aoss:GetSecurityConfig": {}, + "aoss:GetSecurityPolicy": {}, + "aoss:ListAccessPolicies": {}, + "aoss:ListCollections": {}, + "aoss:ListLifecyclePolicies": {}, + "aoss:ListSecurityConfigs": {}, + "aoss:ListSecurityPolicies": {}, + "aoss:ListTagsForResource": {}, + "aoss:ListVpcEndpoints": {}, + "aoss:TagResource": {}, + "aoss:UntagResource": {}, + "aoss:UpdateAccessPolicy": {}, + "aoss:UpdateAccountSettings": {}, + "aoss:UpdateLifecyclePolicy": {}, + "aoss:UpdateSecurityConfig": {}, + "aoss:UpdateSecurityPolicy": {}, + "aoss:UpdateVpcEndpoint": {}, + "app-integrations:ListApplicationAssociations": {}, + "app-integrations:ListApplications": {}, + "app-integrations:ListDataIntegrationAssociations": {}, + "app-integrations:ListDataIntegrations": {}, + "app-integrations:ListEventIntegrationAssociations": {}, + "app-integrations:ListEventIntegrations": {}, + "appconfig:CreateApplication": {}, + "appconfig:CreateDeploymentStrategy": {}, + "appconfig:CreateExtension": {}, + "appconfig:CreateExtensionAssociation": {}, + "appconfig:ListApplications": {}, + "appconfig:ListDeploymentStrategies": {}, + "appconfig:ListExtensionAssociations": {}, + "appconfig:ListExtensions": {}, + "appfabric:ListAppBundles": {}, + "appflow:CreateConnectorProfile": {}, + "appflow:CreateFlow": {}, + "appflow:DescribeConnectorProfiles": {}, + "appflow:DescribeConnectors": {}, + "appflow:DescribeFlows": {}, + "appflow:RegisterConnector": {}, + "application-autoscaling:DescribeScalableTargets": {}, + "application-autoscaling:DescribeScalingActivities": {}, + "application-autoscaling:DescribeScalingPolicies": {}, + "application-autoscaling:DescribeScheduledActions": {}, + "application-cost-profiler:DeleteReportDefinition": {}, + "application-cost-profiler:GetReportDefinition": {}, + "application-cost-profiler:ImportApplicationUsage": {}, + "application-cost-profiler:ListReportDefinitions": {}, + "application-cost-profiler:PutReportDefinition": {}, + "application-cost-profiler:UpdateReportDefinition": {}, + "application-transformation:GetContainerization": {}, + "application-transformation:GetDeployment": {}, + "application-transformation:GetGroupingAssessment": {}, + "application-transformation:GetPortingCompatibilityAssessment": {}, + "application-transformation:GetPortingRecommendationAssessment": {}, + "application-transformation:GetRuntimeAssessment": {}, + "application-transformation:PutLogData": {}, + "application-transformation:PutMetricData": {}, + "application-transformation:StartContainerization": {}, + "application-transformation:StartDeployment": {}, + "application-transformation:StartGroupingAssessment": {}, + "application-transformation:StartPortingCompatibilityAssessment": {}, + "application-transformation:StartPortingRecommendationAssessment": {}, + "application-transformation:StartRuntimeAssessment": {}, + "applicationinsights:AddWorkload": {}, + "applicationinsights:CreateApplication": {}, + "applicationinsights:CreateComponent": {}, + "applicationinsights:CreateLogPattern": {}, + "applicationinsights:DeleteApplication": {}, + "applicationinsights:DeleteComponent": {}, + "applicationinsights:DeleteLogPattern": {}, + "applicationinsights:DescribeApplication": {}, + "applicationinsights:DescribeComponent": {}, + "applicationinsights:DescribeComponentConfiguration": {}, + "applicationinsights:DescribeComponentConfigurationRecommendation": {}, + "applicationinsights:DescribeLogPattern": {}, + "applicationinsights:DescribeObservation": {}, + "applicationinsights:DescribeProblem": {}, + "applicationinsights:DescribeProblemObservations": {}, + "applicationinsights:DescribeWorkload": {}, + "applicationinsights:Link": {}, + "applicationinsights:ListApplications": {}, + "applicationinsights:ListComponents": {}, + "applicationinsights:ListConfigurationHistory": {}, + "applicationinsights:ListLogPatternSets": {}, + "applicationinsights:ListLogPatterns": {}, + "applicationinsights:ListProblems": {}, + "applicationinsights:ListTagsForResource": {}, + "applicationinsights:ListWorkloads": {}, + "applicationinsights:RemoveWorkload": {}, + "applicationinsights:TagResource": {}, + "applicationinsights:UntagResource": {}, + "applicationinsights:UpdateApplication": {}, + "applicationinsights:UpdateComponent": {}, + "applicationinsights:UpdateComponentConfiguration": {}, + "applicationinsights:UpdateLogPattern": {}, + "applicationinsights:UpdateProblem": {}, + "applicationinsights:UpdateWorkload": {}, + "appmesh-preview:ListMeshes": {}, + "appmesh:ListMeshes": {}, + "apprunner:ListAutoScalingConfigurations": {}, + "apprunner:ListConnections": {}, + "apprunner:ListObservabilityConfigurations": {}, + "apprunner:ListServices": {}, + "apprunner:ListVpcConnectors": {}, + "apprunner:ListVpcIngressConnections": {}, + "appstream:CreateAppBlock": {}, + "appstream:CreateDirectoryConfig": {}, + "appstream:CreateUsageReportSubscription": {}, + "appstream:CreateUser": {}, + "appstream:DeleteDirectoryConfig": {}, + "appstream:DeleteUsageReportSubscription": {}, + "appstream:DeleteUser": {}, + "appstream:DescribeDirectoryConfigs": {}, + "appstream:DescribeUsageReportSubscriptions": {}, + "appstream:DescribeUsers": {}, + "appstream:DisableUser": {}, + "appstream:EnableUser": {}, + "appstream:ExpireSession": {}, + "appstream:ListTagsForResource": {}, + "appstream:UpdateDirectoryConfig": {}, + "appsync:CreateApiCache": {}, + "appsync:CreateApiKey": {}, + "appsync:CreateDataSource": {}, + "appsync:CreateDomainName": {}, + "appsync:CreateFunction": {}, + "appsync:CreateGraphqlApi": {}, + "appsync:CreateResolver": {}, + "appsync:CreateType": {}, + "appsync:DeleteApiCache": {}, + "appsync:DeleteApiKey": {}, + "appsync:DeleteDataSource": {}, + "appsync:DeleteFunction": {}, + "appsync:DeleteResolver": {}, + "appsync:DeleteResourcePolicy": {}, + "appsync:DeleteType": {}, + "appsync:EvaluateCode": {}, + "appsync:EvaluateMappingTemplate": {}, + "appsync:FlushApiCache": {}, + "appsync:GetApiCache": {}, + "appsync:GetDataSource": {}, + "appsync:GetDataSourceIntrospection": {}, + "appsync:GetFunction": {}, + "appsync:GetGraphqlApiEnvironmentVariables": {}, + "appsync:GetIntrospectionSchema": {}, + "appsync:GetResolver": {}, + "appsync:GetResourcePolicy": {}, + "appsync:GetSchemaCreationStatus": {}, + "appsync:GetType": {}, + "appsync:ListApiKeys": {}, + "appsync:ListDataSources": {}, + "appsync:ListDomainNames": {}, + "appsync:ListFunctions": {}, + "appsync:ListGraphqlApis": {}, + "appsync:ListResolvers": {}, + "appsync:ListResolversByFunction": {}, + "appsync:ListSourceApiAssociations": {}, + "appsync:ListTypes": {}, + "appsync:ListTypesByAssociation": {}, + "appsync:PutGraphqlApiEnvironmentVariables": {}, + "appsync:PutResourcePolicy": {}, + "appsync:SetWebACL": {}, + "appsync:StartDataSourceIntrospection": {}, + "appsync:StartSchemaCreation": {}, + "appsync:UpdateApiCache": {}, + "appsync:UpdateApiKey": {}, + "appsync:UpdateDataSource": {}, + "appsync:UpdateFunction": {}, + "appsync:UpdateResolver": {}, + "appsync:UpdateType": {}, + "aps:CreateWorkspace": {}, + "aps:GetDefaultScraperConfiguration": {}, + "aps:ListScrapers": {}, + "aps:ListWorkspaces": {}, + "arc-zonal-shift:ListAutoshifts": {}, + "arc-zonal-shift:ListManagedResources": {}, + "arc-zonal-shift:ListZonalShifts": {}, + "arsenal:RegisterOnPremisesAgent": {}, + "artifact:GetAccountSettings": {}, + "artifact:ListReports": {}, + "artifact:PutAccountSettings": {}, + "athena:GetCatalogs": {}, + "athena:GetExecutionEngine": {}, + "athena:GetExecutionEngines": {}, + "athena:GetNamespace": {}, + "athena:GetNamespaces": {}, + "athena:GetQueryExecutions": {}, + "athena:GetTable": {}, + "athena:GetTables": {}, + "athena:ListApplicationDPUSizes": {}, + "athena:ListCapacityReservations": {}, + "athena:ListDataCatalogs": {}, + "athena:ListEngineVersions": {}, + "athena:ListExecutors": {}, + "athena:ListWorkGroups": {}, + "athena:RunQuery": {}, + "auditmanager:CreateAssessment": {}, + "auditmanager:CreateAssessmentFramework": {}, + "auditmanager:CreateControl": {}, + "auditmanager:DeleteAssessmentFrameworkShare": {}, + "auditmanager:DeregisterAccount": {}, + "auditmanager:DeregisterOrganizationAdminAccount": {}, + "auditmanager:GetAccountStatus": {}, + "auditmanager:GetDelegations": {}, + "auditmanager:GetEvidenceFileUploadUrl": {}, + "auditmanager:GetInsights": {}, + "auditmanager:GetInsightsByAssessment": {}, + "auditmanager:GetOrganizationAdminAccount": {}, + "auditmanager:GetServicesInScope": {}, + "auditmanager:GetSettings": {}, + "auditmanager:ListAssessmentControlInsightsByControlDomain": {}, + "auditmanager:ListAssessmentFrameworkShareRequests": {}, + "auditmanager:ListAssessmentFrameworks": {}, + "auditmanager:ListAssessmentReports": {}, + "auditmanager:ListAssessments": {}, + "auditmanager:ListControlDomainInsights": {}, + "auditmanager:ListControlDomainInsightsByAssessment": {}, + "auditmanager:ListControlInsightsByControlDomain": {}, + "auditmanager:ListControls": {}, + "auditmanager:ListKeywordsForDataSource": {}, + "auditmanager:ListNotifications": {}, + "auditmanager:RegisterAccount": {}, + "auditmanager:RegisterOrganizationAdminAccount": {}, + "auditmanager:UpdateAssessmentFrameworkShare": {}, + "auditmanager:UpdateSettings": {}, + "auditmanager:ValidateAssessmentReportIntegrity": {}, + "autoscaling-plans:CreateScalingPlan": {}, + "autoscaling-plans:DeleteScalingPlan": {}, + "autoscaling-plans:DescribeScalingPlanResources": {}, + "autoscaling-plans:DescribeScalingPlans": {}, + "autoscaling-plans:GetScalingPlanResourceForecastData": {}, + "autoscaling-plans:UpdateScalingPlan": {}, + "autoscaling:DescribeAccountLimits": {}, + "autoscaling:DescribeAdjustmentTypes": {}, + "autoscaling:DescribeAutoScalingGroups": {}, + "autoscaling:DescribeAutoScalingInstances": {}, + "autoscaling:DescribeAutoScalingNotificationTypes": {}, + "autoscaling:DescribeInstanceRefreshes": {}, + "autoscaling:DescribeLaunchConfigurations": {}, + "autoscaling:DescribeLifecycleHookTypes": {}, + "autoscaling:DescribeLifecycleHooks": {}, + "autoscaling:DescribeLoadBalancerTargetGroups": {}, + "autoscaling:DescribeLoadBalancers": {}, + "autoscaling:DescribeMetricCollectionTypes": {}, + "autoscaling:DescribeNotificationConfigurations": {}, + "autoscaling:DescribePolicies": {}, + "autoscaling:DescribeScalingActivities": {}, + "autoscaling:DescribeScalingProcessTypes": {}, + "autoscaling:DescribeScheduledActions": {}, + "autoscaling:DescribeTags": {}, + "autoscaling:DescribeTerminationPolicyTypes": {}, + "autoscaling:DescribeTrafficSources": {}, + "autoscaling:DescribeWarmPool": {}, + "autoscaling:GetPredictiveScalingForecast": {}, "aws-marketplace-management:GetAdditionalSellerNotificationRecipients": {}, - "aws-marketplace-management:GetBankAccountVerificationDetails": {}, - "aws-marketplace-management:GetSecondaryUserVerificationDetails": {}, - "aws-marketplace-management:GetSellerVerificationDetails": {}, + "aws-marketplace-management:GetBankAccountVerificationDetails": {}, + "aws-marketplace-management:GetSecondaryUserVerificationDetails": {}, + "aws-marketplace-management:GetSellerVerificationDetails": {}, "aws-marketplace-management:PutAdditionalSellerNotificationRecipients": {}, - "aws-marketplace-management:PutBankAccountVerificationDetails": {}, - "aws-marketplace-management:PutSecondaryUserVerificationDetails": {}, - "aws-marketplace-management:PutSellerVerificationDetails": {}, - "aws-marketplace-management:uploadFiles": {}, - "aws-marketplace-management:viewMarketing": {}, - "aws-marketplace-management:viewReports": {}, - "aws-marketplace-management:viewSettings": {}, - "aws-marketplace-management:viewSupport": {}, - "aws-marketplace:AcceptAgreementApprovalRequest": {}, - "aws-marketplace:AcceptAgreementRequest": {}, - "aws-marketplace:AssociateProductsWithPrivateMarketplace": {}, - "aws-marketplace:BatchMeterUsage": {}, - "aws-marketplace:CancelAgreement": {}, - "aws-marketplace:CancelAgreementRequest": {}, - "aws-marketplace:CompleteTask": {}, - "aws-marketplace:CreateAgreementRequest": {}, - "aws-marketplace:CreatePrivateMarketplaceRequests": {}, - "aws-marketplace:DescribeAgreement": {}, - "aws-marketplace:DescribeBuilds": {}, - "aws-marketplace:DescribePrivateMarketplaceRequests": {}, - "aws-marketplace:DescribeProcurementSystemConfiguration": {}, - "aws-marketplace:DescribeTask": {}, - "aws-marketplace:DisassociateProductsFromPrivateMarketplace": {}, - "aws-marketplace:GetAgreementApprovalRequest": {}, - "aws-marketplace:GetAgreementRequest": {}, - "aws-marketplace:GetAgreementTerms": {}, - "aws-marketplace:ListAgreementApprovalRequests": {}, - "aws-marketplace:ListAgreementRequests": {}, - "aws-marketplace:ListBuilds": {}, - "aws-marketplace:ListChangeSets": {}, - "aws-marketplace:ListEntities": {}, - "aws-marketplace:ListEntitlementDetails": {}, - "aws-marketplace:ListPrivateListings": {}, - "aws-marketplace:ListPrivateMarketplaceRequests": {}, - "aws-marketplace:ListTasks": {}, - "aws-marketplace:MeterUsage": {}, - "aws-marketplace:PutProcurementSystemConfiguration": {}, - "aws-marketplace:RegisterUsage": {}, - "aws-marketplace:RejectAgreementApprovalRequest": {}, - "aws-marketplace:ResolveCustomer": {}, - "aws-marketplace:SearchAgreements": {}, - "aws-marketplace:StartBuild": {}, - "aws-marketplace:Subscribe": {}, - "aws-marketplace:Unsubscribe": {}, - "aws-marketplace:UpdateAgreementApprovalRequest": {}, - "aws-marketplace:UpdateTask": {}, - "aws-marketplace:ViewSubscriptions": {}, - "aws-portal:GetConsoleActionSetEnforced": {}, - "aws-portal:ModifyAccount": {}, - "aws-portal:ModifyBilling": {}, - "aws-portal:ModifyPaymentMethods": {}, - "aws-portal:UpdateConsoleActionSetEnforced": {}, - "aws-portal:ViewAccount": {}, - "aws-portal:ViewBilling": {}, - "aws-portal:ViewPaymentMethods": {}, - "aws-portal:ViewUsage": {}, - "awsconnector:GetConnectorHealth": {}, - "awsconnector:RegisterConnector": {}, - "awsconnector:ValidateConnectorId": {}, - "b2bi:CreateProfile": {}, - "b2bi:CreateTransformer": {}, - "b2bi:ListCapabilities": {}, - "b2bi:ListPartnerships": {}, - "b2bi:ListProfiles": {}, - "b2bi:ListTransformers": {}, - "backup-gateway:CreateGateway": {}, - "backup-gateway:ImportHypervisorConfiguration": {}, - "backup-gateway:ListGateways": {}, - "backup-gateway:ListHypervisors": {}, - "backup-gateway:ListVirtualMachines": {}, - "backup-storage:CommitBackupJob": {}, - "backup-storage:DeleteObjects": {}, - "backup-storage:DescribeBackupJob": {}, - "backup-storage:GetBaseBackup": {}, - "backup-storage:GetChunk": {}, - "backup-storage:GetIncrementalBaseBackup": {}, - "backup-storage:GetObjectMetadata": {}, - "backup-storage:ListChunks": {}, - "backup-storage:ListObjects": {}, - "backup-storage:MountCapsule": {}, - "backup-storage:NotifyObjectComplete": {}, - "backup-storage:PutChunk": {}, - "backup-storage:PutObject": {}, - "backup-storage:StartObject": {}, - "backup-storage:UpdateObjectComplete": {}, - "backup:DescribeBackupJob": {}, - "backup:DescribeCopyJob": {}, - "backup:DescribeGlobalSettings": {}, - "backup:DescribeProtectedResource": {}, - "backup:DescribeRegionSettings": {}, - "backup:DescribeReportJob": {}, - "backup:DescribeRestoreJob": {}, - "backup:ExportBackupPlanTemplate": {}, - "backup:GetBackupPlanFromJSON": {}, - "backup:GetBackupPlanFromTemplate": {}, - "backup:GetRestoreJobMetadata": {}, - "backup:GetRestoreTestingInferredMetadata": {}, - "backup:GetSupportedResourceTypes": {}, - "backup:ListBackupJobSummaries": {}, - "backup:ListBackupJobs": {}, - "backup:ListBackupPlanTemplates": {}, - "backup:ListBackupPlans": {}, - "backup:ListBackupVaults": {}, - "backup:ListCopyJobSummaries": {}, - "backup:ListCopyJobs": {}, - "backup:ListFrameworks": {}, - "backup:ListLegalHolds": {}, - "backup:ListProtectedResources": {}, - "backup:ListRecoveryPointsByResource": {}, - "backup:ListReportJobs": {}, - "backup:ListReportPlans": {}, - "backup:ListRestoreJobSummaries": {}, - "backup:ListRestoreJobs": {}, - "backup:ListRestoreJobsByProtectedResource": {}, - "backup:ListRestoreTestingPlans": {}, - "backup:PutRestoreValidationResult": {}, - "backup:StopBackupJob": {}, - "backup:UpdateGlobalSettings": {}, - "backup:UpdateRegionSettings": {}, - "batch:DescribeComputeEnvironments": {}, - "batch:DescribeJobDefinitions": {}, - "batch:DescribeJobQueues": {}, - "batch:DescribeJobs": {}, - "batch:DescribeSchedulingPolicies": {}, - "batch:ListJobs": {}, - "batch:ListSchedulingPolicies": {}, - "bcm-data-exports:ListExports": {}, - "bcm-data-exports:ListTables": {}, - "bedrock:AssociateThirdPartyKnowledgeBase": {}, - "bedrock:CreateAgent": {}, - "bedrock:CreateFoundationModelAgreement": {}, - "bedrock:CreateGuardrail": {}, - "bedrock:CreateKnowledgeBase": {}, - "bedrock:DeleteFoundationModelAgreement": {}, - "bedrock:DeleteModelInvocationLoggingConfiguration": {}, - "bedrock:GetFoundationModelAvailability": {}, - "bedrock:GetModelInvocationLoggingConfiguration": {}, - "bedrock:GetUseCaseForModelAccess": {}, - "bedrock:ListAgents": {}, - "bedrock:ListCustomModels": {}, - "bedrock:ListFoundationModelAgreementOffers": {}, - "bedrock:ListFoundationModels": {}, - "bedrock:ListKnowledgeBases": {}, - "bedrock:ListModelCustomizationJobs": {}, - "bedrock:ListModelEvaluationJobs": {}, - "bedrock:ListModelInvocationJobs": {}, - "bedrock:ListProvisionedModelThroughputs": {}, - "bedrock:PutFoundationModelEntitlement": {}, - "bedrock:PutModelInvocationLoggingConfiguration": {}, - "bedrock:PutUseCaseForModelAccess": {}, - "bedrock:RetrieveAndGenerate": {}, - "billing:GetBillingData": {}, - "billing:GetBillingDetails": {}, - "billing:GetBillingNotifications": {}, - "billing:GetBillingPreferences": {}, - "billing:GetContractInformation": {}, - "billing:GetCredits": {}, - "billing:GetIAMAccessPreference": {}, - "billing:GetSellerOfRecord": {}, - "billing:ListBillingViews": {}, - "billing:PutContractInformation": {}, - "billing:RedeemCredits": {}, - "billing:UpdateBillingPreferences": {}, - "billing:UpdateIAMAccessPreference": {}, - "billingconductor:CreatePricingRule": {}, - "billingconductor:ListAccountAssociations": {}, - "billingconductor:ListBillingGroupCostReports": {}, - "billingconductor:ListBillingGroups": {}, - "billingconductor:ListCustomLineItems": {}, - "billingconductor:ListPricingPlans": {}, - "billingconductor:ListPricingRules": {}, - "braket:AcceptUserAgreement": {}, - "braket:AccessBraketFeature": {}, - "braket:CreateJob": {}, - "braket:CreateQuantumTask": {}, - "braket:GetDevice": {}, - "braket:GetServiceLinkedRoleStatus": {}, - "braket:GetUserAgreementStatus": {}, - "braket:SearchDevices": {}, - "braket:SearchJobs": {}, - "braket:SearchQuantumTasks": {}, - "budgets:DescribeBudgetActionsForAccount": {}, - "bugbust:CreateEvent": {}, - "bugbust:ListEvents": {}, - "cases:CreateDomain": {}, - "cases:ListDomains": {}, - "cases:ListTagsForResource": {}, - "ce:CreateAnomalyMonitor": {}, - "ce:CreateAnomalySubscription": {}, - "ce:CreateCostCategoryDefinition": {}, - "ce:CreateNotificationSubscription": {}, - "ce:CreateReport": {}, - "ce:DeleteNotificationSubscription": {}, - "ce:DeleteReport": {}, - "ce:DescribeNotificationSubscription": {}, - "ce:DescribeReport": {}, - "ce:GetApproximateUsageRecords": {}, - "ce:GetConsoleActionSetEnforced": {}, - "ce:GetCostAndUsage": {}, - "ce:GetCostAndUsageWithResources": {}, - "ce:GetCostCategories": {}, - "ce:GetCostForecast": {}, - "ce:GetDimensionValues": {}, - "ce:GetPreferences": {}, - "ce:GetReservationCoverage": {}, - "ce:GetReservationPurchaseRecommendation": {}, - "ce:GetReservationUtilization": {}, - "ce:GetRightsizingRecommendation": {}, - "ce:GetSavingsPlanPurchaseRecommendationDetails": {}, - "ce:GetSavingsPlansCoverage": {}, - "ce:GetSavingsPlansPurchaseRecommendation": {}, - "ce:GetSavingsPlansUtilization": {}, - "ce:GetSavingsPlansUtilizationDetails": {}, - "ce:GetTags": {}, - "ce:GetUsageForecast": {}, - "ce:ListCostAllocationTags": {}, - "ce:ListCostCategoryDefinitions": {}, - "ce:ListSavingsPlansPurchaseRecommendationGeneration": {}, - "ce:ProvideAnomalyFeedback": {}, - "ce:StartSavingsPlansPurchaseRecommendationGeneration": {}, - "ce:UpdateConsoleActionSetEnforced": {}, - "ce:UpdateCostAllocationTagsStatus": {}, - "ce:UpdateNotificationSubscription": {}, - "ce:UpdatePreferences": {}, - "ce:UpdateReport": {}, - "chatbot:CreateChimeWebhookConfiguration": {}, - "chatbot:CreateMicrosoftTeamsChannelConfiguration": {}, - "chatbot:CreateSlackChannelConfiguration": {}, - "chatbot:DeleteMicrosoftTeamsChannelConfiguration": {}, - "chatbot:DeleteMicrosoftTeamsConfiguredTeam": {}, - "chatbot:DeleteMicrosoftTeamsUserIdentity": {}, - "chatbot:DeleteSlackUserIdentity": {}, - "chatbot:DeleteSlackWorkspaceAuthorization": {}, - "chatbot:DescribeChimeWebhookConfigurations": {}, - "chatbot:DescribeSlackChannelConfigurations": {}, - "chatbot:DescribeSlackChannels": {}, - "chatbot:DescribeSlackUserIdentities": {}, - "chatbot:DescribeSlackWorkspaces": {}, - "chatbot:GetAccountPreferences": {}, - "chatbot:GetMicrosoftTeamsChannelConfiguration": {}, - "chatbot:GetMicrosoftTeamsOauthParameters": {}, - "chatbot:GetSlackOauthParameters": {}, - "chatbot:ListMicrosoftTeamsChannelConfigurations": {}, - "chatbot:ListMicrosoftTeamsConfiguredTeams": {}, - "chatbot:ListMicrosoftTeamsUserIdentities": {}, - "chatbot:RedeemMicrosoftTeamsOauthCode": {}, - "chatbot:RedeemSlackOauthCode": {}, - "chatbot:UpdateAccountPreferences": {}, - "chatbot:UpdateMicrosoftTeamsChannelConfiguration": {}, - "chime:AcceptDelegate": {}, - "chime:ActivateUsers": {}, - "chime:AddDomain": {}, - "chime:AddOrUpdateGroups": {}, - "chime:AssociatePhoneNumberWithUser": {}, - "chime:AssociatePhoneNumbersWithVoiceConnectorGroup": {}, - "chime:AssociateSigninDelegateGroupsWithAccount": {}, - "chime:AuthorizeDirectory": {}, - "chime:BatchCreateRoomMembership": {}, - "chime:BatchDeletePhoneNumber": {}, - "chime:BatchSuspendUser": {}, - "chime:BatchUnsuspendUser": {}, - "chime:BatchUpdatePhoneNumber": {}, - "chime:BatchUpdateUser": {}, - "chime:ConnectDirectory": {}, - "chime:CreateAccount": {}, - "chime:CreateApiKey": {}, - "chime:CreateAppInstance": {}, - "chime:CreateAppInstanceBot": {}, - "chime:CreateAppInstanceUser": {}, - "chime:CreateBot": {}, - "chime:CreateCDRBucket": {}, - "chime:CreateMediaCapturePipeline": {}, - "chime:CreateMediaConcatenationPipeline": {}, - "chime:CreateMediaInsightsPipelineConfiguration": {}, - "chime:CreateMediaLiveConnectorPipeline": {}, - "chime:CreateMediaPipelineKinesisVideoStreamPool": {}, - "chime:CreateMeeting": {}, - "chime:CreateMeetingWithAttendees": {}, - "chime:CreatePhoneNumberOrder": {}, - "chime:CreateRoom": {}, - "chime:CreateRoomMembership": {}, - "chime:CreateSipMediaApplication": {}, - "chime:CreateUser": {}, - "chime:CreateVoiceConnector": {}, - "chime:CreateVoiceProfile": {}, - "chime:CreateVoiceProfileDomain": {}, - "chime:DeleteAccount": {}, - "chime:DeleteAccountOpenIdConfig": {}, - "chime:DeleteApiKey": {}, - "chime:DeleteCDRBucket": {}, - "chime:DeleteDelegate": {}, - "chime:DeleteDomain": {}, - "chime:DeleteEventsConfiguration": {}, - "chime:DeleteGroups": {}, - "chime:DeletePhoneNumber": {}, - "chime:DeleteRoom": {}, - "chime:DeleteRoomMembership": {}, - "chime:DeleteSipRule": {}, - "chime:DeleteVoiceConnectorGroup": {}, - "chime:DisassociatePhoneNumberFromUser": {}, - "chime:DisassociatePhoneNumbersFromVoiceConnectorGroup": {}, - "chime:DisassociateSigninDelegateGroupsFromAccount": {}, - "chime:DisconnectDirectory": {}, - "chime:GetAccount": {}, - "chime:GetAccountResource": {}, - "chime:GetAccountSettings": {}, - "chime:GetAccountWithOpenIdConfig": {}, - "chime:GetBot": {}, - "chime:GetCDRBucket": {}, - "chime:GetDomain": {}, - "chime:GetEventsConfiguration": {}, - "chime:GetGlobalSettings": {}, - "chime:GetMeetingDetail": {}, - "chime:GetMessagingSessionEndpoint": {}, - "chime:GetPhoneNumber": {}, - "chime:GetPhoneNumberOrder": {}, - "chime:GetPhoneNumberSettings": {}, - "chime:GetRetentionSettings": {}, - "chime:GetRoom": {}, - "chime:GetSipRule": {}, - "chime:GetTelephonyLimits": {}, - "chime:GetUser": {}, - "chime:GetUserActivityReportData": {}, - "chime:GetUserByEmail": {}, - "chime:GetUserSettings": {}, - "chime:GetVoiceConnectorGroup": {}, - "chime:InviteDelegate": {}, - "chime:InviteUsers": {}, - "chime:InviteUsersFromProvider": {}, - "chime:ListAccountUsageReportData": {}, - "chime:ListAccounts": {}, - "chime:ListApiKeys": {}, - "chime:ListAvailableVoiceConnectorRegions": {}, - "chime:ListBots": {}, - "chime:ListCDRBucket": {}, - "chime:ListCallingRegions": {}, - "chime:ListDelegates": {}, - "chime:ListDirectories": {}, - "chime:ListDomains": {}, - "chime:ListGroups": {}, - "chime:ListMediaCapturePipelines": {}, - "chime:ListMediaInsightsPipelineConfigurations": {}, - "chime:ListMediaPipelineKinesisVideoStreamPools": {}, - "chime:ListMediaPipelines": {}, - "chime:ListMeetingEvents": {}, - "chime:ListMeetings": {}, - "chime:ListMeetingsReportData": {}, - "chime:ListPhoneNumberOrders": {}, - "chime:ListPhoneNumbers": {}, - "chime:ListRoomMemberships": {}, - "chime:ListRooms": {}, - "chime:ListSipMediaApplications": {}, - "chime:ListSupportedPhoneNumberCountries": {}, - "chime:ListUsers": {}, - "chime:ListVoiceConnectorGroups": {}, - "chime:ListVoiceConnectors": {}, - "chime:ListVoiceProfileDomains": {}, - "chime:LogoutUser": {}, - "chime:PutEventsConfiguration": {}, - "chime:PutRetentionSettings": {}, - "chime:RedactConversationMessage": {}, - "chime:RedactRoomMessage": {}, - "chime:RegenerateSecurityToken": {}, - "chime:RenameAccount": {}, - "chime:RenewDelegate": {}, - "chime:ResetAccountResource": {}, - "chime:ResetPersonalPIN": {}, - "chime:RestorePhoneNumber": {}, - "chime:RetrieveDataExports": {}, - "chime:SearchAvailablePhoneNumbers": {}, - "chime:StartDataExport": {}, - "chime:StartMeetingTranscription": {}, - "chime:StopMeetingTranscription": {}, - "chime:SubmitSupportRequest": {}, - "chime:SuspendUsers": {}, - "chime:UnauthorizeDirectory": {}, - "chime:UpdateAccount": {}, - "chime:UpdateAccountOpenIdConfig": {}, - "chime:UpdateAccountResource": {}, - "chime:UpdateAccountSettings": {}, - "chime:UpdateBot": {}, - "chime:UpdateCDRSettings": {}, - "chime:UpdateGlobalSettings": {}, - "chime:UpdatePhoneNumber": {}, - "chime:UpdatePhoneNumberSettings": {}, - "chime:UpdateRoom": {}, - "chime:UpdateRoomMembership": {}, - "chime:UpdateSupportedLicenses": {}, - "chime:UpdateUser": {}, - "chime:UpdateUserLicenses": {}, - "chime:UpdateUserSettings": {}, - "chime:ValidateAccountResource": {}, - "chime:ValidateE911Address": {}, - "cleanrooms-ml:CreateTrainingDataset": {}, - "cleanrooms-ml:ListAudienceModels": {}, - "cleanrooms-ml:ListConfiguredAudienceModels": {}, - "cleanrooms-ml:ListTrainingDatasets": {}, - "cleanrooms:ListCollaborations": {}, - "cleanrooms:ListConfiguredTables": {}, - "cleanrooms:ListMemberships": {}, - "cloud9:CreateEnvironmentEC2": {}, - "cloud9:CreateEnvironmentSSH": {}, - "cloud9:GetMigrationExperiences": {}, - "cloud9:GetUserPublicKey": {}, - "cloud9:GetUserSettings": {}, - "cloud9:ListEnvironments": {}, - "cloud9:UpdateUserSettings": {}, - "cloud9:ValidateEnvironmentName": {}, - "clouddirectory:CreateSchema": {}, - "clouddirectory:ListDevelopmentSchemaArns": {}, - "clouddirectory:ListDirectories": {}, - "clouddirectory:ListManagedSchemaArns": {}, - "clouddirectory:ListPublishedSchemaArns": {}, - "clouddirectory:PutSchemaFromJson": {}, - "cloudformation:ActivateOrganizationsAccess": {}, - "cloudformation:ActivateType": {}, - "cloudformation:BatchDescribeTypeConfigurations": {}, - "cloudformation:CancelResourceRequest": {}, - "cloudformation:CreateGeneratedTemplate": {}, - "cloudformation:CreateResource": {}, - "cloudformation:CreateStackSet": {}, - "cloudformation:CreateUploadBucket": {}, - "cloudformation:DeactivateOrganizationsAccess": {}, - "cloudformation:DeactivateType": {}, - "cloudformation:DeleteGeneratedTemplate": {}, - "cloudformation:DeleteResource": {}, - "cloudformation:DeregisterType": {}, - "cloudformation:DescribeAccountLimits": {}, - "cloudformation:DescribeGeneratedTemplate": {}, - "cloudformation:DescribeOrganizationsAccess": {}, - "cloudformation:DescribePublisher": {}, - "cloudformation:DescribeResourceScan": {}, - "cloudformation:DescribeStackDriftDetectionStatus": {}, - "cloudformation:DescribeType": {}, - "cloudformation:DescribeTypeRegistration": {}, - "cloudformation:EstimateTemplateCost": {}, - "cloudformation:GetGeneratedTemplate": {}, - "cloudformation:GetResource": {}, - "cloudformation:GetResourceRequestStatus": {}, - "cloudformation:ListExports": {}, - "cloudformation:ListGeneratedTemplates": {}, - "cloudformation:ListImports": {}, - "cloudformation:ListResourceRequests": {}, - "cloudformation:ListResourceScanRelatedResources": {}, - "cloudformation:ListResourceScanResources": {}, - "cloudformation:ListResourceScans": {}, - "cloudformation:ListResources": {}, - "cloudformation:ListStackSets": {}, - "cloudformation:ListStacks": {}, - "cloudformation:ListTypeRegistrations": {}, - "cloudformation:ListTypeVersions": {}, - "cloudformation:ListTypes": {}, - "cloudformation:PublishType": {}, - "cloudformation:RegisterPublisher": {}, - "cloudformation:RegisterType": {}, - "cloudformation:SetTypeConfiguration": {}, - "cloudformation:SetTypeDefaultVersion": {}, - "cloudformation:StartResourceScan": {}, - "cloudformation:TestType": {}, - "cloudformation:UpdateGeneratedTemplate": {}, - "cloudformation:UpdateResource": {}, - "cloudformation:ValidateTemplate": {}, - "cloudfront:CreateFieldLevelEncryptionConfig": {}, - "cloudfront:CreateFieldLevelEncryptionProfile": {}, - "cloudfront:CreateKeyGroup": {}, - "cloudfront:CreateMonitoringSubscription": {}, - "cloudfront:CreateOriginAccessControl": {}, - "cloudfront:CreatePublicKey": {}, - "cloudfront:CreateSavingsPlan": {}, - "cloudfront:DeleteKeyGroup": {}, - "cloudfront:DeleteMonitoringSubscription": {}, - "cloudfront:DeletePublicKey": {}, - "cloudfront:GetKeyGroup": {}, - "cloudfront:GetKeyGroupConfig": {}, - "cloudfront:GetMonitoringSubscription": {}, - "cloudfront:GetPublicKey": {}, - "cloudfront:GetPublicKeyConfig": {}, - "cloudfront:GetSavingsPlan": {}, - "cloudfront:ListCachePolicies": {}, - "cloudfront:ListCloudFrontOriginAccessIdentities": {}, - "cloudfront:ListContinuousDeploymentPolicies": {}, - "cloudfront:ListDistributions": {}, - "cloudfront:ListDistributionsByCachePolicyId": {}, - "cloudfront:ListDistributionsByKeyGroup": {}, - "cloudfront:ListDistributionsByLambdaFunction": {}, - "cloudfront:ListDistributionsByOriginRequestPolicyId": {}, - "cloudfront:ListDistributionsByRealtimeLogConfig": {}, - "cloudfront:ListDistributionsByResponseHeadersPolicyId": {}, - "cloudfront:ListDistributionsByWebACLId": {}, - "cloudfront:ListFieldLevelEncryptionConfigs": {}, - "cloudfront:ListFieldLevelEncryptionProfiles": {}, - "cloudfront:ListFunctions": {}, - "cloudfront:ListKeyGroups": {}, - "cloudfront:ListKeyValueStores": {}, - "cloudfront:ListOriginAccessControls": {}, - "cloudfront:ListOriginRequestPolicies": {}, - "cloudfront:ListPublicKeys": {}, - "cloudfront:ListRateCards": {}, - "cloudfront:ListRealtimeLogConfigs": {}, - "cloudfront:ListResponseHeadersPolicies": {}, - "cloudfront:ListSavingsPlans": {}, - "cloudfront:ListStreamingDistributions": {}, - "cloudfront:ListUsages": {}, - "cloudfront:UpdateFieldLevelEncryptionConfig": {}, - "cloudfront:UpdateKeyGroup": {}, - "cloudfront:UpdatePublicKey": {}, - "cloudfront:UpdateSavingsPlan": {}, - "cloudhsm:AddTagsToResource": {}, - "cloudhsm:CreateHapg": {}, - "cloudhsm:CreateLunaClient": {}, - "cloudhsm:DeleteHapg": {}, - "cloudhsm:DeleteHsm": {}, - "cloudhsm:DeleteLunaClient": {}, - "cloudhsm:DescribeBackups": {}, - "cloudhsm:DescribeClusters": {}, - "cloudhsm:DescribeHapg": {}, - "cloudhsm:DescribeHsm": {}, - "cloudhsm:DescribeLunaClient": {}, - "cloudhsm:GetConfig": {}, - "cloudhsm:ListAvailableZones": {}, - "cloudhsm:ListHapgs": {}, - "cloudhsm:ListHsms": {}, - "cloudhsm:ListLunaClients": {}, - "cloudhsm:ListTagsForResource": {}, - "cloudhsm:ModifyHapg": {}, - "cloudhsm:ModifyHsm": {}, - "cloudhsm:ModifyLunaClient": {}, - "cloudhsm:RemoveTagsFromResource": {}, - "cloudshell:CreateEnvironment": {}, - "cloudtrail:DeregisterOrganizationDelegatedAdmin": {}, - "cloudtrail:DescribeTrails": {}, - "cloudtrail:GetImport": {}, - "cloudtrail:ListChannels": {}, - "cloudtrail:ListEventDataStores": {}, - "cloudtrail:ListImportFailures": {}, - "cloudtrail:ListImports": {}, - "cloudtrail:ListPublicKeys": {}, - "cloudtrail:ListServiceLinkedChannels": {}, - "cloudtrail:ListTrails": {}, - "cloudtrail:LookupEvents": {}, - "cloudtrail:RegisterOrganizationDelegatedAdmin": {}, - "cloudtrail:StartImport": {}, - "cloudtrail:StopImport": {}, - "cloudwatch:BatchGetServiceLevelIndicatorReport": {}, - "cloudwatch:CreateServiceLevelObjective": {}, - "cloudwatch:DeleteAnomalyDetector": {}, - "cloudwatch:DescribeAlarmsForMetric": {}, - "cloudwatch:DescribeAnomalyDetectors": {}, - "cloudwatch:DescribeInsightRules": {}, - "cloudwatch:EnableTopologyDiscovery": {}, - "cloudwatch:GenerateQuery": {}, - "cloudwatch:GetMetricData": {}, - "cloudwatch:GetMetricStatistics": {}, - "cloudwatch:GetMetricWidgetImage": {}, - "cloudwatch:GetTopologyDiscoveryStatus": {}, - "cloudwatch:GetTopologyMap": {}, - "cloudwatch:Link": {}, - "cloudwatch:ListDashboards": {}, - "cloudwatch:ListManagedInsightRules": {}, - "cloudwatch:ListMetricStreams": {}, - "cloudwatch:ListMetrics": {}, - "cloudwatch:ListServiceLevelObjectives": {}, - "cloudwatch:ListServices": {}, - "cloudwatch:PutAnomalyDetector": {}, - "cloudwatch:PutManagedInsightRules": {}, - "cloudwatch:PutMetricData": {}, - "codeartifact:CreateDomain": {}, - "codeartifact:CreateRepository": {}, - "codeartifact:ListDomains": {}, - "codeartifact:ListRepositories": {}, - "codebuild:DeleteOAuthToken": {}, - "codebuild:DeleteSourceCredentials": {}, - "codebuild:ImportSourceCredentials": {}, - "codebuild:ListBuildBatches": {}, - "codebuild:ListBuilds": {}, - "codebuild:ListConnectedOAuthAccounts": {}, - "codebuild:ListCuratedEnvironmentImages": {}, - "codebuild:ListFleets": {}, - "codebuild:ListProjects": {}, - "codebuild:ListReportGroups": {}, - "codebuild:ListReports": {}, - "codebuild:ListRepositories": {}, - "codebuild:ListSharedProjects": {}, - "codebuild:ListSharedReportGroups": {}, - "codebuild:ListSourceCredentials": {}, - "codebuild:PersistOAuthToken": {}, - "codecatalyst:AcceptConnection": {}, - "codecatalyst:CreateIdentityCenterApplication": {}, - "codecatalyst:CreateSpace": {}, - "codecatalyst:GetPendingConnection": {}, - "codecatalyst:ListConnections": {}, - "codecatalyst:ListIdentityCenterApplications": {}, - "codecatalyst:ListIdentityCenterApplicationsForSpace": {}, - "codecatalyst:RejectConnection": {}, - "codecommit:CreateApprovalRuleTemplate": {}, - "codecommit:DeleteApprovalRuleTemplate": {}, - "codecommit:GetApprovalRuleTemplate": {}, - "codecommit:ListApprovalRuleTemplates": {}, - "codecommit:ListRepositories": {}, - "codecommit:ListRepositoriesForApprovalRuleTemplate": {}, - "codecommit:UpdateApprovalRuleTemplateContent": {}, - "codecommit:UpdateApprovalRuleTemplateDescription": {}, - "codecommit:UpdateApprovalRuleTemplateName": {}, - "codedeploy-commands-secure:GetDeploymentSpecification": {}, - "codedeploy-commands-secure:PollHostCommand": {}, - "codedeploy-commands-secure:PutHostCommandAcknowledgement": {}, - "codedeploy-commands-secure:PutHostCommandComplete": {}, - "codedeploy:BatchGetDeploymentTargets": {}, - "codedeploy:ContinueDeployment": {}, - "codedeploy:DeleteGitHubAccountToken": {}, - "codedeploy:DeleteResourcesByExternalId": {}, - "codedeploy:GetDeploymentTarget": {}, - "codedeploy:ListApplications": {}, - "codedeploy:ListDeploymentConfigs": {}, - "codedeploy:ListDeploymentTargets": {}, - "codedeploy:ListGitHubAccountTokenNames": {}, - "codedeploy:ListOnPremisesInstances": {}, - "codedeploy:PutLifecycleEventHookExecutionStatus": {}, - "codedeploy:SkipWaitTimeForInstanceTermination": {}, - "codedeploy:StopDeployment": {}, - "codeguru-profiler:CreateProfilingGroup": {}, - "codeguru-profiler:GetFindingsReportAccountSummary": {}, - "codeguru-profiler:ListProfilingGroups": {}, - "codeguru-reviewer:AssociateRepository": {}, - "codeguru-reviewer:CreateConnectionToken": {}, - "codeguru-reviewer:GetMetricsData": {}, - "codeguru-reviewer:ListCodeReviews": {}, - "codeguru-reviewer:ListRepositoryAssociations": {}, - "codeguru-reviewer:ListThirdPartyRepositories": {}, - "codeguru-security:DeleteScansByCategory": {}, - "codeguru-security:GetAccountConfiguration": {}, - "codeguru-security:GetMetricsSummary": {}, - "codeguru-security:ListFindings": {}, - "codeguru-security:ListFindingsMetrics": {}, - "codeguru-security:ListScans": {}, - "codeguru-security:UpdateAccountConfiguration": {}, - "codeguru:GetCodeGuruFreeTrialSummary": {}, - "codepipeline:AcknowledgeJob": {}, - "codepipeline:AcknowledgeThirdPartyJob": {}, - "codepipeline:GetActionType": {}, - "codepipeline:GetJobDetails": {}, - "codepipeline:GetThirdPartyJobDetails": {}, - "codepipeline:ListActionTypes": {}, - "codepipeline:ListPipelines": {}, - "codepipeline:PollForThirdPartyJobs": {}, - "codepipeline:PutJobFailureResult": {}, - "codepipeline:PutJobSuccessResult": {}, - "codepipeline:PutThirdPartyJobFailureResult": {}, - "codepipeline:PutThirdPartyJobSuccessResult": {}, - "codestar-connections:CreateConnection": {}, - "codestar-connections:CreateHost": {}, - "codestar-connections:DeleteSyncConfiguration": {}, - "codestar-connections:GetIndividualAccessToken": {}, - "codestar-connections:GetInstallationUrl": {}, - "codestar-connections:GetResourceSyncStatus": {}, - "codestar-connections:GetSyncBlockerSummary": {}, - "codestar-connections:GetSyncConfiguration": {}, - "codestar-connections:ListHosts": {}, - "codestar-connections:ListInstallationTargets": {}, - "codestar-connections:ListRepositoryLinks": {}, - "codestar-connections:ListRepositorySyncDefinitions": {}, - "codestar-connections:ListSyncConfigurations": {}, - "codestar-connections:RegisterAppCode": {}, - "codestar-connections:StartAppRegistrationHandshake": {}, - "codestar-connections:StartOAuthHandshake": {}, - "codestar-connections:UpdateSyncBlocker": {}, - "codestar-connections:UpdateSyncConfiguration": {}, - "codestar-notifications:DeleteTarget": {}, - "codestar-notifications:ListEventTypes": {}, - "codestar-notifications:ListNotificationRules": {}, - "codestar-notifications:ListTargets": {}, - "codestar:CreateProject": {}, - "codestar:DescribeUserProfile": {}, - "codestar:ListProjects": {}, - "codestar:ListUserProfiles": {}, - "codewhisperer:GenerateRecommendations": {}, - "codewhisperer:ListProfiles": {}, - "cognito-identity:CreateIdentityPool": {}, - "cognito-identity:DeleteIdentities": {}, - "cognito-identity:DescribeIdentity": {}, - "cognito-identity:GetCredentialsForIdentity": {}, - "cognito-identity:GetId": {}, - "cognito-identity:GetOpenIdToken": {}, - "cognito-identity:ListIdentityPools": {}, - "cognito-identity:SetIdentityPoolRoles": {}, - "cognito-identity:SetPrincipalTagAttributeMap": {}, - "cognito-identity:UnlinkIdentity": {}, - "cognito-idp:AssociateSoftwareToken": {}, - "cognito-idp:ChangePassword": {}, - "cognito-idp:ConfirmDevice": {}, - "cognito-idp:ConfirmForgotPassword": {}, - "cognito-idp:ConfirmSignUp": {}, - "cognito-idp:CreateUserPool": {}, - "cognito-idp:DeleteUser": {}, - "cognito-idp:DeleteUserAttributes": {}, - "cognito-idp:DescribeUserPoolDomain": {}, - "cognito-idp:ForgetDevice": {}, - "cognito-idp:ForgotPassword": {}, - "cognito-idp:GetDevice": {}, - "cognito-idp:GetUser": {}, - "cognito-idp:GetUserAttributeVerificationCode": {}, - "cognito-idp:GlobalSignOut": {}, - "cognito-idp:InitiateAuth": {}, - "cognito-idp:ListDevices": {}, - "cognito-idp:ListUserPools": {}, - "cognito-idp:ResendConfirmationCode": {}, - "cognito-idp:RespondToAuthChallenge": {}, - "cognito-idp:RevokeToken": {}, - "cognito-idp:SetUserMFAPreference": {}, - "cognito-idp:SetUserSettings": {}, - "cognito-idp:SignUp": {}, - "cognito-idp:UpdateDeviceStatus": {}, - "cognito-idp:UpdateUserAttributes": {}, - "cognito-idp:VerifySoftwareToken": {}, - "cognito-idp:VerifyUserAttribute": {}, - "comprehend:BatchDetectDominantLanguage": {}, - "comprehend:BatchDetectEntities": {}, - "comprehend:BatchDetectKeyPhrases": {}, - "comprehend:BatchDetectSentiment": {}, - "comprehend:BatchDetectSyntax": {}, - "comprehend:BatchDetectTargetedSentiment": {}, - "comprehend:ContainsPiiEntities": {}, - "comprehend:DetectDominantLanguage": {}, - "comprehend:DetectKeyPhrases": {}, - "comprehend:DetectPiiEntities": {}, - "comprehend:DetectSentiment": {}, - "comprehend:DetectSyntax": {}, - "comprehend:DetectTargetedSentiment": {}, - "comprehend:DetectToxicContent": {}, - "comprehend:ListDocumentClassificationJobs": {}, - "comprehend:ListDocumentClassifierSummaries": {}, - "comprehend:ListDocumentClassifiers": {}, - "comprehend:ListDominantLanguageDetectionJobs": {}, - "comprehend:ListEndpoints": {}, - "comprehend:ListEntitiesDetectionJobs": {}, - "comprehend:ListEntityRecognizerSummaries": {}, - "comprehend:ListEntityRecognizers": {}, - "comprehend:ListEventsDetectionJobs": {}, - "comprehend:ListFlywheels": {}, - "comprehend:ListKeyPhrasesDetectionJobs": {}, - "comprehend:ListPiiEntitiesDetectionJobs": {}, - "comprehend:ListSentimentDetectionJobs": {}, - "comprehend:ListTargetedSentimentDetectionJobs": {}, - "comprehend:ListTopicsDetectionJobs": {}, - "comprehendmedical:DescribeEntitiesDetectionV2Job": {}, - "comprehendmedical:DescribeICD10CMInferenceJob": {}, - "comprehendmedical:DescribePHIDetectionJob": {}, - "comprehendmedical:DescribeRxNormInferenceJob": {}, - "comprehendmedical:DescribeSNOMEDCTInferenceJob": {}, - "comprehendmedical:DetectEntitiesV2": {}, - "comprehendmedical:DetectPHI": {}, - "comprehendmedical:InferICD10CM": {}, - "comprehendmedical:InferRxNorm": {}, - "comprehendmedical:InferSNOMEDCT": {}, - "comprehendmedical:ListEntitiesDetectionV2Jobs": {}, - "comprehendmedical:ListICD10CMInferenceJobs": {}, - "comprehendmedical:ListPHIDetectionJobs": {}, - "comprehendmedical:ListRxNormInferenceJobs": {}, - "comprehendmedical:ListSNOMEDCTInferenceJobs": {}, - "comprehendmedical:StartEntitiesDetectionV2Job": {}, - "comprehendmedical:StartICD10CMInferenceJob": {}, - "comprehendmedical:StartPHIDetectionJob": {}, - "comprehendmedical:StartRxNormInferenceJob": {}, - "comprehendmedical:StartSNOMEDCTInferenceJob": {}, - "comprehendmedical:StopEntitiesDetectionV2Job": {}, - "comprehendmedical:StopICD10CMInferenceJob": {}, - "comprehendmedical:StopPHIDetectionJob": {}, - "comprehendmedical:StopRxNormInferenceJob": {}, - "comprehendmedical:StopSNOMEDCTInferenceJob": {}, - "compute-optimizer:DeleteRecommendationPreferences": {}, - "compute-optimizer:DescribeRecommendationExportJobs": {}, - "compute-optimizer:ExportAutoScalingGroupRecommendations": {}, - "compute-optimizer:ExportEBSVolumeRecommendations": {}, - "compute-optimizer:ExportEC2InstanceRecommendations": {}, - "compute-optimizer:ExportECSServiceRecommendations": {}, - "compute-optimizer:ExportLambdaFunctionRecommendations": {}, - "compute-optimizer:ExportLicenseRecommendations": {}, - "compute-optimizer:GetAutoScalingGroupRecommendations": {}, - "compute-optimizer:GetEBSVolumeRecommendations": {}, - "compute-optimizer:GetEC2InstanceRecommendations": {}, - "compute-optimizer:GetEC2RecommendationProjectedMetrics": {}, - "compute-optimizer:GetECSServiceRecommendationProjectedMetrics": {}, - "compute-optimizer:GetECSServiceRecommendations": {}, - "compute-optimizer:GetEffectiveRecommendationPreferences": {}, - "compute-optimizer:GetEnrollmentStatus": {}, - "compute-optimizer:GetEnrollmentStatusesForOrganization": {}, - "compute-optimizer:GetLambdaFunctionRecommendations": {}, - "compute-optimizer:GetLicenseRecommendations": {}, - "compute-optimizer:GetRecommendationPreferences": {}, - "compute-optimizer:GetRecommendationSummaries": {}, - "compute-optimizer:PutRecommendationPreferences": {}, - "compute-optimizer:UpdateEnrollmentStatus": {}, - "config:BatchGetResourceConfig": {}, - "config:DeleteConfigurationRecorder": {}, - "config:DeleteDeliveryChannel": {}, - "config:DeletePendingAggregationRequest": {}, - "config:DeleteRemediationExceptions": {}, - "config:DeleteResourceConfig": {}, - "config:DeleteRetentionConfiguration": {}, - "config:DeliverConfigSnapshot": {}, - "config:DescribeAggregationAuthorizations": {}, - "config:DescribeComplianceByConfigRule": {}, - "config:DescribeComplianceByResource": {}, - "config:DescribeConfigRuleEvaluationStatus": {}, - "config:DescribeConfigRules": {}, - "config:DescribeConfigurationAggregators": {}, - "config:DescribeConfigurationRecorderStatus": {}, - "config:DescribeConfigurationRecorders": {}, - "config:DescribeConformancePackStatus": {}, - "config:DescribeConformancePacks": {}, - "config:DescribeDeliveryChannelStatus": {}, - "config:DescribeDeliveryChannels": {}, - "config:DescribeOrganizationConfigRuleStatuses": {}, - "config:DescribeOrganizationConfigRules": {}, - "config:DescribeOrganizationConformancePackStatuses": {}, - "config:DescribeOrganizationConformancePacks": {}, - "config:DescribePendingAggregationRequests": {}, - "config:DescribeRemediationExceptions": {}, - "config:DescribeRetentionConfigurations": {}, - "config:GetComplianceDetailsByResource": {}, - "config:GetComplianceSummaryByConfigRule": {}, - "config:GetComplianceSummaryByResourceType": {}, - "config:GetDiscoveredResourceCounts": {}, - "config:GetResourceConfigHistory": {}, - "config:GetResourceEvaluationSummary": {}, - "config:ListConformancePackComplianceScores": {}, - "config:ListDiscoveredResources": {}, - "config:ListResourceEvaluations": {}, - "config:ListStoredQueries": {}, - "config:PutConfigurationRecorder": {}, - "config:PutDeliveryChannel": {}, - "config:PutEvaluations": {}, - "config:PutRemediationExceptions": {}, - "config:PutResourceConfig": {}, - "config:PutRetentionConfiguration": {}, - "config:SelectResourceConfig": {}, - "config:StartConfigurationRecorder": {}, - "config:StartRemediationExecution": {}, - "config:StartResourceEvaluation": {}, - "config:StopConfigurationRecorder": {}, - "connect-campaigns:DeleteConnectInstanceConfig": {}, - "connect-campaigns:DeleteInstanceOnboardingJob": {}, - "connect-campaigns:GetConnectInstanceConfig": {}, - "connect-campaigns:GetInstanceOnboardingJobStatus": {}, - "connect-campaigns:ListCampaigns": {}, - "connect-campaigns:StartInstanceOnboardingJob": {}, - "connect:CreateInstance": {}, - "connect:ListInstances": {}, - "connect:SendChatIntegrationEvent": {}, - "consoleapp:ListDeviceIdentities": {}, - "consolidatedbilling:GetAccountBillingRole": {}, - "consolidatedbilling:ListLinkedAccounts": {}, - "controltower:CreateLandingZone": {}, - "controltower:CreateManagedAccount": {}, - "controltower:DeregisterManagedAccount": {}, - "controltower:DeregisterOrganizationalUnit": {}, - "controltower:DescribeAccountFactoryConfig": {}, - "controltower:DescribeCoreService": {}, - "controltower:DescribeGuardrail": {}, - "controltower:DescribeGuardrailForTarget": {}, - "controltower:DescribeLandingZoneConfiguration": {}, - "controltower:DescribeManagedAccount": {}, - "controltower:DescribeManagedOrganizationalUnit": {}, - "controltower:DescribeRegisterOrganizationalUnitOperation": {}, - "controltower:DescribeSingleSignOn": {}, - "controltower:DisableGuardrail": {}, - "controltower:EnableGuardrail": {}, - "controltower:GetAccountInfo": {}, - "controltower:GetAvailableUpdates": {}, - "controltower:GetControlOperation": {}, - "controltower:GetGuardrailComplianceStatus": {}, - "controltower:GetHomeRegion": {}, - "controltower:GetLandingZoneDriftStatus": {}, - "controltower:GetLandingZoneOperation": {}, - "controltower:GetLandingZoneStatus": {}, - "controltower:ListDirectoryGroups": {}, - "controltower:ListDriftDetails": {}, - "controltower:ListEnabledControls": {}, - "controltower:ListEnabledGuardrails": {}, - "controltower:ListExtendGovernancePrecheckDetails": {}, - "controltower:ListExternalConfigRuleCompliance": {}, - "controltower:ListGuardrailViolations": {}, - "controltower:ListGuardrails": {}, - "controltower:ListGuardrailsForTarget": {}, - "controltower:ListLandingZones": {}, - "controltower:ListManagedAccounts": {}, - "controltower:ListManagedAccountsForGuardrail": {}, - "controltower:ListManagedAccountsForParent": {}, - "controltower:ListManagedOrganizationalUnits": {}, - "controltower:ListManagedOrganizationalUnitsForGuardrail": {}, - "controltower:ManageOrganizationalUnit": {}, - "controltower:PerformPreLaunchChecks": {}, - "controltower:SetupLandingZone": {}, - "controltower:UpdateAccountFactoryConfig": {}, - "cost-optimization-hub:GetPreferences": {}, - "cost-optimization-hub:GetRecommendation": {}, - "cost-optimization-hub:ListEnrollmentStatuses": {}, - "cost-optimization-hub:ListRecommendationSummaries": {}, - "cost-optimization-hub:ListRecommendations": {}, - "cost-optimization-hub:UpdateEnrollmentStatus": {}, - "cost-optimization-hub:UpdatePreferences": {}, - "cur:DescribeReportDefinitions": {}, - "cur:GetClassicReport": {}, - "cur:GetClassicReportPreferences": {}, - "cur:GetUsageReport": {}, - "cur:PutClassicReportPreferences": {}, - "cur:ValidateReportDestination": {}, - "customer-verification:CreateCustomerVerificationDetails": {}, - "customer-verification:GetCustomerVerificationDetails": {}, - "customer-verification:GetCustomerVerificationEligibility": {}, - "customer-verification:UpdateCustomerVerificationDetails": {}, - "databrew:CreateDataset": {}, - "databrew:CreateProfileJob": {}, - "databrew:CreateProject": {}, - "databrew:CreateRecipe": {}, - "databrew:CreateRecipeJob": {}, - "databrew:CreateRuleset": {}, - "databrew:CreateSchedule": {}, - "databrew:ListDatasets": {}, - "databrew:ListJobs": {}, - "databrew:ListProjects": {}, - "databrew:ListRecipes": {}, - "databrew:ListRulesets": {}, - "databrew:ListSchedules": {}, - "dataexchange:CreateDataSet": {}, - "dataexchange:CreateEventAction": {}, - "dataexchange:CreateJob": {}, - "dataexchange:ListDataSets": {}, - "dataexchange:ListEventActions": {}, - "dataexchange:ListJobs": {}, - "datapipeline:CreatePipeline": {}, - "datapipeline:GetAccountLimits": {}, - "datapipeline:ListPipelines": {}, - "datapipeline:PollForTask": {}, - "datapipeline:PutAccountLimits": {}, - "datapipeline:ReportTaskRunnerHeartbeat": {}, - "datasync:CreateAgent": {}, - "datasync:CreateLocationAzureBlob": {}, - "datasync:CreateLocationEfs": {}, - "datasync:CreateLocationFsxLustre": {}, - "datasync:CreateLocationFsxOntap": {}, - "datasync:CreateLocationFsxOpenZfs": {}, - "datasync:CreateLocationFsxWindows": {}, - "datasync:CreateLocationHdfs": {}, - "datasync:CreateLocationNfs": {}, - "datasync:CreateLocationObjectStorage": {}, - "datasync:CreateLocationS3": {}, - "datasync:CreateLocationSmb": {}, - "datasync:ListAgents": {}, - "datasync:ListDiscoveryJobs": {}, - "datasync:ListLocations": {}, - "datasync:ListStorageSystems": {}, - "datasync:ListTaskExecutions": {}, - "datasync:ListTasks": {}, - "datazone:AcceptPredictions": {}, - "datazone:AcceptSubscriptionRequest": {}, - "datazone:CancelSubscription": {}, - "datazone:CreateAsset": {}, - "datazone:CreateAssetRevision": {}, - "datazone:CreateAssetType": {}, - "datazone:CreateDataSource": {}, - "datazone:CreateDomain": {}, - "datazone:CreateEnvironment": {}, - "datazone:CreateEnvironmentBlueprint": {}, - "datazone:CreateEnvironmentProfile": {}, - "datazone:CreateFormType": {}, - "datazone:CreateGlossary": {}, - "datazone:CreateGlossaryTerm": {}, - "datazone:CreateGroupProfile": {}, - "datazone:CreateListingChangeSet": {}, - "datazone:CreateProject": {}, - "datazone:CreateProjectMembership": {}, - "datazone:CreateSubscriptionGrant": {}, - "datazone:CreateSubscriptionRequest": {}, - "datazone:CreateSubscriptionTarget": {}, - "datazone:CreateUserProfile": {}, - "datazone:DeleteAsset": {}, - "datazone:DeleteAssetType": {}, - "datazone:DeleteDataSource": {}, - "datazone:DeleteDomainSharingPolicy": {}, - "datazone:DeleteEnvironment": {}, - "datazone:DeleteEnvironmentBlueprint": {}, - "datazone:DeleteEnvironmentBlueprintConfiguration": {}, - "datazone:DeleteEnvironmentProfile": {}, - "datazone:DeleteFormType": {}, - "datazone:DeleteGlossary": {}, - "datazone:DeleteGlossaryTerm": {}, - "datazone:DeleteListing": {}, - "datazone:DeleteProject": {}, - "datazone:DeleteProjectMembership": {}, - "datazone:DeleteSubscriptionGrant": {}, - "datazone:DeleteSubscriptionRequest": {}, - "datazone:DeleteSubscriptionTarget": {}, - "datazone:GetAsset": {}, - "datazone:GetAssetType": {}, - "datazone:GetDataSource": {}, - "datazone:GetDataSourceRun": {}, - "datazone:GetDomainSharingPolicy": {}, - "datazone:GetEnvironment": {}, - "datazone:GetEnvironmentActionLink": {}, - "datazone:GetEnvironmentBlueprint": {}, - "datazone:GetEnvironmentBlueprintConfiguration": {}, - "datazone:GetEnvironmentCredentials": {}, - "datazone:GetEnvironmentProfile": {}, - "datazone:GetFormType": {}, - "datazone:GetGlossary": {}, - "datazone:GetGlossaryTerm": {}, - "datazone:GetGroupProfile": {}, - "datazone:GetIamPortalLoginUrl": {}, - "datazone:GetListing": {}, - "datazone:GetMetadataGenerationRun": {}, - "datazone:GetProject": {}, - "datazone:GetSubscription": {}, - "datazone:GetSubscriptionEligibility": {}, - "datazone:GetSubscriptionGrant": {}, - "datazone:GetSubscriptionRequestDetails": {}, - "datazone:GetSubscriptionTarget": {}, - "datazone:GetUserProfile": {}, - "datazone:ListAccountEnvironments": {}, - "datazone:ListAssetRevisions": {}, - "datazone:ListDataSourceRunActivities": {}, - "datazone:ListDataSourceRuns": {}, - "datazone:ListDataSources": {}, - "datazone:ListDomains": {}, - "datazone:ListEnvironmentBlueprintConfigurationSummaries": {}, - "datazone:ListEnvironmentBlueprintConfigurations": {}, - "datazone:ListEnvironmentBlueprints": {}, - "datazone:ListEnvironmentProfiles": {}, - "datazone:ListEnvironments": {}, - "datazone:ListGroupsForUser": {}, - "datazone:ListMetadataGenerationRuns": {}, - "datazone:ListNotifications": {}, - "datazone:ListProjectMemberships": {}, - "datazone:ListProjects": {}, - "datazone:ListSubscriptionGrants": {}, - "datazone:ListSubscriptionRequests": {}, - "datazone:ListSubscriptionTargets": {}, - "datazone:ListSubscriptions": {}, - "datazone:ListWarehouseMetadata": {}, - "datazone:ProvisionDomain": {}, - "datazone:PutDomainSharingPolicy": {}, - "datazone:PutEnvironmentBlueprintConfiguration": {}, - "datazone:RefreshToken": {}, - "datazone:RejectPredictions": {}, - "datazone:RejectSubscriptionRequest": {}, - "datazone:RevokeSubscription": {}, - "datazone:Search": {}, - "datazone:SearchGroupProfiles": {}, - "datazone:SearchListings": {}, - "datazone:SearchTypes": {}, - "datazone:SearchUserProfiles": {}, - "datazone:SsoLogin": {}, - "datazone:SsoLogout": {}, - "datazone:StartDataSourceRun": {}, - "datazone:StartMetadataGenerationRun": {}, - "datazone:StopMetadataGenerationRun": {}, - "datazone:UpdateDataSource": {}, - "datazone:UpdateEnvironment": {}, - "datazone:UpdateEnvironmentBlueprint": {}, - "datazone:UpdateEnvironmentConfiguration": {}, - "datazone:UpdateEnvironmentDeploymentStatus": {}, - "datazone:UpdateEnvironmentProfile": {}, - "datazone:UpdateGlossary": {}, - "datazone:UpdateGlossaryTerm": {}, - "datazone:UpdateGroupProfile": {}, - "datazone:UpdateProject": {}, - "datazone:UpdateSubscriptionGrantStatus": {}, - "datazone:UpdateSubscriptionRequest": {}, - "datazone:UpdateSubscriptionTarget": {}, - "datazone:UpdateUserProfile": {}, - "datazone:ValidatePassRole": {}, - "dax:CreateParameterGroup": {}, - "dax:CreateSubnetGroup": {}, - "dax:DeleteParameterGroup": {}, - "dax:DeleteSubnetGroup": {}, - "dax:DescribeDefaultParameters": {}, - "dax:DescribeEvents": {}, - "dax:DescribeParameterGroups": {}, - "dax:DescribeParameters": {}, - "dax:DescribeSubnetGroups": {}, - "dax:UpdateParameterGroup": {}, - "dax:UpdateSubnetGroup": {}, - "dbqms:CreateFavoriteQuery": {}, - "dbqms:CreateTab": {}, - "dbqms:DeleteFavoriteQueries": {}, - "dbqms:DeleteQueryHistory": {}, - "dbqms:DeleteTab": {}, - "dbqms:DescribeFavoriteQueries": {}, - "dbqms:DescribeQueryHistory": {}, - "dbqms:DescribeTabs": {}, - "dbqms:GetQueryString": {}, - "dbqms:UpdateFavoriteQuery": {}, - "dbqms:UpdateQueryHistory": {}, - "dbqms:UpdateTab": {}, - "deepcomposer:AssociateCoupon": {}, - "deepracer:AdminGetAccountConfig": {}, - "deepracer:AdminListAssociatedResources": {}, - "deepracer:AdminListAssociatedUsers": {}, - "deepracer:AdminManageUser": {}, - "deepracer:AdminSetAccountConfig": {}, - "deepracer:CreateCar": {}, - "deepracer:CreateLeaderboard": {}, - "deepracer:GetAccountConfig": {}, - "deepracer:GetAlias": {}, - "deepracer:GetCars": {}, - "deepracer:ImportModel": {}, - "deepracer:ListLeaderboards": {}, - "deepracer:ListModels": {}, - "deepracer:ListPrivateLeaderboards": {}, - "deepracer:ListSubscribedPrivateLeaderboards": {}, - "deepracer:ListTracks": {}, - "deepracer:MigrateModels": {}, - "deepracer:SetAlias": {}, - "deepracer:TestRewardFunction": {}, - "detective:AcceptInvitation": {}, - "detective:BatchGetMembershipDatasources": {}, - "detective:CreateGraph": {}, - "detective:DisableOrganizationAdminAccount": {}, - "detective:DisassociateMembership": {}, - "detective:EnableOrganizationAdminAccount": {}, - "detective:GetPricingInformation": {}, - "detective:ListGraphs": {}, - "detective:ListInvitations": {}, - "detective:ListOrganizationAdminAccount": {}, - "detective:RejectInvitation": {}, - "devicefarm:CreateInstanceProfile": {}, - "devicefarm:CreateProject": {}, - "devicefarm:CreateTestGridProject": {}, - "devicefarm:CreateVPCEConfiguration": {}, - "devicefarm:GetAccountSettings": {}, - "devicefarm:GetOfferingStatus": {}, - "devicefarm:ListDeviceInstances": {}, - "devicefarm:ListDevices": {}, - "devicefarm:ListInstanceProfiles": {}, - "devicefarm:ListOfferingPromotions": {}, - "devicefarm:ListOfferingTransactions": {}, - "devicefarm:ListOfferings": {}, - "devicefarm:ListProjects": {}, - "devicefarm:ListTestGridProjects": {}, - "devicefarm:ListVPCEConfigurations": {}, - "devicefarm:PurchaseOffering": {}, - "devicefarm:RenewOffering": {}, - "devops-guru:DeleteInsight": {}, - "devops-guru:DescribeAccountHealth": {}, - "devops-guru:DescribeAccountOverview": {}, - "devops-guru:DescribeAnomaly": {}, - "devops-guru:DescribeEventSourcesConfig": {}, - "devops-guru:DescribeFeedback": {}, - "devops-guru:DescribeInsight": {}, - "devops-guru:DescribeOrganizationHealth": {}, - "devops-guru:DescribeOrganizationOverview": {}, - "devops-guru:DescribeOrganizationResourceCollectionHealth": {}, - "devops-guru:DescribeResourceCollectionHealth": {}, - "devops-guru:DescribeServiceIntegration": {}, - "devops-guru:GetCostEstimation": {}, - "devops-guru:GetResourceCollection": {}, - "devops-guru:ListAnomaliesForInsight": {}, - "devops-guru:ListAnomalousLogGroups": {}, - "devops-guru:ListEvents": {}, - "devops-guru:ListInsights": {}, - "devops-guru:ListMonitoredResources": {}, - "devops-guru:ListNotificationChannels": {}, - "devops-guru:ListOrganizationInsights": {}, - "devops-guru:ListRecommendations": {}, - "devops-guru:PutFeedback": {}, - "devops-guru:SearchInsights": {}, - "devops-guru:SearchOrganizationInsights": {}, - "devops-guru:StartCostEstimation": {}, - "devops-guru:UpdateEventSourcesConfig": {}, - "devops-guru:UpdateResourceCollection": {}, - "devops-guru:UpdateServiceIntegration": {}, - "directconnect:ConfirmCustomerAgreement": {}, - "directconnect:CreateDirectConnectGateway": {}, - "directconnect:DeleteDirectConnectGatewayAssociationProposal": {}, - "directconnect:DescribeCustomerMetadata": {}, - "directconnect:DescribeLocations": {}, - "directconnect:DescribeVirtualGateways": {}, - "directconnect:UpdateDirectConnectGatewayAssociation": {}, - "discovery:AssociateConfigurationItemsToApplication": {}, - "discovery:BatchDeleteAgents": {}, - "discovery:BatchDeleteImportData": {}, - "discovery:CreateApplication": {}, - "discovery:CreateTags": {}, - "discovery:DeleteApplications": {}, - "discovery:DeleteTags": {}, - "discovery:DescribeAgents": {}, - "discovery:DescribeBatchDeleteConfigurationTask": {}, - "discovery:DescribeConfigurations": {}, - "discovery:DescribeContinuousExports": {}, - "discovery:DescribeExportConfigurations": {}, - "discovery:DescribeExportTasks": {}, - "discovery:DescribeImportTasks": {}, - "discovery:DescribeTags": {}, - "discovery:DisassociateConfigurationItemsFromApplication": {}, - "discovery:ExportConfigurations": {}, - "discovery:GetDiscoverySummary": {}, - "discovery:GetNetworkConnectionGraph": {}, - "discovery:ListConfigurations": {}, - "discovery:ListServerNeighbors": {}, - "discovery:StartBatchDeleteConfigurationTask": {}, - "discovery:StartContinuousExport": {}, - "discovery:StartDataCollectionByAgentIds": {}, - "discovery:StartExportTask": {}, - "discovery:StartImportTask": {}, - "discovery:StopContinuousExport": {}, - "discovery:StopDataCollectionByAgentIds": {}, - "discovery:UpdateApplication": {}, - "dlm:CreateLifecyclePolicy": {}, - "dlm:GetLifecyclePolicies": {}, - "dms:BatchStartRecommendations": {}, - "dms:CreateDataProvider": {}, - "dms:CreateEndpoint": {}, - "dms:CreateEventSubscription": {}, - "dms:CreateFleetAdvisorCollector": {}, - "dms:CreateInstanceProfile": {}, - "dms:CreateReplicationInstance": {}, - "dms:CreateReplicationSubnetGroup": {}, - "dms:DeleteFleetAdvisorCollector": {}, - "dms:DeleteFleetAdvisorDatabases": {}, - "dms:DescribeAccountAttributes": {}, - "dms:DescribeCertificates": {}, - "dms:DescribeConnections": {}, - "dms:DescribeDataMigrations": {}, - "dms:DescribeEndpointSettings": {}, - "dms:DescribeEndpointTypes": {}, - "dms:DescribeEndpoints": {}, - "dms:DescribeEngineVersions": {}, - "dms:DescribeEventCategories": {}, - "dms:DescribeEventSubscriptions": {}, - "dms:DescribeEvents": {}, - "dms:DescribeFleetAdvisorCollectors": {}, - "dms:DescribeFleetAdvisorDatabases": {}, - "dms:DescribeFleetAdvisorLsaAnalysis": {}, - "dms:DescribeFleetAdvisorSchemaObjectSummary": {}, - "dms:DescribeFleetAdvisorSchemas": {}, - "dms:DescribeOrderableReplicationInstances": {}, - "dms:DescribePendingMaintenanceActions": {}, - "dms:DescribeRecommendationLimitations": {}, - "dms:DescribeRecommendations": {}, - "dms:DescribeReplicationConfigs": {}, - "dms:DescribeReplicationInstances": {}, - "dms:DescribeReplicationSubnetGroups": {}, - "dms:DescribeReplicationTasks": {}, - "dms:DescribeReplications": {}, - "dms:ImportCertificate": {}, - "dms:ModifyEventSubscription": {}, - "dms:ModifyFleetAdvisorCollector": {}, - "dms:ModifyFleetAdvisorCollectorStatuses": {}, - "dms:ModifyReplicationSubnetGroup": {}, - "dms:RunFleetAdvisorLsaAnalysis": {}, - "dms:StartRecommendations": {}, - "dms:UpdateSubscriptionsToEventBridge": {}, - "dms:UploadFileMetadataList": {}, - "docdb-elastic:CreateCluster": {}, - "docdb-elastic:ListClusterSnapshots": {}, - "docdb-elastic:ListClusters": {}, - "drs:BatchDeleteSnapshotRequestForDrs": {}, - "drs:CreateExtendedSourceServer": {}, - "drs:CreateLaunchConfigurationTemplate": {}, - "drs:CreateReplicationConfigurationTemplate": {}, - "drs:CreateSourceNetwork": {}, - "drs:CreateSourceServerForDrs": {}, - "drs:DescribeJobs": {}, - "drs:DescribeLaunchConfigurationTemplates": {}, - "drs:DescribeRecoveryInstances": {}, - "drs:DescribeReplicationConfigurationTemplates": {}, - "drs:DescribeReplicationServerAssociationsForDrs": {}, - "drs:DescribeSnapshotRequestsForDrs": {}, - "drs:DescribeSourceNetworks": {}, - "drs:DescribeSourceServers": {}, - "drs:GetAgentInstallationAssetsForDrs": {}, - "drs:GetChannelCommandsForDrs": {}, - "drs:InitializeService": {}, - "drs:ListExtensibleSourceServers": {}, - "drs:ListStagingAccounts": {}, - "drs:ListTagsForResource": {}, - "drs:SendChannelCommandResultForDrs": {}, - "drs:SendClientLogsForDrs": {}, - "drs:SendClientMetricsForDrs": {}, - "ds:CheckAlias": {}, - "ds:ConnectDirectory": {}, - "ds:CreateDirectory": {}, - "ds:CreateIdentityPoolDirectory": {}, - "ds:CreateMicrosoftAD": {}, - "ds:DescribeDirectories": {}, - "ds:DescribeSnapshots": {}, - "ds:DescribeTrusts": {}, - "ds:GetDirectoryLimits": {}, - "ds:ListLogSubscriptions": {}, - "dynamodb:DescribeEndpoints": {}, - "dynamodb:DescribeLimits": {}, - "dynamodb:DescribeReservedCapacity": {}, - "dynamodb:DescribeReservedCapacityOfferings": {}, - "dynamodb:ListBackups": {}, - "dynamodb:ListContributorInsights": {}, - "dynamodb:ListExports": {}, - "dynamodb:ListGlobalTables": {}, - "dynamodb:ListImports": {}, - "dynamodb:ListStreams": {}, - "dynamodb:ListTables": {}, - "dynamodb:PurchaseReservedCapacityOfferings": {}, - "ec2:AcceptReservedInstancesExchangeQuote": {}, - "ec2:AdvertiseByoipCidr": {}, - "ec2:AssociateIpamByoasn": {}, - "ec2:AssociateTrunkInterface": {}, - "ec2:BundleInstance": {}, - "ec2:CancelBundleTask": {}, - "ec2:CancelConversionTask": {}, - "ec2:CancelReservedInstancesListing": {}, - "ec2:ConfirmProductInstance": {}, - "ec2:CreateDefaultSubnet": {}, - "ec2:CreateDefaultVpc": {}, - "ec2:CreateReservedInstancesListing": {}, - "ec2:CreateSpotDatafeedSubscription": {}, - "ec2:CreateSubnetCidrReservation": {}, - "ec2:DeleteQueuedReservedInstances": {}, - "ec2:DeleteSpotDatafeedSubscription": {}, - "ec2:DeleteSubnetCidrReservation": {}, - "ec2:DeprovisionByoipCidr": {}, - "ec2:DeregisterInstanceEventNotificationAttributes": {}, - "ec2:DescribeAccountAttributes": {}, - "ec2:DescribeAddressTransfers": {}, - "ec2:DescribeAddresses": {}, - "ec2:DescribeAggregateIdFormat": {}, - "ec2:DescribeAvailabilityZones": {}, - "ec2:DescribeAwsNetworkPerformanceMetricSubscriptions": {}, - "ec2:DescribeBundleTasks": {}, - "ec2:DescribeByoipCidrs": {}, - "ec2:DescribeCapacityBlockOfferings": {}, - "ec2:DescribeCapacityReservationFleets": {}, - "ec2:DescribeCapacityReservations": {}, - "ec2:DescribeCarrierGateways": {}, - "ec2:DescribeClassicLinkInstances": {}, - "ec2:DescribeCoipPools": {}, - "ec2:DescribeConversionTasks": {}, - "ec2:DescribeCustomerGateways": {}, - "ec2:DescribeDhcpOptions": {}, - "ec2:DescribeEgressOnlyInternetGateways": {}, - "ec2:DescribeElasticGpus": {}, - "ec2:DescribeExportImageTasks": {}, - "ec2:DescribeExportTasks": {}, - "ec2:DescribeFastLaunchImages": {}, - "ec2:DescribeFastSnapshotRestores": {}, - "ec2:DescribeFleets": {}, - "ec2:DescribeFlowLogs": {}, - "ec2:DescribeFpgaImages": {}, - "ec2:DescribeHostReservationOfferings": {}, - "ec2:DescribeHostReservations": {}, - "ec2:DescribeHosts": {}, - "ec2:DescribeIamInstanceProfileAssociations": {}, - "ec2:DescribeIdFormat": {}, - "ec2:DescribeIdentityIdFormat": {}, - "ec2:DescribeImages": {}, - "ec2:DescribeImportImageTasks": {}, - "ec2:DescribeImportSnapshotTasks": {}, - "ec2:DescribeInstanceConnectEndpoints": {}, - "ec2:DescribeInstanceCreditSpecifications": {}, - "ec2:DescribeInstanceEventNotificationAttributes": {}, - "ec2:DescribeInstanceEventWindows": {}, - "ec2:DescribeInstanceStatus": {}, - "ec2:DescribeInstanceTopology": {}, - "ec2:DescribeInstanceTypeOfferings": {}, - "ec2:DescribeInstanceTypes": {}, - "ec2:DescribeInstances": {}, - "ec2:DescribeInternetGateways": {}, - "ec2:DescribeIpamByoasn": {}, - "ec2:DescribeIpamPools": {}, - "ec2:DescribeIpamResourceDiscoveries": {}, - "ec2:DescribeIpamResourceDiscoveryAssociations": {}, - "ec2:DescribeIpamScopes": {}, - "ec2:DescribeIpams": {}, - "ec2:DescribeIpv6Pools": {}, - "ec2:DescribeKeyPairs": {}, - "ec2:DescribeLaunchTemplateVersions": {}, - "ec2:DescribeLaunchTemplates": {}, - "ec2:DescribeLocalGatewayRouteTablePermissions": {}, - "ec2:DescribeLocalGatewayRouteTableVirtualInterfaceGroupAssociations": {}, - "ec2:DescribeLocalGatewayRouteTableVpcAssociations": {}, - "ec2:DescribeLocalGatewayRouteTables": {}, - "ec2:DescribeLocalGatewayVirtualInterfaceGroups": {}, - "ec2:DescribeLocalGatewayVirtualInterfaces": {}, - "ec2:DescribeLocalGateways": {}, - "ec2:DescribeLockedSnapshots": {}, - "ec2:DescribeManagedPrefixLists": {}, - "ec2:DescribeMovingAddresses": {}, - "ec2:DescribeNatGateways": {}, - "ec2:DescribeNetworkAcls": {}, - "ec2:DescribeNetworkInsightsAccessScopeAnalyses": {}, - "ec2:DescribeNetworkInsightsAccessScopes": {}, - "ec2:DescribeNetworkInsightsAnalyses": {}, - "ec2:DescribeNetworkInsightsPaths": {}, - "ec2:DescribeNetworkInterfaceAttribute": {}, - "ec2:DescribeNetworkInterfacePermissions": {}, - "ec2:DescribeNetworkInterfaces": {}, - "ec2:DescribePlacementGroups": {}, - "ec2:DescribePrefixLists": {}, - "ec2:DescribePrincipalIdFormat": {}, - "ec2:DescribePublicIpv4Pools": {}, - "ec2:DescribeRegions": {}, - "ec2:DescribeReplaceRootVolumeTasks": {}, - "ec2:DescribeReservedInstances": {}, - "ec2:DescribeReservedInstancesListings": {}, - "ec2:DescribeReservedInstancesModifications": {}, - "ec2:DescribeReservedInstancesOfferings": {}, - "ec2:DescribeRouteTables": {}, - "ec2:DescribeScheduledInstanceAvailability": {}, - "ec2:DescribeScheduledInstances": {}, - "ec2:DescribeSecurityGroupReferences": {}, - "ec2:DescribeSecurityGroupRules": {}, - "ec2:DescribeSecurityGroups": {}, - "ec2:DescribeSnapshotTierStatus": {}, - "ec2:DescribeSnapshots": {}, - "ec2:DescribeSpotDatafeedSubscription": {}, - "ec2:DescribeSpotFleetRequests": {}, - "ec2:DescribeSpotInstanceRequests": {}, - "ec2:DescribeSpotPriceHistory": {}, - "ec2:DescribeStaleSecurityGroups": {}, - "ec2:DescribeStoreImageTasks": {}, - "ec2:DescribeSubnets": {}, - "ec2:DescribeTags": {}, - "ec2:DescribeTrafficMirrorFilters": {}, - "ec2:DescribeTrafficMirrorSessions": {}, - "ec2:DescribeTrafficMirrorTargets": {}, - "ec2:DescribeTransitGatewayAttachments": {}, - "ec2:DescribeTransitGatewayConnectPeers": {}, - "ec2:DescribeTransitGatewayConnects": {}, - "ec2:DescribeTransitGatewayMulticastDomains": {}, - "ec2:DescribeTransitGatewayPeeringAttachments": {}, - "ec2:DescribeTransitGatewayPolicyTables": {}, - "ec2:DescribeTransitGatewayRouteTableAnnouncements": {}, - "ec2:DescribeTransitGatewayRouteTables": {}, - "ec2:DescribeTransitGatewayVpcAttachments": {}, - "ec2:DescribeTransitGateways": {}, - "ec2:DescribeTrunkInterfaceAssociations": {}, - "ec2:DescribeVerifiedAccessEndpoints": {}, - "ec2:DescribeVerifiedAccessGroups": {}, - "ec2:DescribeVerifiedAccessInstanceLoggingConfigurations": {}, - "ec2:DescribeVerifiedAccessInstanceWebAclAssociations": {}, - "ec2:DescribeVerifiedAccessInstances": {}, - "ec2:DescribeVerifiedAccessTrustProviders": {}, - "ec2:DescribeVolumeStatus": {}, - "ec2:DescribeVolumes": {}, - "ec2:DescribeVolumesModifications": {}, - "ec2:DescribeVpcClassicLink": {}, - "ec2:DescribeVpcClassicLinkDnsSupport": {}, - "ec2:DescribeVpcEndpointConnectionNotifications": {}, - "ec2:DescribeVpcEndpointConnections": {}, - "ec2:DescribeVpcEndpointServiceConfigurations": {}, - "ec2:DescribeVpcEndpointServices": {}, - "ec2:DescribeVpcEndpoints": {}, - "ec2:DescribeVpcPeeringConnections": {}, - "ec2:DescribeVpcs": {}, - "ec2:DescribeVpnConnections": {}, - "ec2:DescribeVpnGateways": {}, - "ec2:DisableAwsNetworkPerformanceMetricSubscription": {}, - "ec2:DisableEbsEncryptionByDefault": {}, - "ec2:DisableImageBlockPublicAccess": {}, - "ec2:DisableIpamOrganizationAdminAccount": {}, - "ec2:DisableSerialConsoleAccess": {}, - "ec2:DisableSnapshotBlockPublicAccess": {}, - "ec2:DisassociateIpamByoasn": {}, - "ec2:DisassociateTrunkInterface": {}, - "ec2:EnableAwsNetworkPerformanceMetricSubscription": {}, - "ec2:EnableEbsEncryptionByDefault": {}, - "ec2:EnableImageBlockPublicAccess": {}, - "ec2:EnableIpamOrganizationAdminAccount": {}, - "ec2:EnableReachabilityAnalyzerOrganizationSharing": {}, - "ec2:EnableSerialConsoleAccess": {}, - "ec2:EnableSnapshotBlockPublicAccess": {}, - "ec2:ExportTransitGatewayRoutes": {}, - "ec2:GetAssociatedIpv6PoolCidrs": {}, - "ec2:GetAwsNetworkPerformanceData": {}, - "ec2:GetDefaultCreditSpecification": {}, - "ec2:GetEbsDefaultKmsKeyId": {}, - "ec2:GetEbsEncryptionByDefault": {}, - "ec2:GetHostReservationPurchasePreview": {}, - "ec2:GetImageBlockPublicAccessState": {}, - "ec2:GetInstanceTypesFromInstanceRequirements": {}, - "ec2:GetReservedInstancesExchangeQuote": {}, - "ec2:GetSerialConsoleAccessStatus": {}, - "ec2:GetSnapshotBlockPublicAccessState": {}, - "ec2:GetSpotPlacementScores": {}, - "ec2:GetSubnetCidrReservations": {}, - "ec2:GetTransitGatewayAttachmentPropagations": {}, - "ec2:GetTransitGatewayPrefixListReferences": {}, - "ec2:GetTransitGatewayRouteTableAssociations": {}, - "ec2:GetTransitGatewayRouteTablePropagations": {}, - "ec2:GetVpnConnectionDeviceTypes": {}, - "ec2:InjectApiError": {}, - "ec2:ListImagesInRecycleBin": {}, - "ec2:ListSnapshotsInRecycleBin": {}, - "ec2:ModifyAvailabilityZoneGroup": {}, - "ec2:ModifyDefaultCreditSpecification": {}, - "ec2:ModifyEbsDefaultKmsKeyId": {}, - "ec2:ModifyIdFormat": {}, - "ec2:ModifyIdentityIdFormat": {}, - "ec2:MoveAddressToVpc": {}, - "ec2:ProvisionByoipCidr": {}, - "ec2:PurchaseReservedInstancesOffering": {}, - "ec2:PurchaseScheduledInstances": {}, - "ec2:RegisterInstanceEventNotificationAttributes": {}, - "ec2:ReportInstanceStatus": {}, - "ec2:ResetEbsDefaultKmsKeyId": {}, - "ec2:RestoreAddressToClassic": {}, - "ec2:RunScheduledInstances": {}, - "ec2:WithdrawByoipCidr": {}, - "ec2messages:AcknowledgeMessage": {}, - "ec2messages:DeleteMessage": {}, - "ec2messages:FailMessage": {}, - "ec2messages:GetEndpoint": {}, - "ec2messages:GetMessages": {}, - "ec2messages:SendReply": {}, - "ecr-public:GetAuthorizationToken": {}, - "ecr:BatchImportUpstreamImage": {}, - "ecr:CreatePullThroughCacheRule": {}, - "ecr:CreateRepository": {}, - "ecr:CreateRepositoryCreationTemplate": {}, - "ecr:DeletePullThroughCacheRule": {}, - "ecr:DeleteRegistryPolicy": {}, - "ecr:DeleteRepositoryCreationTemplate": {}, - "ecr:DescribePullThroughCacheRules": {}, - "ecr:DescribeRegistry": {}, - "ecr:DescribeRepositoryCreationTemplate": {}, - "ecr:GetAuthorizationToken": {}, - "ecr:GetRegistryPolicy": {}, - "ecr:GetRegistryScanningConfiguration": {}, - "ecr:PutRegistryPolicy": {}, - "ecr:PutRegistryScanningConfiguration": {}, - "ecr:PutReplicationConfiguration": {}, - "ecr:UpdatePullThroughCacheRule": {}, - "ecr:ValidatePullThroughCacheRule": {}, - "ecs:CreateCapacityProvider": {}, - "ecs:CreateCluster": {}, - "ecs:CreateTaskSet": {}, - "ecs:DeleteAccountSetting": {}, - "ecs:DeregisterTaskDefinition": {}, - "ecs:DescribeTaskDefinition": {}, - "ecs:DiscoverPollEndpoint": {}, - "ecs:ListAccountSettings": {}, - "ecs:ListClusters": {}, - "ecs:ListServices": {}, - "ecs:ListServicesByNamespace": {}, - "ecs:ListTaskDefinitionFamilies": {}, - "ecs:ListTaskDefinitions": {}, - "ecs:PutAccountSetting": {}, - "ecs:PutAccountSettingDefault": {}, - "ecs:RegisterTaskDefinition": {}, - "eks:CreateCluster": {}, - "eks:CreateEksAnywhereSubscription": {}, - "eks:DescribeAddonConfiguration": {}, - "eks:DescribeAddonVersions": {}, - "eks:ListAccessPolicies": {}, - "eks:ListClusters": {}, - "eks:ListEksAnywhereSubscriptions": {}, - "eks:RegisterCluster": {}, - "elasticache:DescribeCacheEngineVersions": {}, - "elasticache:DescribeEngineDefaultParameters": {}, - "elasticache:DescribeEvents": {}, - "elasticache:DescribeReservedCacheNodesOfferings": {}, - "elasticache:DescribeServiceUpdates": {}, - "elasticbeanstalk:CheckDNSAvailability": {}, - "elasticbeanstalk:CreateStorageLocation": {}, - "elasticbeanstalk:DescribeAccountAttributes": {}, - "elasticbeanstalk:ListPlatformBranches": {}, - "elasticfilesystem:CreateFileSystem": {}, - "elasticfilesystem:DescribeAccountPreferences": {}, - "elasticfilesystem:PutAccountPreferences": {}, - "elasticloadbalancing:DescribeAccountLimits": {}, - "elasticloadbalancing:DescribeInstanceHealth": {}, - "elasticloadbalancing:DescribeListenerCertificates": {}, - "elasticloadbalancing:DescribeListeners": {}, - "elasticloadbalancing:DescribeLoadBalancerAttributes": {}, - "elasticloadbalancing:DescribeLoadBalancerPolicies": {}, - "elasticloadbalancing:DescribeLoadBalancerPolicyTypes": {}, - "elasticloadbalancing:DescribeLoadBalancers": {}, - "elasticloadbalancing:DescribeRules": {}, - "elasticloadbalancing:DescribeSSLPolicies": {}, - "elasticloadbalancing:DescribeTags": {}, - "elasticloadbalancing:DescribeTargetGroupAttributes": {}, - "elasticloadbalancing:DescribeTargetGroups": {}, - "elasticloadbalancing:DescribeTargetHealth": {}, - "elasticloadbalancing:DescribeTrustStoreAssociations": {}, - "elasticloadbalancing:DescribeTrustStoreRevocations": {}, - "elasticloadbalancing:DescribeTrustStores": {}, - "elasticloadbalancing:SetWebAcl": {}, - "elasticmapreduce:CreateRepository": {}, - "elasticmapreduce:CreateSecurityConfiguration": {}, - "elasticmapreduce:CreateStudio": {}, - "elasticmapreduce:DeleteRepository": {}, - "elasticmapreduce:DeleteSecurityConfiguration": {}, - "elasticmapreduce:DescribeReleaseLabel": {}, - "elasticmapreduce:DescribeRepository": {}, - "elasticmapreduce:DescribeSecurityConfiguration": {}, - "elasticmapreduce:GetBlockPublicAccessConfiguration": {}, - "elasticmapreduce:LinkRepository": {}, - "elasticmapreduce:ListClusters": {}, - "elasticmapreduce:ListEditors": {}, - "elasticmapreduce:ListNotebookExecutions": {}, - "elasticmapreduce:ListReleaseLabels": {}, - "elasticmapreduce:ListRepositories": {}, - "elasticmapreduce:ListSecurityConfigurations": {}, - "elasticmapreduce:ListStudioSessionMappings": {}, - "elasticmapreduce:ListStudios": {}, - "elasticmapreduce:ListSupportedInstanceTypes": {}, - "elasticmapreduce:PutBlockPublicAccessConfiguration": {}, - "elasticmapreduce:RunJobFlow": {}, - "elasticmapreduce:UnlinkRepository": {}, - "elasticmapreduce:UpdateRepository": {}, - "elasticmapreduce:ViewEventsFromAllClustersInConsole": {}, - "elastictranscoder:CreatePipeline": {}, - "elastictranscoder:CreatePreset": {}, - "elastictranscoder:ListJobsByStatus": {}, - "elastictranscoder:ListPipelines": {}, - "elastictranscoder:ListPresets": {}, - "elastictranscoder:TestRole": {}, - "elemental-activations:CompleteAccountRegistration": {}, - "elemental-activations:CompleteFileUpload": {}, - "elemental-activations:DownloadSoftware": {}, - "elemental-activations:GenerateLicenses": {}, - "elemental-activations:StartAccountRegistration": {}, - "elemental-activations:StartFileUpload": {}, - "elemental-appliances-software:CompleteUpload": {}, - "elemental-appliances-software:CreateOrderV1": {}, - "elemental-appliances-software:GetAvsCorrectAddress": {}, - "elemental-appliances-software:GetBillingAddresses": {}, - "elemental-appliances-software:GetDeliveryAddressesV2": {}, - "elemental-appliances-software:GetOrder": {}, - "elemental-appliances-software:GetOrdersV2": {}, - "elemental-appliances-software:GetTaxes": {}, - "elemental-appliances-software:ListQuotes": {}, - "elemental-appliances-software:StartUpload": {}, - "elemental-appliances-software:SubmitOrderV1": {}, - "elemental-support-cases:CheckCasePermission": {}, - "elemental-support-cases:CreateCase": {}, - "elemental-support-cases:GetCase": {}, - "elemental-support-cases:GetCases": {}, - "elemental-support-cases:UpdateCase": {}, - "elemental-support-content:Query": {}, - "emr-containers:CreateJobTemplate": {}, - "emr-containers:CreateVirtualCluster": {}, - "emr-containers:ListJobTemplates": {}, - "emr-containers:ListVirtualClusters": {}, - "emr-serverless:CreateApplication": {}, - "emr-serverless:ListApplications": {}, - "entityresolution:CreateIdMappingWorkflow": {}, - "entityresolution:CreateMatchingWorkflow": {}, - "entityresolution:CreateSchemaMapping": {}, - "entityresolution:ListIdMappingWorkflows": {}, - "entityresolution:ListMatchingWorkflows": {}, - "entityresolution:ListSchemaMappings": {}, - "entityresolution:ListTagsForResource": {}, - "entityresolution:TagResource": {}, - "entityresolution:UntagResource": {}, - "es:AcceptInboundConnection": {}, - "es:AcceptInboundCrossClusterSearchConnection": {}, - "es:AuthorizeVpcEndpointAccess": {}, - "es:CreateElasticsearchServiceRole": {}, - "es:CreatePackage": {}, - "es:CreateServiceRole": {}, - "es:CreateVpcEndpoint": {}, - "es:DeleteElasticsearchServiceRole": {}, - "es:DeleteInboundConnection": {}, - "es:DeleteInboundCrossClusterSearchConnection": {}, - "es:DeleteOutboundConnection": {}, - "es:DeleteOutboundCrossClusterSearchConnection": {}, - "es:DeletePackage": {}, - "es:DeleteVpcEndpoint": {}, - "es:DescribeElasticsearchInstanceTypeLimits": {}, - "es:DescribeInboundConnections": {}, - "es:DescribeInboundCrossClusterSearchConnections": {}, - "es:DescribeInstanceTypeLimits": {}, - "es:DescribeOutboundConnections": {}, - "es:DescribeOutboundCrossClusterSearchConnections": {}, - "es:DescribePackages": {}, - "es:DescribeReservedElasticsearchInstanceOfferings": {}, - "es:DescribeReservedElasticsearchInstances": {}, - "es:DescribeReservedInstanceOfferings": {}, - "es:DescribeReservedInstances": {}, - "es:DescribeVpcEndpoints": {}, - "es:GetPackageVersionHistory": {}, - "es:ListDomainNames": {}, - "es:ListDomainsForPackage": {}, - "es:ListElasticsearchInstanceTypeDetails": {}, - "es:ListElasticsearchInstanceTypes": {}, - "es:ListElasticsearchVersions": {}, - "es:ListInstanceTypeDetails": {}, - "es:ListVersions": {}, - "es:ListVpcEndpointAccess": {}, - "es:ListVpcEndpoints": {}, - "es:ListVpcEndpointsForDomain": {}, - "es:PurchaseReservedElasticsearchInstanceOffering": {}, - "es:PurchaseReservedInstanceOffering": {}, - "es:RejectInboundConnection": {}, - "es:RejectInboundCrossClusterSearchConnection": {}, - "es:RevokeVpcEndpointAccess": {}, - "es:UpdatePackage": {}, - "es:UpdateVpcEndpoint": {}, - "events:ListApiDestinations": {}, - "events:ListArchives": {}, - "events:ListConnections": {}, - "events:ListEndpoints": {}, - "events:ListEventBuses": {}, - "events:ListEventSources": {}, - "events:ListPartnerEventSources": {}, - "events:ListReplays": {}, - "events:ListRuleNamesByTarget": {}, - "events:ListRules": {}, - "events:PutPartnerEvents": {}, - "events:PutPermission": {}, - "events:RemovePermission": {}, - "events:TestEventPattern": {}, - "evidently:CreateExperiment": {}, - "evidently:CreateFeature": {}, - "evidently:CreateLaunch": {}, - "evidently:CreateProject": {}, - "evidently:CreateSegment": {}, - "evidently:ListExperiments": {}, - "evidently:ListFeatures": {}, - "evidently:ListLaunches": {}, - "evidently:ListProjects": {}, - "evidently:ListSegmentReferences": {}, - "evidently:ListSegments": {}, - "evidently:ListTagsForResource": {}, - "evidently:TestSegmentPattern": {}, - "finspace:CreateKxEnvironment": {}, - "finspace:ListKxEnvironments": {}, - "firehose:ListDeliveryStreams": {}, - "fis:GetTargetResourceType": {}, - "fis:ListActions": {}, - "fis:ListExperimentTemplates": {}, - "fis:ListExperiments": {}, - "fis:ListTargetResourceTypes": {}, - "fms:AssociateAdminAccount": {}, - "fms:AssociateThirdPartyFirewall": {}, - "fms:DeleteNotificationChannel": {}, - "fms:DisassociateAdminAccount": {}, - "fms:DisassociateThirdPartyFirewall": {}, - "fms:GetAdminAccount": {}, - "fms:GetAdminScope": {}, - "fms:GetNotificationChannel": {}, - "fms:GetThirdPartyFirewallAssociationStatus": {}, - "fms:ListAdminAccountsForOrganization": {}, - "fms:ListAdminsManagingAccount": {}, - "fms:ListAppsLists": {}, - "fms:ListDiscoveredResources": {}, - "fms:ListMemberAccounts": {}, - "fms:ListPolicies": {}, - "fms:ListProtocolsLists": {}, - "fms:ListResourceSets": {}, - "fms:ListThirdPartyFirewallFirewallPolicies": {}, - "fms:PutAdminAccount": {}, - "fms:PutNotificationChannel": {}, - "forecast:CreateAutoPredictor": {}, - "forecast:ListDatasetGroups": {}, - "forecast:ListDatasetImportJobs": {}, - "forecast:ListDatasets": {}, - "forecast:ListExplainabilities": {}, - "forecast:ListExplainabilityExports": {}, - "forecast:ListForecastExportJobs": {}, - "forecast:ListForecasts": {}, - "forecast:ListMonitors": {}, - "forecast:ListPredictorBacktestExportJobs": {}, - "forecast:ListPredictors": {}, - "forecast:ListWhatIfAnalyses": {}, - "forecast:ListWhatIfForecastExports": {}, - "forecast:ListWhatIfForecasts": {}, - "frauddetector:BatchCreateVariable": {}, - "frauddetector:CreateList": {}, - "frauddetector:CreateVariable": {}, - "frauddetector:GetKMSEncryptionKey": {}, - "frauddetector:PutKMSEncryptionKey": {}, - "freertos:CreateSubscription": {}, - "freertos:DescribeHardwarePlatform": {}, - "freertos:GetEmpPatchUrl": {}, - "freertos:GetSoftwareURL": {}, - "freertos:GetSoftwareURLForConfiguration": {}, - "freertos:GetSubscriptionBillingAmount": {}, - "freertos:ListFreeRTOSVersions": {}, - "freertos:ListHardwarePlatforms": {}, - "freertos:ListHardwareVendors": {}, - "freertos:ListSoftwareConfigurations": {}, - "freertos:ListSoftwarePatches": {}, - "freertos:ListSubscriptionEmails": {}, - "freertos:ListSubscriptions": {}, - "freertos:UpdateEmailRecipients": {}, - "freertos:VerifyEmail": {}, - "freetier:GetFreeTierAlertPreference": {}, - "freetier:GetFreeTierUsage": {}, - "freetier:PutFreeTierAlertPreference": {}, - "fsx:DescribeBackups": {}, - "fsx:DescribeDataRepositoryAssociations": {}, - "fsx:DescribeDataRepositoryTasks": {}, - "fsx:DescribeFileCaches": {}, - "fsx:DescribeFileSystems": {}, - "fsx:DescribeSharedVpcConfiguration": {}, - "fsx:DescribeSnapshots": {}, - "fsx:DescribeStorageVirtualMachines": {}, - "fsx:DescribeVolumes": {}, - "fsx:UpdateSharedVpcConfiguration": {}, - "gamelift:AcceptMatch": {}, - "gamelift:CreateAlias": {}, - "gamelift:CreateBuild": {}, - "gamelift:CreateFleet": {}, - "gamelift:CreateGameServerGroup": {}, - "gamelift:CreateGameSession": {}, - "gamelift:CreateGameSessionQueue": {}, - "gamelift:CreateLocation": {}, - "gamelift:CreateMatchmakingConfiguration": {}, - "gamelift:CreateMatchmakingRuleSet": {}, - "gamelift:CreatePlayerSession": {}, - "gamelift:CreatePlayerSessions": {}, - "gamelift:CreateScript": {}, - "gamelift:CreateVpcPeeringAuthorization": {}, - "gamelift:CreateVpcPeeringConnection": {}, - "gamelift:DeleteVpcPeeringAuthorization": {}, - "gamelift:DeleteVpcPeeringConnection": {}, - "gamelift:DescribeEC2InstanceLimits": {}, - "gamelift:DescribeFleetAttributes": {}, - "gamelift:DescribeFleetCapacity": {}, - "gamelift:DescribeFleetUtilization": {}, - "gamelift:DescribeGameSessionDetails": {}, - "gamelift:DescribeGameSessionPlacement": {}, - "gamelift:DescribeGameSessionQueues": {}, - "gamelift:DescribeGameSessions": {}, - "gamelift:DescribeMatchmaking": {}, - "gamelift:DescribeMatchmakingConfigurations": {}, - "gamelift:DescribeMatchmakingRuleSets": {}, - "gamelift:DescribePlayerSessions": {}, - "gamelift:DescribeVpcPeeringAuthorizations": {}, - "gamelift:DescribeVpcPeeringConnections": {}, - "gamelift:GetGameSessionLogUrl": {}, - "gamelift:ListAliases": {}, - "gamelift:ListBuilds": {}, - "gamelift:ListFleets": {}, - "gamelift:ListGameServerGroups": {}, - "gamelift:ListLocations": {}, - "gamelift:ListScripts": {}, - "gamelift:SearchGameSessions": {}, - "gamelift:StartMatchBackfill": {}, - "gamelift:StartMatchmaking": {}, - "gamelift:StopGameSessionPlacement": {}, - "gamelift:StopMatchmaking": {}, - "gamelift:UpdateGameSession": {}, - "gamelift:ValidateMatchmakingRuleSet": {}, - "glacier:GetDataRetrievalPolicy": {}, - "glacier:ListProvisionedCapacity": {}, - "glacier:ListVaults": {}, - "glacier:PurchaseProvisionedCapacity": {}, - "glacier:SetDataRetrievalPolicy": {}, - "globalaccelerator:AdvertiseByoipCidr": {}, - "globalaccelerator:CreateAccelerator": {}, - "globalaccelerator:CreateCrossAccountAttachment": {}, - "globalaccelerator:CreateCustomRoutingAccelerator": {}, - "globalaccelerator:DeprovisionByoipCidr": {}, - "globalaccelerator:ListAccelerators": {}, - "globalaccelerator:ListByoipCidrs": {}, - "globalaccelerator:ListCrossAccountAttachments": {}, - "globalaccelerator:ListCrossAccountResourceAccounts": {}, - "globalaccelerator:ListCrossAccountResources": {}, - "globalaccelerator:ListCustomRoutingAccelerators": {}, - "globalaccelerator:ListCustomRoutingPortMappingsByDestination": {}, - "globalaccelerator:ProvisionByoipCidr": {}, - "globalaccelerator:WithdrawByoipCidr": {}, - "glue:CheckSchemaVersionValidity": {}, - "glue:CreateClassifier": {}, - "glue:CreateCrawler": {}, - "glue:CreateCustomEntityType": {}, - "glue:CreateDataQualityRuleset": {}, - "glue:CreateDevEndpoint": {}, - "glue:CreateMLTransform": {}, - "glue:CreateScript": {}, - "glue:CreateSecurityConfiguration": {}, - "glue:CreateSession": {}, - "glue:DeleteClassifier": {}, - "glue:DeleteSecurityConfiguration": {}, - "glue:DeregisterDataPreview": {}, - "glue:GetClassifier": {}, - "glue:GetClassifiers": {}, - "glue:GetColumnStatisticsTaskRun": {}, - "glue:GetColumnStatisticsTaskRuns": {}, - "glue:GetCrawlerMetrics": {}, - "glue:GetCrawlers": {}, - "glue:GetDataPreviewStatement": {}, - "glue:GetDataflowGraph": {}, - "glue:GetDevEndpoints": {}, - "glue:GetJobBookmark": {}, - "glue:GetJobs": {}, - "glue:GetMapping": {}, - "glue:GetNotebookInstanceStatus": {}, - "glue:GetPlan": {}, - "glue:GetSecurityConfiguration": {}, - "glue:GetSecurityConfigurations": {}, - "glue:GetTriggers": {}, - "glue:GlueNotebookAuthorize": {}, - "glue:GlueNotebookRefreshCredentials": {}, - "glue:ListBlueprints": {}, - "glue:ListColumnStatisticsTaskRuns": {}, - "glue:ListCrawlers": {}, - "glue:ListCrawls": {}, - "glue:ListCustomEntityTypes": {}, - "glue:ListDevEndpoints": {}, - "glue:ListJobs": {}, - "glue:ListRegistries": {}, - "glue:ListSessions": {}, - "glue:ListTriggers": {}, - "glue:ListWorkflows": {}, - "glue:ResetJobBookmark": {}, - "glue:RunDataPreviewStatement": {}, - "glue:SendFeedback": {}, - "glue:StartCompletion": {}, - "glue:StartCrawlerSchedule": {}, - "glue:StartNotebook": {}, - "glue:StopCrawlerSchedule": {}, - "glue:TerminateNotebook": {}, - "glue:TestConnection": {}, - "glue:UpdateClassifier": {}, - "glue:UpdateCrawlerSchedule": {}, - "glue:UseGlueStudio": {}, - "grafana:CreateWorkspace": {}, - "grafana:ListWorkspaces": {}, - "greengrass:AssociateServiceRoleToAccount": {}, - "greengrass:CreateConnectorDefinition": {}, - "greengrass:CreateCoreDefinition": {}, - "greengrass:CreateDeployment": {}, - "greengrass:CreateDeviceDefinition": {}, - "greengrass:CreateFunctionDefinition": {}, - "greengrass:CreateGroup": {}, - "greengrass:CreateLoggerDefinition": {}, - "greengrass:CreateResourceDefinition": {}, - "greengrass:CreateSoftwareUpdateJob": {}, - "greengrass:CreateSubscriptionDefinition": {}, - "greengrass:DisassociateServiceRoleFromAccount": {}, - "greengrass:GetServiceRoleForAccount": {}, - "greengrass:ListBulkDeployments": {}, - "greengrass:ListComponents": {}, - "greengrass:ListConnectorDefinitions": {}, - "greengrass:ListCoreDefinitions": {}, - "greengrass:ListCoreDevices": {}, - "greengrass:ListDeployments": {}, - "greengrass:ListDeviceDefinitions": {}, - "greengrass:ListFunctionDefinitions": {}, - "greengrass:ListGroups": {}, - "greengrass:ListLoggerDefinitions": {}, - "greengrass:ListResourceDefinitions": {}, - "greengrass:ListSubscriptionDefinitions": {}, - "greengrass:StartBulkDeployment": {}, - "groundstation:CreateConfig": {}, - "groundstation:CreateDataflowEndpointGroup": {}, - "groundstation:CreateEphemeris": {}, - "groundstation:CreateMissionProfile": {}, - "groundstation:GetMinuteUsage": {}, - "groundstation:ListConfigs": {}, - "groundstation:ListContacts": {}, - "groundstation:ListDataflowEndpointGroups": {}, - "groundstation:ListEphemerides": {}, - "groundstation:ListGroundStations": {}, - "groundstation:ListMissionProfiles": {}, - "groundstation:ListSatellites": {}, - "groundstation:RegisterAgent": {}, - "groundstation:ReserveContact": {}, - "groundtruthlabeling:AssociatePatchToManifestJob": {}, - "groundtruthlabeling:DescribeConsoleJob": {}, - "groundtruthlabeling:ListDatasetObjects": {}, - "groundtruthlabeling:RunFilterOrSampleDatasetJob": {}, - "groundtruthlabeling:RunGenerateManifestByCrawlingJob": {}, - "guardduty:AcceptAdministratorInvitation": {}, - "guardduty:AcceptInvitation": {}, - "guardduty:ArchiveFindings": {}, - "guardduty:CreateDetector": {}, - "guardduty:CreateIPSet": {}, - "guardduty:CreateMembers": {}, - "guardduty:CreatePublishingDestination": {}, - "guardduty:CreateSampleFindings": {}, - "guardduty:CreateThreatIntelSet": {}, - "guardduty:DeclineInvitations": {}, - "guardduty:DeleteInvitations": {}, - "guardduty:DeleteMembers": {}, - "guardduty:DescribeMalwareScans": {}, - "guardduty:DescribeOrganizationConfiguration": {}, - "guardduty:DisableOrganizationAdminAccount": {}, - "guardduty:DisassociateFromAdministratorAccount": {}, - "guardduty:DisassociateFromMasterAccount": {}, - "guardduty:DisassociateMembers": {}, - "guardduty:EnableOrganizationAdminAccount": {}, - "guardduty:GetAdministratorAccount": {}, - "guardduty:GetFindings": {}, - "guardduty:GetFindingsStatistics": {}, - "guardduty:GetInvitationsCount": {}, - "guardduty:GetMalwareScanSettings": {}, - "guardduty:GetMasterAccount": {}, - "guardduty:GetMemberDetectors": {}, - "guardduty:GetMembers": {}, - "guardduty:GetOrganizationStatistics": {}, - "guardduty:GetRemainingFreeTrialDays": {}, - "guardduty:GetUsageStatistics": {}, - "guardduty:InviteMembers": {}, - "guardduty:ListDetectors": {}, - "guardduty:ListFilters": {}, - "guardduty:ListFindings": {}, - "guardduty:ListIPSets": {}, - "guardduty:ListInvitations": {}, - "guardduty:ListMembers": {}, - "guardduty:ListOrganizationAdminAccounts": {}, - "guardduty:ListPublishingDestinations": {}, - "guardduty:ListThreatIntelSets": {}, - "guardduty:SendSecurityTelemetry": {}, - "guardduty:StartMalwareScan": {}, - "guardduty:StartMonitoringMembers": {}, - "guardduty:StopMonitoringMembers": {}, - "guardduty:UnarchiveFindings": {}, - "guardduty:UpdateFindingsFeedback": {}, - "guardduty:UpdateMalwareScanSettings": {}, - "guardduty:UpdateMemberDetectors": {}, - "guardduty:UpdateOrganizationConfiguration": {}, - "health:DescribeAffectedAccountsForOrganization": {}, - "health:DescribeAffectedEntitiesForOrganization": {}, - "health:DescribeEntityAggregates": {}, - "health:DescribeEntityAggregatesForOrganization": {}, - "health:DescribeEventAggregates": {}, - "health:DescribeEventDetailsForOrganization": {}, - "health:DescribeEventTypes": {}, - "health:DescribeEvents": {}, - "health:DescribeEventsForOrganization": {}, - "health:DescribeHealthServiceStatusForOrganization": {}, - "health:DisableHealthServiceAccessForOrganization": {}, - "health:EnableHealthServiceAccessForOrganization": {}, - "healthlake:CreateFHIRDatastore": {}, - "healthlake:ListFHIRDatastores": {}, - "honeycode:ApproveTeamAssociation": {}, - "honeycode:CreateTeam": {}, - "honeycode:CreateTenant": {}, - "honeycode:DeleteDomains": {}, - "honeycode:DeregisterGroups": {}, - "honeycode:DescribeTeam": {}, - "honeycode:ListDomains": {}, - "honeycode:ListGroups": {}, - "honeycode:ListTagsForResource": {}, - "honeycode:ListTeamAssociations": {}, - "honeycode:ListTenants": {}, - "honeycode:RegisterDomainForVerification": {}, - "honeycode:RegisterGroups": {}, - "honeycode:RejectTeamAssociation": {}, - "honeycode:RestartDomainVerification": {}, - "honeycode:TagResource": {}, - "honeycode:UntagResource": {}, - "honeycode:UpdateTeam": {}, - "iam:CreateAccountAlias": {}, - "iam:DeleteAccountAlias": {}, - "iam:DeleteAccountPasswordPolicy": {}, - "iam:DeleteCloudFrontPublicKey": {}, - "iam:GenerateCredentialReport": {}, - "iam:GetAccountAuthorizationDetails": {}, - "iam:GetAccountEmailAddress": {}, - "iam:GetAccountName": {}, - "iam:GetAccountPasswordPolicy": {}, - "iam:GetAccountSummary": {}, - "iam:GetCloudFrontPublicKey": {}, - "iam:GetContextKeysForCustomPolicy": {}, - "iam:GetCredentialReport": {}, - "iam:GetOrganizationsAccessReport": {}, - "iam:GetServiceLastAccessedDetails": {}, - "iam:GetServiceLastAccessedDetailsWithEntities": {}, - "iam:ListAccountAliases": {}, - "iam:ListCloudFrontPublicKeys": {}, - "iam:ListGroups": {}, - "iam:ListInstanceProfiles": {}, - "iam:ListOpenIDConnectProviders": {}, - "iam:ListPolicies": {}, - "iam:ListRoles": {}, - "iam:ListSAMLProviders": {}, - "iam:ListSTSRegionalEndpointsStatus": {}, - "iam:ListServerCertificates": {}, - "iam:ListUsers": {}, - "iam:ListVirtualMFADevices": {}, - "iam:SetSTSRegionalEndpointStatus": {}, - "iam:SetSecurityTokenServicePreferences": {}, - "iam:SimulateCustomPolicy": {}, - "iam:UpdateAccountEmailAddress": {}, - "iam:UpdateAccountName": {}, - "iam:UpdateAccountPasswordPolicy": {}, - "iam:UpdateCloudFrontPublicKey": {}, - "iam:UploadCloudFrontPublicKey": {}, - "identity-sync:CreateSyncProfile": {}, - "identitystore-auth:BatchDeleteSession": {}, - "identitystore-auth:BatchGetSession": {}, - "identitystore-auth:ListSessions": {}, - "imagebuilder:ListComponents": {}, - "imagebuilder:ListContainerRecipes": {}, - "imagebuilder:ListDistributionConfigurations": {}, - "imagebuilder:ListImagePipelines": {}, - "imagebuilder:ListImageRecipes": {}, - "imagebuilder:ListImages": {}, - "imagebuilder:ListInfrastructureConfigurations": {}, - "imagebuilder:ListLifecyclePolicies": {}, - "imagebuilder:ListWaitingWorkflowSteps": {}, - "imagebuilder:ListWorkflows": {}, - "importexport:CancelJob": {}, - "importexport:CreateJob": {}, - "importexport:GetShippingLabel": {}, - "importexport:GetStatus": {}, - "importexport:ListJobs": {}, - "importexport:UpdateJob": {}, - "inspector-scan:ScanSbom": {}, - "inspector2:AssociateMember": {}, - "inspector2:BatchGetAccountStatus": {}, - "inspector2:BatchGetCodeSnippet": {}, - "inspector2:BatchGetFindingDetails": {}, - "inspector2:BatchGetFreeTrialInfo": {}, - "inspector2:BatchGetMemberEc2DeepInspectionStatus": {}, - "inspector2:BatchUpdateMemberEc2DeepInspectionStatus": {}, - "inspector2:CancelFindingsReport": {}, - "inspector2:CancelSbomExport": {}, - "inspector2:CreateFindingsReport": {}, - "inspector2:CreateSbomExport": {}, - "inspector2:DescribeOrganizationConfiguration": {}, - "inspector2:Disable": {}, - "inspector2:DisableDelegatedAdminAccount": {}, - "inspector2:DisassociateMember": {}, - "inspector2:Enable": {}, - "inspector2:EnableDelegatedAdminAccount": {}, - "inspector2:GetCisScanReport": {}, - "inspector2:GetCisScanResultDetails": {}, - "inspector2:GetConfiguration": {}, - "inspector2:GetDelegatedAdminAccount": {}, - "inspector2:GetEc2DeepInspectionConfiguration": {}, - "inspector2:GetEncryptionKey": {}, - "inspector2:GetFindingsReportStatus": {}, - "inspector2:GetMember": {}, - "inspector2:GetSbomExport": {}, - "inspector2:ListAccountPermissions": {}, - "inspector2:ListCisScanConfigurations": {}, - "inspector2:ListCisScanResultsAggregatedByChecks": {}, - "inspector2:ListCisScanResultsAggregatedByTargetResource": {}, - "inspector2:ListCisScans": {}, - "inspector2:ListCoverage": {}, - "inspector2:ListCoverageStatistics": {}, - "inspector2:ListDelegatedAdminAccounts": {}, - "inspector2:ListFilters": {}, - "inspector2:ListFindingAggregations": {}, - "inspector2:ListFindings": {}, - "inspector2:ListMembers": {}, - "inspector2:ListTagsForResource": {}, - "inspector2:ListUsageTotals": {}, - "inspector2:ResetEncryptionKey": {}, - "inspector2:SearchVulnerabilities": {}, - "inspector2:SendCisSessionHealth": {}, - "inspector2:SendCisSessionTelemetry": {}, - "inspector2:StartCisSession": {}, - "inspector2:StopCisSession": {}, - "inspector2:UpdateConfiguration": {}, - "inspector2:UpdateEc2DeepInspectionConfiguration": {}, - "inspector2:UpdateEncryptionKey": {}, - "inspector2:UpdateOrgEc2DeepInspectionConfiguration": {}, - "inspector2:UpdateOrganizationConfiguration": {}, - "inspector:AddAttributesToFindings": {}, - "inspector:CreateAssessmentTarget": {}, - "inspector:CreateAssessmentTemplate": {}, - "inspector:CreateExclusionsPreview": {}, - "inspector:CreateResourceGroup": {}, - "inspector:DeleteAssessmentRun": {}, - "inspector:DeleteAssessmentTarget": {}, - "inspector:DeleteAssessmentTemplate": {}, - "inspector:DescribeAssessmentRuns": {}, - "inspector:DescribeAssessmentTargets": {}, - "inspector:DescribeAssessmentTemplates": {}, - "inspector:DescribeCrossAccountAccessRole": {}, - "inspector:DescribeExclusions": {}, - "inspector:DescribeFindings": {}, - "inspector:DescribeResourceGroups": {}, - "inspector:DescribeRulesPackages": {}, - "inspector:GetAssessmentReport": {}, - "inspector:GetExclusionsPreview": {}, - "inspector:GetTelemetryMetadata": {}, - "inspector:ListAssessmentRunAgents": {}, - "inspector:ListAssessmentRuns": {}, - "inspector:ListAssessmentTargets": {}, - "inspector:ListAssessmentTemplates": {}, - "inspector:ListEventSubscriptions": {}, - "inspector:ListExclusions": {}, - "inspector:ListFindings": {}, - "inspector:ListRulesPackages": {}, - "inspector:ListTagsForResource": {}, - "inspector:PreviewAgents": {}, - "inspector:RegisterCrossAccountAccessRole": {}, - "inspector:RemoveAttributesFromFindings": {}, - "inspector:SetTagsForResource": {}, - "inspector:StartAssessmentRun": {}, - "inspector:StopAssessmentRun": {}, - "inspector:SubscribeToEvent": {}, - "inspector:UnsubscribeFromEvent": {}, - "inspector:UpdateAssessmentTarget": {}, - "internetmonitor:ListMonitors": {}, - "invoicing:GetInvoiceEmailDeliveryPreferences": {}, - "invoicing:GetInvoicePDF": {}, - "invoicing:ListInvoiceSummaries": {}, - "invoicing:PutInvoiceEmailDeliveryPreferences": {}, - "iot-device-tester:CheckVersion": {}, - "iot-device-tester:DownloadTestSuite": {}, - "iot-device-tester:LatestIdt": {}, - "iot-device-tester:SendMetrics": {}, - "iot-device-tester:SupportedVersion": {}, - "iot1click:ClaimDevicesByClaimCode": {}, - "iot1click:ListDevices": {}, - "iot1click:ListProjects": {}, - "iot:AttachThingPrincipal": {}, - "iot:CancelAuditMitigationActionsTask": {}, - "iot:CancelAuditTask": {}, - "iot:CancelDetectMitigationActionsTask": {}, - "iot:ClearDefaultAuthorizer": {}, - "iot:CreateAuditSuppression": {}, - "iot:CreateCertificateFromCsr": {}, - "iot:CreateKeysAndCertificate": {}, - "iot:DeleteAccountAuditConfiguration": {}, - "iot:DeleteAuditSuppression": {}, - "iot:DeleteRegistrationCode": {}, - "iot:DeleteV2LoggingLevel": {}, - "iot:DescribeAccountAuditConfiguration": {}, - "iot:DescribeAuditFinding": {}, - "iot:DescribeAuditMitigationActionsTask": {}, - "iot:DescribeAuditSuppression": {}, - "iot:DescribeAuditTask": {}, - "iot:DescribeDefaultAuthorizer": {}, - "iot:DescribeDetectMitigationActionsTask": {}, - "iot:DescribeEndpoint": {}, - "iot:DescribeEventConfigurations": {}, - "iot:DescribeThingRegistrationTask": {}, - "iot:DetachThingPrincipal": {}, - "iot:GetIndexingConfiguration": {}, - "iot:GetLoggingOptions": {}, - "iot:GetPackageConfiguration": {}, - "iot:GetRegistrationCode": {}, - "iot:GetV2LoggingOptions": {}, - "iot:ListAttachedPolicies": {}, - "iot:ListAuditFindings": {}, - "iot:ListAuditMitigationActionsExecutions": {}, - "iot:ListAuditMitigationActionsTasks": {}, - "iot:ListAuditSuppressions": {}, - "iot:ListAuditTasks": {}, - "iot:ListAuthorizers": {}, - "iot:ListBillingGroups": {}, - "iot:ListCACertificates": {}, - "iot:ListCertificateProviders": {}, - "iot:ListCertificates": {}, - "iot:ListCertificatesByCA": {}, - "iot:ListCustomMetrics": {}, - "iot:ListDetectMitigationActionsTasks": {}, - "iot:ListDimensions": {}, - "iot:ListDomainConfigurations": {}, - "iot:ListFleetMetrics": {}, - "iot:ListIndices": {}, - "iot:ListJobTemplates": {}, - "iot:ListJobs": {}, - "iot:ListManagedJobTemplates": {}, - "iot:ListMitigationActions": {}, - "iot:ListOTAUpdates": {}, - "iot:ListOutgoingCertificates": {}, - "iot:ListPackageVersions": {}, - "iot:ListPackages": {}, - "iot:ListPolicies": {}, - "iot:ListPolicyPrincipals": {}, - "iot:ListPrincipalPolicies": {}, - "iot:ListPrincipalThings": {}, - "iot:ListProvisioningTemplates": {}, - "iot:ListRelatedResourcesForAuditFinding": {}, - "iot:ListRetainedMessages": {}, - "iot:ListRoleAliases": {}, - "iot:ListScheduledAudits": {}, - "iot:ListStreams": {}, - "iot:ListThingGroups": {}, - "iot:ListThingPrincipals": {}, - "iot:ListThingRegistrationTaskReports": {}, - "iot:ListThingRegistrationTasks": {}, - "iot:ListThingTypes": {}, - "iot:ListThings": {}, - "iot:ListTopicRuleDestinations": {}, - "iot:ListTopicRules": {}, - "iot:ListTunnels": {}, - "iot:ListV2LoggingLevels": {}, - "iot:OpenTunnel": {}, - "iot:PutVerificationStateOnViolation": {}, - "iot:RegisterCACertificate": {}, - "iot:RegisterCertificate": {}, - "iot:RegisterCertificateWithoutCA": {}, - "iot:RegisterThing": {}, - "iot:SetLoggingOptions": {}, - "iot:SetV2LoggingLevel": {}, - "iot:SetV2LoggingOptions": {}, - "iot:StartAuditMitigationActionsTask": {}, - "iot:StartOnDemandAuditTask": {}, - "iot:StartThingRegistrationTask": {}, - "iot:StopThingRegistrationTask": {}, - "iot:UpdateAccountAuditConfiguration": {}, - "iot:UpdateAuditSuppression": {}, - "iot:UpdateEventConfigurations": {}, - "iot:UpdateIndexingConfiguration": {}, - "iot:UpdatePackageConfiguration": {}, - "iot:ValidateSecurityProfileBehaviors": {}, - "iotanalytics:DescribeLoggingOptions": {}, - "iotanalytics:ListChannels": {}, - "iotanalytics:ListDatasets": {}, - "iotanalytics:ListDatastores": {}, - "iotanalytics:ListPipelines": {}, - "iotanalytics:PutLoggingOptions": {}, - "iotanalytics:RunPipelineActivity": {}, - "iotdeviceadvisor:CreateSuiteDefinition": {}, - "iotdeviceadvisor:GetEndpoint": {}, - "iotdeviceadvisor:ListSuiteDefinitions": {}, - "iotdeviceadvisor:StartSuiteRun": {}, - "iotevents:DescribeDetectorModelAnalysis": {}, - "iotevents:DescribeLoggingOptions": {}, - "iotevents:GetDetectorModelAnalysisResults": {}, - "iotevents:ListAlarmModels": {}, - "iotevents:ListDetectorModels": {}, - "iotevents:ListInputRoutings": {}, - "iotevents:ListInputs": {}, - "iotevents:PutLoggingOptions": {}, - "iotevents:StartDetectorModelAnalysis": {}, - "iotfleethub:CreateApplication": {}, - "iotfleethub:ListApplications": {}, - "iotfleetwise:GetEncryptionConfiguration": {}, - "iotfleetwise:GetLoggingOptions": {}, - "iotfleetwise:GetRegisterAccountStatus": {}, - "iotfleetwise:ListCampaigns": {}, - "iotfleetwise:ListDecoderManifests": {}, - "iotfleetwise:ListFleets": {}, - "iotfleetwise:ListModelManifests": {}, - "iotfleetwise:ListSignalCatalogs": {}, - "iotfleetwise:ListVehicles": {}, - "iotfleetwise:PutEncryptionConfiguration": {}, - "iotfleetwise:PutLoggingOptions": {}, - "iotfleetwise:RegisterAccount": {}, - "iotroborunner:CreateSite": {}, - "iotroborunner:ListSites": {}, - "iotsitewise:CreateAssetModel": {}, - "iotsitewise:CreateBulkImportJob": {}, - "iotsitewise:CreateGateway": {}, - "iotsitewise:CreatePortal": {}, - "iotsitewise:DescribeBulkImportJob": {}, - "iotsitewise:DescribeDefaultEncryptionConfiguration": {}, - "iotsitewise:DescribeLoggingOptions": {}, - "iotsitewise:DescribeStorageConfiguration": {}, - "iotsitewise:EnableSiteWiseIntegration": {}, - "iotsitewise:ExecuteQuery": {}, - "iotsitewise:ListAssetModels": {}, - "iotsitewise:ListBulkImportJobs": {}, - "iotsitewise:ListGateways": {}, - "iotsitewise:ListPortals": {}, - "iotsitewise:PutDefaultEncryptionConfiguration": {}, - "iotsitewise:PutLoggingOptions": {}, - "iotsitewise:PutStorageConfiguration": {}, - "iottwinmaker:CreateMetadataTransferJob": {}, - "iottwinmaker:CreateWorkspace": {}, - "iottwinmaker:GetPricingPlan": {}, - "iottwinmaker:ListMetadataTransferJobs": {}, - "iottwinmaker:ListWorkspaces": {}, - "iottwinmaker:UpdatePricingPlan": {}, - "iotwireless:AssociateAwsAccountWithPartnerAccount": {}, - "iotwireless:CreateDestination": {}, - "iotwireless:CreateDeviceProfile": {}, - "iotwireless:CreateFuotaTask": {}, - "iotwireless:CreateMulticastGroup": {}, - "iotwireless:CreateServiceProfile": {}, - "iotwireless:CreateWirelessDevice": {}, - "iotwireless:CreateWirelessGateway": {}, - "iotwireless:CreateWirelessGatewayTaskDefinition": {}, - "iotwireless:DeleteQueuedMessages": {}, - "iotwireless:GetEventConfigurationByResourceTypes": {}, - "iotwireless:GetLogLevelsByResourceTypes": {}, - "iotwireless:GetPositionEstimate": {}, - "iotwireless:GetServiceEndpoint": {}, - "iotwireless:ListDestinations": {}, - "iotwireless:ListDeviceProfiles": {}, - "iotwireless:ListEventConfigurations": {}, - "iotwireless:ListFuotaTasks": {}, - "iotwireless:ListMulticastGroups": {}, - "iotwireless:ListNetworkAnalyzerConfigurations": {}, - "iotwireless:ListPartnerAccounts": {}, - "iotwireless:ListPositionConfigurations": {}, - "iotwireless:ListQueuedMessages": {}, - "iotwireless:ListServiceProfiles": {}, - "iotwireless:ListWirelessDeviceImportTasks": {}, - "iotwireless:ListWirelessDevices": {}, - "iotwireless:ListWirelessGatewayTaskDefinitions": {}, - "iotwireless:ListWirelessGateways": {}, - "iotwireless:ResetAllResourceLogLevels": {}, - "iotwireless:StartSingleWirelessDeviceImportTask": {}, - "iotwireless:UpdateEventConfigurationByResourceTypes": {}, - "iotwireless:UpdateLogLevelsByResourceTypes": {}, - "iq:span": {}, - "ivs:ListEncoderConfigurations": {}, - "ivs:ListPlaybackRestrictionPolicies": {}, - "ivs:ListStorageConfigurations": {}, - "kafka:DescribeClusterOperation": {}, - "kafka:DescribeClusterOperationV2": {}, - "kafka:GetBootstrapBrokers": {}, - "kafka:GetCompatibleKafkaVersions": {}, - "kafka:ListClusters": {}, - "kafka:ListClustersV2": {}, - "kafka:ListConfigurations": {}, - "kafka:ListKafkaVersions": {}, - "kafka:ListReplicators": {}, - "kafka:ListVpcConnections": {}, - "kafkaconnect:CreateConnector": {}, - "kafkaconnect:CreateCustomPlugin": {}, - "kafkaconnect:CreateWorkerConfiguration": {}, - "kafkaconnect:DeleteConnector": {}, - "kafkaconnect:DeleteCustomPlugin": {}, - "kafkaconnect:ListConnectors": {}, - "kafkaconnect:ListCustomPlugins": {}, - "kafkaconnect:ListWorkerConfigurations": {}, - "kafkaconnect:UpdateConnector": {}, - "kendra-ranking:CreateRescoreExecutionPlan": {}, - "kendra-ranking:ListRescoreExecutionPlans": {}, - "kendra:CreateIndex": {}, - "kendra:ListIndices": {}, - "kinesis:DescribeLimits": {}, - "kinesis:DisableEnhancedMonitoring": {}, - "kinesis:EnableEnhancedMonitoring": {}, - "kinesis:ListStreams": {}, - "kinesis:UpdateShardCount": {}, - "kinesis:UpdateStreamMode": {}, - "kinesisanalytics:CreateApplication": {}, - "kinesisanalytics:DiscoverInputSchema": {}, - "kinesisanalytics:ListApplications": {}, - "kinesisvideo:ListEdgeAgentConfigurations": {}, - "kinesisvideo:ListSignalingChannels": {}, - "kinesisvideo:ListStreams": {}, - "kms:ConnectCustomKeyStore": {}, - "kms:CreateCustomKeyStore": {}, - "kms:CreateKey": {}, - "kms:DeleteCustomKeyStore": {}, - "kms:DescribeCustomKeyStores": {}, - "kms:DisconnectCustomKeyStore": {}, - "kms:GenerateRandom": {}, - "kms:ListAliases": {}, - "kms:ListKeys": {}, - "kms:ListRetirableGrants": {}, - "kms:UpdateCustomKeyStore": {}, - "lakeformation:AddLFTagsToResource": {}, - "lakeformation:BatchGrantPermissions": {}, - "lakeformation:BatchRevokePermissions": {}, - "lakeformation:CancelTransaction": {}, - "lakeformation:CommitTransaction": {}, - "lakeformation:CreateDataCellsFilter": {}, - "lakeformation:CreateLFTag": {}, - "lakeformation:CreateLakeFormationIdentityCenterConfiguration": {}, - "lakeformation:CreateLakeFormationOptIn": {}, - "lakeformation:DeleteDataCellsFilter": {}, - "lakeformation:DeleteLFTag": {}, - "lakeformation:DeleteLakeFormationIdentityCenterConfiguration": {}, - "lakeformation:DeleteLakeFormationOptIn": {}, - "lakeformation:DeleteObjectsOnCancel": {}, - "lakeformation:DeregisterResource": {}, - "lakeformation:DescribeLakeFormationIdentityCenterConfiguration": {}, - "lakeformation:DescribeResource": {}, - "lakeformation:DescribeTransaction": {}, - "lakeformation:ExtendTransaction": {}, - "lakeformation:GetDataAccess": {}, - "lakeformation:GetDataCellsFilter": {}, - "lakeformation:GetDataLakeSettings": {}, - "lakeformation:GetEffectivePermissionsForPath": {}, - "lakeformation:GetLFTag": {}, - "lakeformation:GetQueryState": {}, - "lakeformation:GetQueryStatistics": {}, - "lakeformation:GetResourceLFTags": {}, - "lakeformation:GetTableObjects": {}, - "lakeformation:GetWorkUnitResults": {}, - "lakeformation:GetWorkUnits": {}, - "lakeformation:GrantPermissions": {}, - "lakeformation:ListDataCellsFilter": {}, - "lakeformation:ListLFTags": {}, - "lakeformation:ListLakeFormationOptIns": {}, - "lakeformation:ListPermissions": {}, - "lakeformation:ListResources": {}, - "lakeformation:ListTableStorageOptimizers": {}, - "lakeformation:ListTransactions": {}, - "lakeformation:PutDataLakeSettings": {}, - "lakeformation:RegisterResource": {}, - "lakeformation:RemoveLFTagsFromResource": {}, - "lakeformation:RevokePermissions": {}, - "lakeformation:SearchDatabasesByLFTags": {}, - "lakeformation:SearchTablesByLFTags": {}, - "lakeformation:StartQueryPlanning": {}, - "lakeformation:StartTransaction": {}, - "lakeformation:UpdateDataCellsFilter": {}, - "lakeformation:UpdateLFTag": {}, - "lakeformation:UpdateLakeFormationIdentityCenterConfiguration": {}, - "lakeformation:UpdateResource": {}, - "lakeformation:UpdateTableObjects": {}, - "lakeformation:UpdateTableStorageOptimizer": {}, - "lambda:CreateCodeSigningConfig": {}, - "lambda:CreateEventSourceMapping": {}, - "lambda:GetAccountSettings": {}, - "lambda:ListCodeSigningConfigs": {}, - "lambda:ListEventSourceMappings": {}, - "lambda:ListFunctions": {}, - "lambda:ListLayerVersions": {}, - "lambda:ListLayers": {}, - "launchwizard:CreateAdditionalNode": {}, - "launchwizard:CreateDeployment": {}, - "launchwizard:CreateSettingsSet": {}, - "launchwizard:DeleteAdditionalNode": {}, - "launchwizard:DeleteApp": {}, - "launchwizard:DeleteDeployment": {}, - "launchwizard:DeleteSettingsSet": {}, - "launchwizard:DescribeAdditionalNode": {}, - "launchwizard:DescribeProvisionedApp": {}, - "launchwizard:DescribeProvisioningEvents": {}, - "launchwizard:DescribeSettingsSet": {}, - "launchwizard:GetDeployment": {}, - "launchwizard:GetInfrastructureSuggestion": {}, - "launchwizard:GetIpAddress": {}, - "launchwizard:GetResourceCostEstimate": {}, - "launchwizard:GetResourceRecommendation": {}, - "launchwizard:GetSettingsSet": {}, - "launchwizard:GetWorkload": {}, - "launchwizard:GetWorkloadAsset": {}, - "launchwizard:GetWorkloadAssets": {}, - "launchwizard:ListAdditionalNodes": {}, - "launchwizard:ListAllowedResources": {}, - "launchwizard:ListDeploymentEvents": {}, - "launchwizard:ListDeployments": {}, - "launchwizard:ListProvisionedApps": {}, - "launchwizard:ListResourceCostEstimates": {}, - "launchwizard:ListSettingsSets": {}, - "launchwizard:ListWorkloadDeploymentOptions": {}, - "launchwizard:ListWorkloadDeploymentPatterns": {}, - "launchwizard:ListWorkloads": {}, - "launchwizard:PutSettingsSet": {}, - "launchwizard:StartProvisioning": {}, - "launchwizard:UpdateSettingsSet": {}, - "lex:CreateTestSet": {}, - "lex:CreateUploadUrl": {}, - "lex:GetBotAliases": {}, - "lex:GetBots": {}, - "lex:GetBuiltinIntent": {}, - "lex:GetBuiltinIntents": {}, - "lex:GetBuiltinSlotTypes": {}, - "lex:GetImport": {}, - "lex:GetIntents": {}, - "lex:GetMigration": {}, - "lex:GetMigrations": {}, - "lex:GetSlotTypes": {}, - "lex:ListBots": {}, - "lex:ListBuiltInIntents": {}, - "lex:ListBuiltInSlotTypes": {}, - "lex:ListExports": {}, - "lex:ListImports": {}, - "lex:ListTestExecutions": {}, - "lex:ListTestSets": {}, - "lex:StartImport": {}, - "license-manager-linux-subscriptions:GetServiceSettings": {}, - "license-manager-linux-subscriptions:ListLinuxSubscriptionInstances": {}, - "license-manager-linux-subscriptions:ListLinuxSubscriptions": {}, - "license-manager-linux-subscriptions:UpdateServiceSettings": {}, - "license-manager-user-subscriptions:AssociateUser": {}, - "license-manager-user-subscriptions:DeregisterIdentityProvider": {}, - "license-manager-user-subscriptions:DisassociateUser": {}, - "license-manager-user-subscriptions:ListIdentityProviders": {}, - "license-manager-user-subscriptions:ListInstances": {}, - "license-manager-user-subscriptions:ListProductSubscriptions": {}, - "license-manager-user-subscriptions:ListUserAssociations": {}, - "license-manager-user-subscriptions:RegisterIdentityProvider": {}, - "license-manager-user-subscriptions:StartProductSubscription": {}, - "license-manager-user-subscriptions:StopProductSubscription": {}, - "license-manager-user-subscriptions:UpdateIdentityProviderSettings": {}, - "license-manager:CheckInLicense": {}, - "license-manager:CheckoutLicense": {}, - "license-manager:CreateLicense": {}, - "license-manager:CreateLicenseConfiguration": {}, - "license-manager:CreateLicenseConversionTaskForResource": {}, - "license-manager:CreateLicenseManagerReportGenerator": {}, - "license-manager:DeleteToken": {}, - "license-manager:ExtendLicenseConsumption": {}, - "license-manager:GetAccessToken": {}, - "license-manager:GetLicenseConversionTask": {}, - "license-manager:GetServiceSettings": {}, - "license-manager:ListDistributedGrants": {}, - "license-manager:ListLicenseConfigurations": {}, - "license-manager:ListLicenseConversionTasks": {}, - "license-manager:ListLicenseSpecificationsForResource": {}, - "license-manager:ListLicenses": {}, - "license-manager:ListReceivedGrants": {}, - "license-manager:ListReceivedGrantsForOrganization": {}, - "license-manager:ListReceivedLicenses": {}, - "license-manager:ListReceivedLicensesForOrganization": {}, - "license-manager:ListResourceInventory": {}, - "license-manager:ListTokens": {}, - "license-manager:UpdateServiceSettings": {}, - "lightsail:AllocateStaticIp": {}, - "lightsail:CopySnapshot": {}, - "lightsail:CreateBucket": {}, - "lightsail:CreateCertificate": {}, - "lightsail:CreateCloudFormationStack": {}, - "lightsail:CreateContactMethod": {}, - "lightsail:CreateContainerService": {}, - "lightsail:CreateContainerServiceRegistryLogin": {}, - "lightsail:CreateDisk": {}, - "lightsail:CreateDistribution": {}, - "lightsail:CreateDomain": {}, - "lightsail:CreateInstances": {}, - "lightsail:CreateKeyPair": {}, - "lightsail:CreateLoadBalancer": {}, - "lightsail:CreateRelationalDatabase": {}, - "lightsail:CreateRelationalDatabaseSnapshot": {}, - "lightsail:DeleteAutoSnapshot": {}, - "lightsail:DeleteContactMethod": {}, - "lightsail:DisableAddOn": {}, - "lightsail:DownloadDefaultKeyPair": {}, - "lightsail:EnableAddOn": {}, - "lightsail:GetActiveNames": {}, - "lightsail:GetAlarms": {}, - "lightsail:GetAutoSnapshots": {}, - "lightsail:GetBlueprints": {}, - "lightsail:GetBucketAccessKeys": {}, - "lightsail:GetBucketBundles": {}, - "lightsail:GetBucketMetricData": {}, - "lightsail:GetBuckets": {}, - "lightsail:GetBundles": {}, - "lightsail:GetCertificates": {}, - "lightsail:GetCloudFormationStackRecords": {}, - "lightsail:GetContactMethods": {}, - "lightsail:GetContainerAPIMetadata": {}, - "lightsail:GetContainerImages": {}, - "lightsail:GetContainerLog": {}, - "lightsail:GetContainerServiceDeployments": {}, - "lightsail:GetContainerServiceMetricData": {}, - "lightsail:GetContainerServicePowers": {}, - "lightsail:GetContainerServices": {}, - "lightsail:GetDisk": {}, - "lightsail:GetDiskSnapshot": {}, - "lightsail:GetDiskSnapshots": {}, - "lightsail:GetDisks": {}, - "lightsail:GetDistributionBundles": {}, - "lightsail:GetDistributionLatestCacheReset": {}, - "lightsail:GetDistributionMetricData": {}, - "lightsail:GetDistributions": {}, - "lightsail:GetDomain": {}, - "lightsail:GetDomains": {}, - "lightsail:GetExportSnapshotRecords": {}, - "lightsail:GetInstance": {}, - "lightsail:GetInstanceMetricData": {}, - "lightsail:GetInstancePortStates": {}, - "lightsail:GetInstanceSnapshot": {}, - "lightsail:GetInstanceSnapshots": {}, - "lightsail:GetInstanceState": {}, - "lightsail:GetInstances": {}, - "lightsail:GetKeyPair": {}, - "lightsail:GetKeyPairs": {}, - "lightsail:GetLoadBalancer": {}, - "lightsail:GetLoadBalancerMetricData": {}, - "lightsail:GetLoadBalancerTlsCertificates": {}, - "lightsail:GetLoadBalancerTlsPolicies": {}, - "lightsail:GetLoadBalancers": {}, - "lightsail:GetOperation": {}, - "lightsail:GetOperations": {}, - "lightsail:GetOperationsForResource": {}, - "lightsail:GetRegions": {}, - "lightsail:GetRelationalDatabase": {}, - "lightsail:GetRelationalDatabaseBlueprints": {}, - "lightsail:GetRelationalDatabaseBundles": {}, - "lightsail:GetRelationalDatabaseEvents": {}, - "lightsail:GetRelationalDatabaseLogEvents": {}, - "lightsail:GetRelationalDatabaseLogStreams": {}, - "lightsail:GetRelationalDatabaseMetricData": {}, - "lightsail:GetRelationalDatabaseParameters": {}, - "lightsail:GetRelationalDatabaseSnapshot": {}, - "lightsail:GetRelationalDatabaseSnapshots": {}, - "lightsail:GetRelationalDatabases": {}, - "lightsail:GetStaticIp": {}, - "lightsail:GetStaticIps": {}, - "lightsail:ImportKeyPair": {}, - "lightsail:IsVpcPeered": {}, - "lightsail:PeerVpc": {}, - "lightsail:SendContactMethodVerification": {}, - "lightsail:UnpeerVpc": {}, - "logs:CancelExportTask": {}, - "logs:CreateLogDelivery": {}, - "logs:DeleteAccountPolicy": {}, - "logs:DeleteLogDelivery": {}, - "logs:DeleteQueryDefinition": {}, - "logs:DeleteResourcePolicy": {}, - "logs:DescribeAccountPolicies": {}, - "logs:DescribeDeliveries": {}, - "logs:DescribeDeliveryDestinations": {}, - "logs:DescribeDeliverySources": {}, - "logs:DescribeDestinations": {}, - "logs:DescribeExportTasks": {}, - "logs:DescribeLogGroups": {}, - "logs:DescribeQueries": {}, - "logs:DescribeQueryDefinitions": {}, - "logs:DescribeResourcePolicies": {}, - "logs:GetLogDelivery": {}, - "logs:Link": {}, - "logs:ListLogDeliveries": {}, - "logs:PutAccountPolicy": {}, - "logs:PutQueryDefinition": {}, - "logs:PutResourcePolicy": {}, - "logs:StopLiveTail": {}, - "logs:StopQuery": {}, - "logs:TestMetricFilter": {}, - "logs:UpdateLogDelivery": {}, - "lookoutequipment:DescribeDataIngestionJob": {}, - "lookoutequipment:ListDatasets": {}, - "lookoutequipment:ListInferenceSchedulers": {}, - "lookoutequipment:ListModels": {}, - "lookoutequipment:ListRetrainingSchedulers": {}, - "lookoutmetrics:GetSampleData": {}, - "lookoutmetrics:ListAnomalyDetectors": {}, - "lookoutvision:CreateDataset": {}, - "lookoutvision:DeleteDataset": {}, - "lookoutvision:DescribeDataset": {}, - "lookoutvision:DescribeModelPackagingJob": {}, - "lookoutvision:DescribeTrialDetection": {}, - "lookoutvision:ListDatasetEntries": {}, - "lookoutvision:ListModelPackagingJobs": {}, - "lookoutvision:ListModels": {}, - "lookoutvision:ListProjects": {}, - "lookoutvision:ListTrialDetections": {}, - "lookoutvision:StartTrialDetection": {}, - "lookoutvision:UpdateDatasetEntries": {}, - "m2:CreateApplication": {}, - "m2:CreateEnvironment": {}, - "m2:GetSignedBluinsightsUrl": {}, - "m2:ListApplications": {}, - "m2:ListEngineVersions": {}, - "m2:ListEnvironments": {}, - "m2:ListTagsForResource": {}, - "machinelearning:DescribeBatchPredictions": {}, - "machinelearning:DescribeDataSources": {}, - "machinelearning:DescribeEvaluations": {}, - "machinelearning:DescribeMLModels": {}, - "macie2:AcceptInvitation": {}, - "macie2:CreateAllowList": {}, - "macie2:CreateInvitations": {}, - "macie2:CreateSampleFindings": {}, - "macie2:DeclineInvitations": {}, - "macie2:DeleteInvitations": {}, - "macie2:DescribeBuckets": {}, - "macie2:DescribeOrganizationConfiguration": {}, - "macie2:DisableMacie": {}, - "macie2:DisableOrganizationAdminAccount": {}, - "macie2:DisassociateFromAdministratorAccount": {}, - "macie2:DisassociateFromMasterAccount": {}, - "macie2:EnableMacie": {}, - "macie2:EnableOrganizationAdminAccount": {}, - "macie2:GetAdministratorAccount": {}, - "macie2:GetAutomatedDiscoveryConfiguration": {}, - "macie2:GetBucketStatistics": {}, - "macie2:GetClassificationExportConfiguration": {}, - "macie2:GetClassificationScope": {}, - "macie2:GetFindingStatistics": {}, - "macie2:GetFindings": {}, - "macie2:GetFindingsPublicationConfiguration": {}, - "macie2:GetInvitationsCount": {}, - "macie2:GetMacieSession": {}, - "macie2:GetMasterAccount": {}, - "macie2:GetResourceProfile": {}, - "macie2:GetRevealConfiguration": {}, - "macie2:GetSensitiveDataOccurrences": {}, - "macie2:GetSensitiveDataOccurrencesAvailability": {}, - "macie2:GetSensitivityInspectionTemplate": {}, - "macie2:GetUsageStatistics": {}, - "macie2:GetUsageTotals": {}, - "macie2:ListAllowLists": {}, - "macie2:ListClassificationJobs": {}, - "macie2:ListClassificationScopes": {}, - "macie2:ListCustomDataIdentifiers": {}, - "macie2:ListFindings": {}, - "macie2:ListFindingsFilters": {}, - "macie2:ListInvitations": {}, - "macie2:ListManagedDataIdentifiers": {}, - "macie2:ListMembers": {}, - "macie2:ListOrganizationAdminAccounts": {}, - "macie2:ListResourceProfileArtifacts": {}, - "macie2:ListResourceProfileDetections": {}, - "macie2:ListSensitivityInspectionTemplates": {}, - "macie2:PutClassificationExportConfiguration": {}, - "macie2:PutFindingsPublicationConfiguration": {}, - "macie2:SearchResources": {}, - "macie2:TestCustomDataIdentifier": {}, - "macie2:UpdateAutomatedDiscoveryConfiguration": {}, - "macie2:UpdateClassificationScope": {}, - "macie2:UpdateMacieSession": {}, - "macie2:UpdateMemberSession": {}, - "macie2:UpdateOrganizationConfiguration": {}, - "macie2:UpdateResourceProfile": {}, - "macie2:UpdateResourceProfileDetections": {}, - "macie2:UpdateRevealConfiguration": {}, - "macie2:UpdateSensitivityInspectionTemplate": {}, - "managedblockchain-query:BatchGetTokenBalance": {}, - "managedblockchain-query:GetAssetContract": {}, - "managedblockchain-query:GetTokenBalance": {}, - "managedblockchain-query:GetTransaction": {}, - "managedblockchain-query:ListAssetContracts": {}, - "managedblockchain-query:ListTokenBalances": {}, - "managedblockchain-query:ListTransactionEvents": {}, - "managedblockchain-query:ListTransactions": {}, - "managedblockchain:CreateAccessor": {}, - "managedblockchain:CreateNetwork": {}, - "managedblockchain:GET": {}, - "managedblockchain:Invoke": {}, - "managedblockchain:InvokeRpcBitcoinMainnet": {}, - "managedblockchain:InvokeRpcBitcoinTestnet": {}, - "managedblockchain:InvokeRpcPolygonMainnet": {}, - "managedblockchain:InvokeRpcPolygonMumbaiTestnet": {}, - "managedblockchain:ListAccessors": {}, - "managedblockchain:ListInvitations": {}, - "managedblockchain:ListNetworks": {}, - "managedblockchain:POST": {}, - "mechanicalturk:AcceptQualificationRequest": {}, - "mechanicalturk:ApproveAssignment": {}, - "mechanicalturk:AssociateQualificationWithWorker": {}, - "mechanicalturk:CreateAdditionalAssignmentsForHIT": {}, - "mechanicalturk:CreateHIT": {}, - "mechanicalturk:CreateHITType": {}, - "mechanicalturk:CreateHITWithHITType": {}, - "mechanicalturk:CreateQualificationType": {}, - "mechanicalturk:CreateWorkerBlock": {}, - "mechanicalturk:DeleteHIT": {}, - "mechanicalturk:DeleteQualificationType": {}, - "mechanicalturk:DeleteWorkerBlock": {}, - "mechanicalturk:DisassociateQualificationFromWorker": {}, - "mechanicalturk:GetAccountBalance": {}, - "mechanicalturk:GetAssignment": {}, - "mechanicalturk:GetFileUploadURL": {}, - "mechanicalturk:GetHIT": {}, - "mechanicalturk:GetQualificationScore": {}, - "mechanicalturk:GetQualificationType": {}, - "mechanicalturk:ListAssignmentsForHIT": {}, - "mechanicalturk:ListBonusPayments": {}, - "mechanicalturk:ListHITs": {}, - "mechanicalturk:ListHITsForQualificationType": {}, - "mechanicalturk:ListQualificationRequests": {}, - "mechanicalturk:ListQualificationTypes": {}, - "mechanicalturk:ListReviewPolicyResultsForHIT": {}, - "mechanicalturk:ListReviewableHITs": {}, - "mechanicalturk:ListWorkerBlocks": {}, - "mechanicalturk:ListWorkersWithQualificationType": {}, - "mechanicalturk:NotifyWorkers": {}, - "mechanicalturk:RejectAssignment": {}, - "mechanicalturk:RejectQualificationRequest": {}, - "mechanicalturk:SendBonus": {}, - "mechanicalturk:SendTestEventNotification": {}, - "mechanicalturk:UpdateExpirationForHIT": {}, - "mechanicalturk:UpdateHITReviewStatus": {}, - "mechanicalturk:UpdateHITTypeOfHIT": {}, - "mechanicalturk:UpdateNotificationSettings": {}, - "mechanicalturk:UpdateQualificationType": {}, - "mediaconnect:AddFlowMediaStreams": {}, - "mediaconnect:AddFlowOutputs": {}, - "mediaconnect:AddFlowSources": {}, - "mediaconnect:AddFlowVpcInterfaces": {}, - "mediaconnect:CreateFlow": {}, - "mediaconnect:DeleteFlow": {}, - "mediaconnect:DescribeFlow": {}, - "mediaconnect:DescribeFlowSourceMetadata": {}, - "mediaconnect:DescribeOffering": {}, - "mediaconnect:DescribeReservation": {}, - "mediaconnect:DiscoverGatewayPollEndpoint": {}, - "mediaconnect:GrantFlowEntitlements": {}, - "mediaconnect:ListEntitlements": {}, - "mediaconnect:ListFlows": {}, - "mediaconnect:ListGateways": {}, - "mediaconnect:ListOfferings": {}, - "mediaconnect:ListReservations": {}, - "mediaconnect:ListTagsForResource": {}, - "mediaconnect:PollGateway": {}, - "mediaconnect:PurchaseOffering": {}, - "mediaconnect:RemoveFlowMediaStream": {}, - "mediaconnect:RemoveFlowOutput": {}, - "mediaconnect:RemoveFlowSource": {}, - "mediaconnect:RemoveFlowVpcInterface": {}, - "mediaconnect:RevokeFlowEntitlement": {}, - "mediaconnect:StartFlow": {}, - "mediaconnect:StopFlow": {}, - "mediaconnect:SubmitGatewayStateChange": {}, - "mediaconnect:TagResource": {}, - "mediaconnect:UntagResource": {}, - "mediaconnect:UpdateFlow": {}, - "mediaconnect:UpdateFlowEntitlement": {}, - "mediaconnect:UpdateFlowMediaStream": {}, - "mediaconnect:UpdateFlowOutput": {}, - "mediaconnect:UpdateFlowSource": {}, - "mediaconvert:AssociateCertificate": {}, - "mediaconvert:CreatePreset": {}, - "mediaconvert:CreateQueue": {}, - "mediaconvert:DeletePolicy": {}, - "mediaconvert:DescribeEndpoints": {}, - "mediaconvert:DisassociateCertificate": {}, - "mediaconvert:GetPolicy": {}, - "mediaconvert:ListJobTemplates": {}, - "mediaconvert:ListPresets": {}, - "mediaconvert:ListQueues": {}, - "mediaconvert:PutPolicy": {}, - "mediaimport:CreateDatabaseBinarySnapshot": {}, - "medialive:BatchDelete": {}, - "medialive:BatchStart": {}, - "medialive:BatchStop": {}, - "medialive:DescribeAccountConfiguration": {}, - "medialive:ListChannels": {}, - "medialive:ListInputDeviceTransfers": {}, - "medialive:ListInputDevices": {}, - "medialive:ListInputSecurityGroups": {}, - "medialive:ListInputs": {}, - "medialive:ListMultiplexPrograms": {}, - "medialive:ListMultiplexes": {}, - "medialive:ListOfferings": {}, - "medialive:ListReservations": {}, - "medialive:UpdateAccountConfiguration": {}, - "mediapackage-vod:CreateAsset": {}, - "mediapackage-vod:CreatePackagingConfiguration": {}, - "mediapackage-vod:CreatePackagingGroup": {}, - "mediapackage-vod:ListAssets": {}, - "mediapackage-vod:ListPackagingConfigurations": {}, - "mediapackage-vod:ListPackagingGroups": {}, - "mediapackage:CreateChannel": {}, - "mediapackage:CreateHarvestJob": {}, - "mediapackage:CreateOriginEndpoint": {}, - "mediapackage:ListChannels": {}, - "mediapackage:ListHarvestJobs": {}, - "mediapackage:ListOriginEndpoints": {}, - "mediapackagev2:ListChannelGroups": {}, - "mediastore:CreateContainer": {}, - "mediastore:ListContainers": {}, - "mediatailor:CreateChannel": {}, - "mediatailor:CreateLiveSource": {}, - "mediatailor:CreateProgram": {}, - "mediatailor:CreateSourceLocation": {}, - "mediatailor:CreateVodSource": {}, - "mediatailor:ListAlerts": {}, - "mediatailor:ListChannels": {}, - "mediatailor:ListLiveSources": {}, - "mediatailor:ListPlaybackConfigurations": {}, - "mediatailor:ListSourceLocations": {}, - "mediatailor:ListVodSources": {}, - "mediatailor:PutPlaybackConfiguration": {}, - "medical-imaging:CreateDatastore": {}, - "medical-imaging:ListDatastores": {}, - "memorydb:CreateParameterGroup": {}, - "memorydb:CreateSubnetGroup": {}, - "memorydb:CreateUser": {}, - "memorydb:DescribeEngineVersions": {}, - "memorydb:DescribeEvents": {}, - "memorydb:DescribeReservedNodesOfferings": {}, - "memorydb:DescribeServiceUpdates": {}, - "mgh:CreateHomeRegionControl": {}, - "mgh:DeleteHomeRegionControl": {}, - "mgh:DescribeApplicationState": {}, - "mgh:DescribeHomeRegionControls": {}, - "mgh:GetHomeRegion": {}, - "mgh:ListApplicationStates": {}, - "mgh:ListMigrationTasks": {}, - "mgh:ListProgressUpdateStreams": {}, - "mgh:NotifyApplicationState": {}, - "mgn:BatchDeleteSnapshotRequestForMgn": {}, - "mgn:CreateApplication": {}, - "mgn:CreateConnector": {}, - "mgn:CreateLaunchConfigurationTemplate": {}, - "mgn:CreateReplicationConfigurationTemplate": {}, - "mgn:CreateVcenterClientForMgn": {}, - "mgn:CreateWave": {}, - "mgn:DescribeJobs": {}, - "mgn:DescribeLaunchConfigurationTemplates": {}, - "mgn:DescribeReplicationConfigurationTemplates": {}, - "mgn:DescribeReplicationServerAssociationsForMgn": {}, - "mgn:DescribeSnapshotRequestsForMgn": {}, - "mgn:DescribeSourceServers": {}, - "mgn:DescribeVcenterClients": {}, - "mgn:GetAgentInstallationAssetsForMgn": {}, - "mgn:GetChannelCommandsForMgn": {}, - "mgn:InitializeService": {}, - "mgn:ListApplications": {}, - "mgn:ListConnectors": {}, - "mgn:ListExports": {}, - "mgn:ListImports": {}, - "mgn:ListManagedAccounts": {}, - "mgn:ListTagsForResource": {}, - "mgn:ListWaves": {}, - "mgn:RegisterAgentForMgn": {}, - "mgn:SendChannelCommandResultForMgn": {}, - "mgn:SendClientLogsForMgn": {}, - "mgn:SendClientMetricsForMgn": {}, - "mgn:StartExport": {}, - "mgn:StartImport": {}, - "mgn:VerifyClientRoleForMgn": {}, - "migrationhub-orchestrator:CreateWorkflow": {}, - "migrationhub-orchestrator:GetMessage": {}, - "migrationhub-orchestrator:GetTemplate": {}, - "migrationhub-orchestrator:GetTemplateStep": {}, - "migrationhub-orchestrator:GetTemplateStepGroup": {}, - "migrationhub-orchestrator:ListPlugins": {}, - "migrationhub-orchestrator:ListTemplateStepGroups": {}, - "migrationhub-orchestrator:ListTemplateSteps": {}, - "migrationhub-orchestrator:ListTemplates": {}, - "migrationhub-orchestrator:ListWorkflows": {}, - "migrationhub-orchestrator:RegisterPlugin": {}, - "migrationhub-orchestrator:SendMessage": {}, - "migrationhub-strategy:GetAntiPattern": {}, - "migrationhub-strategy:GetApplicationComponentDetails": {}, - "migrationhub-strategy:GetApplicationComponentStrategies": {}, - "migrationhub-strategy:GetAssessment": {}, - "migrationhub-strategy:GetImportFileTask": {}, - "migrationhub-strategy:GetLatestAssessmentId": {}, - "migrationhub-strategy:GetMessage": {}, - "migrationhub-strategy:GetPortfolioPreferences": {}, - "migrationhub-strategy:GetPortfolioSummary": {}, - "migrationhub-strategy:GetRecommendationReportDetails": {}, - "migrationhub-strategy:GetServerDetails": {}, - "migrationhub-strategy:GetServerStrategies": {}, - "migrationhub-strategy:ListAnalyzableServers": {}, - "migrationhub-strategy:ListAntiPatterns": {}, - "migrationhub-strategy:ListApplicationComponents": {}, - "migrationhub-strategy:ListCollectors": {}, - "migrationhub-strategy:ListImportFileTask": {}, - "migrationhub-strategy:ListJarArtifacts": {}, - "migrationhub-strategy:ListServers": {}, - "migrationhub-strategy:PutPortfolioPreferences": {}, - "migrationhub-strategy:RegisterCollector": {}, - "migrationhub-strategy:SendMessage": {}, - "migrationhub-strategy:StartAssessment": {}, - "migrationhub-strategy:StartImportFileTask": {}, - "migrationhub-strategy:StartRecommendationReportGeneration": {}, - "migrationhub-strategy:StopAssessment": {}, - "migrationhub-strategy:UpdateApplicationComponentConfig": {}, - "migrationhub-strategy:UpdateCollectorConfiguration": {}, - "migrationhub-strategy:UpdateServerConfig": {}, - "mobileanalytics:PutEvents": {}, - "monitron:CreateProject": {}, - "monitron:ListProjects": {}, - "mq:CreateBroker": {}, - "mq:CreateConfiguration": {}, - "mq:DescribeBrokerEngineTypes": {}, - "mq:DescribeBrokerInstanceOptions": {}, - "mq:ListBrokers": {}, - "mq:ListConfigurations": {}, - "network-firewall:ListRuleGroups": {}, - "networkmanager-chat:CancelMessageResponse": {}, - "networkmanager-chat:CreateConversation": {}, - "networkmanager-chat:DeleteConversation": {}, - "networkmanager-chat:ListConversationMessages": {}, - "networkmanager-chat:ListConversations": {}, - "networkmanager-chat:NotifyConversationIsActive": {}, - "networkmanager-chat:SendConversationMessage": {}, - "networkmanager:CreateGlobalNetwork": {}, - "networkmanager:ListCoreNetworks": {}, - "networkmanager:ListOrganizationServiceAccessStatus": {}, - "networkmanager:ListPeerings": {}, - "networkmanager:StartOrganizationServiceAccessUpdate": {}, - "networkmonitor:CreateProbe": {}, - "networkmonitor:ListMonitors": {}, - "nimble:GetFeatureMap": {}, - "nimble:ListStudios": {}, - "notifications-contacts:CreateEmailContact": {}, - "notifications-contacts:ListEmailContacts": {}, - "notifications-contacts:ListTagsForResource": {}, - "notifications:CreateEventRule": {}, - "notifications:CreateNotificationConfiguration": {}, - "notifications:DeregisterNotificationHub": {}, - "notifications:ListChannels": {}, - "notifications:ListEventRules": {}, - "notifications:ListNotificationConfigurations": {}, - "notifications:ListNotificationEvents": {}, - "notifications:ListNotificationHubs": {}, - "notifications:ListTagsForResource": {}, - "notifications:RegisterNotificationHub": {}, - "oam:CreateSink": {}, - "oam:ListLinks": {}, - "oam:ListSinks": {}, - "omics:AcceptShare": {}, - "omics:CreateAnnotationStore": {}, - "omics:CreateReferenceStore": {}, - "omics:CreateRunGroup": {}, - "omics:CreateSequenceStore": {}, - "omics:CreateShare": {}, - "omics:CreateVariantStore": {}, - "omics:CreateWorkflow": {}, - "omics:DeleteShare": {}, - "omics:GetShare": {}, - "omics:ListAnnotationImportJobs": {}, - "omics:ListAnnotationStores": {}, - "omics:ListReferenceStores": {}, - "omics:ListRunGroups": {}, - "omics:ListRuns": {}, - "omics:ListSequenceStores": {}, - "omics:ListShares": {}, - "omics:ListTagsForResource": {}, - "omics:ListVariantImportJobs": {}, - "omics:ListVariantStores": {}, - "omics:ListWorkflows": {}, - "omics:StartAnnotationImportJob": {}, - "omics:StartRun": {}, - "omics:StartVariantImportJob": {}, - "one:CreateDeviceConfigurationTemplate": {}, - "one:CreateDeviceInstance": {}, - "one:CreateSite": {}, - "one:ListDeviceConfigurationTemplates": {}, - "one:ListDeviceInstances": {}, - "one:ListSites": {}, - "one:ListUsers": {}, - "opsworks-cm:AssociateNode": {}, - "opsworks-cm:CreateBackup": {}, - "opsworks-cm:CreateServer": {}, - "opsworks-cm:DeleteBackup": {}, - "opsworks-cm:DeleteServer": {}, - "opsworks-cm:DescribeAccountAttributes": {}, - "opsworks-cm:DescribeBackups": {}, - "opsworks-cm:DescribeEvents": {}, - "opsworks-cm:DescribeNodeAssociationStatus": {}, - "opsworks-cm:DescribeServers": {}, - "opsworks-cm:DisassociateNode": {}, - "opsworks-cm:ExportServerEngineAttribute": {}, - "opsworks-cm:ListTagsForResource": {}, - "opsworks-cm:RestoreServer": {}, - "opsworks-cm:StartMaintenance": {}, - "opsworks-cm:TagResource": {}, - "opsworks-cm:UntagResource": {}, - "opsworks-cm:UpdateServer": {}, - "opsworks-cm:UpdateServerEngineAttributes": {}, - "opsworks:CreateStack": {}, - "opsworks:CreateUserProfile": {}, - "opsworks:DeleteUserProfile": {}, - "opsworks:DescribeMyUserProfile": {}, - "opsworks:DescribeOperatingSystems": {}, - "opsworks:DescribeUserProfiles": {}, - "opsworks:UpdateMyUserProfile": {}, - "opsworks:UpdateUserProfile": {}, - "organizations:CreateAccount": {}, - "organizations:CreateGovCloudAccount": {}, - "organizations:CreateOrganization": {}, - "organizations:CreatePolicy": {}, - "organizations:DeleteOrganization": {}, - "organizations:DeleteResourcePolicy": {}, - "organizations:DescribeCreateAccountStatus": {}, - "organizations:DescribeOrganization": {}, - "organizations:DescribeResourcePolicy": {}, - "organizations:DisableAWSServiceAccess": {}, - "organizations:EnableAWSServiceAccess": {}, - "organizations:EnableAllFeatures": {}, - "organizations:LeaveOrganization": {}, - "organizations:ListAWSServiceAccessForOrganization": {}, - "organizations:ListAccounts": {}, - "organizations:ListCreateAccountStatus": {}, - "organizations:ListDelegatedAdministrators": {}, - "organizations:ListHandshakesForAccount": {}, - "organizations:ListHandshakesForOrganization": {}, - "organizations:ListPolicies": {}, - "organizations:ListRoots": {}, - "osis:CreatePipeline": {}, - "osis:ListPipelineBlueprints": {}, - "osis:ListPipelines": {}, - "osis:ValidatePipeline": {}, - "outposts:CancelOrder": {}, - "outposts:CreatePrivateConnectivityConfig": {}, - "outposts:CreateSite": {}, - "outposts:GetCatalogItem": {}, - "outposts:GetConnection": {}, - "outposts:GetOrder": {}, - "outposts:GetPrivateConnectivityConfig": {}, - "outposts:ListAssets": {}, - "outposts:ListCatalogItems": {}, - "outposts:ListOrders": {}, - "outposts:ListOutposts": {}, - "outposts:ListSites": {}, - "outposts:ListTagsForResource": {}, - "outposts:StartConnection": {}, - "panorama:CreateApplicationInstance": {}, - "panorama:CreateJobForDevices": {}, - "panorama:CreateNodeFromTemplateJob": {}, - "panorama:CreatePackage": {}, - "panorama:CreatePackageImportJob": {}, - "panorama:DescribeDeviceJob": {}, - "panorama:DescribeNode": {}, - "panorama:DescribeNodeFromTemplateJob": {}, - "panorama:DescribePackageImportJob": {}, - "panorama:DescribeSoftware": {}, - "panorama:GetWebSocketURL": {}, - "panorama:ListDevices": {}, - "panorama:ListNodeFromTemplateJobs": {}, - "panorama:ListNodes": {}, - "panorama:ListPackageImportJobs": {}, - "panorama:ListPackages": {}, - "panorama:ProvisionDevice": {}, - "partnercentral-account-management:AssociatePartnerAccount": {}, - "partnercentral-account-management:AssociatePartnerUser": {}, - "partnercentral-account-management:DisassociatePartnerUser": {}, - "payment-cryptography:CreateKey": {}, - "payment-cryptography:DecryptData": {}, - "payment-cryptography:EncryptData": {}, - "payment-cryptography:GenerateCardValidationData": {}, - "payment-cryptography:GenerateMac": {}, - "payment-cryptography:GeneratePinData": {}, - "payment-cryptography:GetParametersForExport": {}, - "payment-cryptography:GetParametersForImport": {}, - "payment-cryptography:ImportKey": {}, - "payment-cryptography:ListAliases": {}, - "payment-cryptography:ListKeys": {}, - "payment-cryptography:ReEncryptData": {}, - "payment-cryptography:TranslatePinData": {}, - "payment-cryptography:VerifyAuthRequestCryptogram": {}, - "payment-cryptography:VerifyCardValidationData": {}, - "payment-cryptography:VerifyMac": {}, - "payment-cryptography:VerifyPinData": {}, - "payments:CreatePaymentInstrument": {}, - "payments:DeletePaymentInstrument": {}, - "payments:GetPaymentInstrument": {}, - "payments:GetPaymentStatus": {}, - "payments:ListPaymentPreferences": {}, - "payments:MakePayment": {}, - "payments:UpdatePaymentPreferences": {}, - "pca-connector-ad:CreateConnector": {}, - "pca-connector-ad:CreateDirectoryRegistration": {}, - "pca-connector-ad:ListConnectors": {}, - "pca-connector-ad:ListDirectoryRegistrations": {}, - "pca-connector-ad:ListTagsForResource": {}, - "personalize:ListBatchInferenceJobs": {}, - "personalize:ListBatchSegmentJobs": {}, - "personalize:ListCampaigns": {}, - "personalize:ListDataInsightsJobs": {}, - "personalize:ListDatasetExportJobs": {}, - "personalize:ListDatasetGroups": {}, - "personalize:ListDatasetImportJobs": {}, - "personalize:ListDatasets": {}, - "personalize:ListEventTrackers": {}, - "personalize:ListFilters": {}, - "personalize:ListMetricAttributionMetrics": {}, - "personalize:ListMetricAttributions": {}, - "personalize:ListRecipes": {}, - "personalize:ListRecommenders": {}, - "personalize:ListSchemas": {}, - "personalize:ListSolutionVersions": {}, - "personalize:ListSolutions": {}, - "personalize:ListTagsForResource": {}, - "personalize:PutActionInteractions": {}, - "personalize:PutEvents": {}, - "personalize:TagResource": {}, - "personalize:UntagResource": {}, - "pipes:ListPipes": {}, - "polly:DescribeVoices": {}, - "polly:GetSpeechSynthesisTask": {}, - "polly:ListLexicons": {}, - "polly:ListSpeechSynthesisTasks": {}, - "pricing:DescribeServices": {}, - "pricing:GetAttributeValues": {}, - "pricing:GetPriceListFileUrl": {}, - "pricing:GetProducts": {}, - "pricing:ListPriceLists": {}, - "private-networks:ListNetworks": {}, - "private-networks:ListTagsForResource": {}, - "private-networks:Ping": {}, - "profile:GetProfileObjectTypeTemplate": {}, - "profile:ListAccountIntegrations": {}, - "profile:ListDomains": {}, - "profile:ListProfileObjectTypeTemplates": {}, - "proton:CreateEnvironmentAccountConnection": {}, - "proton:CreateServiceSyncConfig": {}, - "proton:CreateTemplateSyncConfig": {}, - "proton:DeleteAccountRoles": {}, - "proton:DeleteServiceSyncConfig": {}, - "proton:DeleteTemplateSyncConfig": {}, - "proton:GetAccountRoles": {}, - "proton:GetAccountSettings": {}, - "proton:GetRepositorySyncStatus": {}, - "proton:GetResourceTemplateVersionStatusCounts": {}, - "proton:GetResourcesSummary": {}, - "proton:GetServiceInstanceSyncStatus": {}, - "proton:GetServiceSyncBlockerSummary": {}, - "proton:GetServiceSyncConfig": {}, - "proton:GetTemplateSyncConfig": {}, - "proton:GetTemplateSyncStatus": {}, - "proton:ListDeployments": {}, - "proton:ListEnvironmentAccountConnections": {}, - "proton:ListEnvironmentTemplates": {}, - "proton:ListEnvironments": {}, - "proton:ListRepositories": {}, - "proton:ListRepositorySyncDefinitions": {}, - "proton:ListServiceInstances": {}, - "proton:ListServiceTemplates": {}, - "proton:ListServices": {}, - "proton:UpdateAccountRoles": {}, - "proton:UpdateAccountSettings": {}, - "proton:UpdateServiceSyncBlocker": {}, - "proton:UpdateServiceSyncConfig": {}, - "proton:UpdateTemplateSyncConfig": {}, - "purchase-orders:GetConsoleActionSetEnforced": {}, - "purchase-orders:ListPurchaseOrders": {}, - "purchase-orders:UpdateConsoleActionSetEnforced": {}, - "q:GetConversation": {}, - "q:GetTroubleshootingResults": {}, - "q:SendMessage": {}, - "q:StartConversation": {}, - "q:StartTroubleshootingAnalysis": {}, - "q:StartTroubleshootingResolutionExplanation": {}, - "qbusiness:AddUserLicenses": {}, - "qbusiness:CreateApplication": {}, - "qbusiness:CreateLicense": {}, - "qbusiness:ListApplications": {}, - "qbusiness:ListUserLicenses": {}, - "qbusiness:RemoveUserLicenses": {}, - "qldb:ListJournalS3Exports": {}, - "qldb:ListLedgers": {}, - "quicksight:AccountConfigurations": {}, - "quicksight:CreateAccountCustomization": {}, - "quicksight:CreateAccountSubscription": {}, - "quicksight:CreateCustomPermissions": {}, - "quicksight:CreateDataSource": {}, - "quicksight:CreateRoleMembership": {}, - "quicksight:CreateVPCConnection": {}, - "quicksight:DeleteCustomPermissions": {}, - "quicksight:DeleteIdentityPropagationConfig": {}, - "quicksight:DeleteRoleCustomPermission": {}, - "quicksight:DeleteRoleMembership": {}, - "quicksight:DescribeAccountSettings": {}, - "quicksight:DescribeCustomPermissions": {}, - "quicksight:DescribeIpRestriction": {}, - "quicksight:DescribeRoleCustomPermission": {}, - "quicksight:GetAnonymousUserEmbedUrl": {}, - "quicksight:GetGroupMapping": {}, - "quicksight:GetSessionEmbedUrl": {}, - "quicksight:ListCustomPermissions": {}, - "quicksight:ListCustomerManagedKeys": {}, - "quicksight:ListDataSets": {}, - "quicksight:ListDataSources": {}, - "quicksight:ListIdentityPropagationConfigs": {}, - "quicksight:ListIngestions": {}, - "quicksight:ListKMSKeysForUser": {}, - "quicksight:ListNamespaces": {}, - "quicksight:ListRefreshSchedules": {}, - "quicksight:ListRoleMemberships": {}, - "quicksight:ListTopicRefreshSchedules": {}, - "quicksight:ListTopics": {}, - "quicksight:ListVPCConnections": {}, - "quicksight:RegisterCustomerManagedKey": {}, - "quicksight:RemoveCustomerManagedKey": {}, - "quicksight:ScopeDownPolicy": {}, - "quicksight:SearchDirectoryGroups": {}, - "quicksight:SetGroupMapping": {}, - "quicksight:Subscribe": {}, - "quicksight:Unsubscribe": {}, - "quicksight:UpdateAccountSettings": {}, - "quicksight:UpdateCustomPermissions": {}, - "quicksight:UpdateIdentityPropagationConfig": {}, - "quicksight:UpdateIpRestriction": {}, - "quicksight:UpdatePublicSharingSettings": {}, - "quicksight:UpdateResourcePermissions": {}, - "quicksight:UpdateRoleCustomPermission": {}, - "ram:CreatePermission": {}, - "ram:CreateResourceShare": {}, - "ram:EnableSharingWithAwsOrganization": {}, - "ram:GetResourcePolicies": {}, - "ram:GetResourceShareAssociations": {}, - "ram:GetResourceShareInvitations": {}, - "ram:GetResourceShares": {}, - "ram:ListPermissionVersions": {}, - "ram:ListPermissions": {}, - "ram:ListPrincipals": {}, - "ram:ListReplacePermissionAssociationsWork": {}, - "ram:ListResourceTypes": {}, - "ram:ListResources": {}, - "rbin:ListRules": {}, - "rds:CancelExportTask": {}, - "rds:CreateDBProxy": {}, - "rds:CrossRegionCommunication": {}, - "rds:DescribeAccountAttributes": {}, - "rds:DescribeCertificates": {}, - "rds:DescribeDBEngineVersions": {}, - "rds:DescribeDBRecommendations": {}, - "rds:DescribeEngineDefaultClusterParameters": {}, - "rds:DescribeEngineDefaultParameters": {}, - "rds:DescribeEventCategories": {}, - "rds:DescribeEvents": {}, - "rds:DescribeExportTasks": {}, - "rds:DescribeOrderableDBInstanceOptions": {}, - "rds:DescribeRecommendationGroups": {}, - "rds:DescribeRecommendations": {}, - "rds:DescribeReservedDBInstancesOfferings": {}, - "rds:DescribeSourceRegions": {}, - "rds:ModifyCertificates": {}, - "rds:ModifyDBRecommendation": {}, - "rds:ModifyRecommendation": {}, - "rds:StartExportTask": {}, - "redshift-data:CancelStatement": {}, - "redshift-data:DescribeStatement": {}, - "redshift-data:GetStatementResult": {}, - "redshift-data:ListStatements": {}, - "redshift-serverless:CreateUsageLimit": {}, - "redshift-serverless:DeleteResourcePolicy": {}, - "redshift-serverless:DeleteScheduledAction": {}, - "redshift-serverless:DeleteSnapshotCopyConfiguration": {}, - "redshift-serverless:DeleteUsageLimit": {}, - "redshift-serverless:GetResourcePolicy": {}, - "redshift-serverless:GetScheduledAction": {}, - "redshift-serverless:GetTableRestoreStatus": {}, - "redshift-serverless:GetUsageLimit": {}, - "redshift-serverless:ListCustomDomainAssociations": {}, - "redshift-serverless:ListNamespaces": {}, - "redshift-serverless:ListScheduledActions": {}, - "redshift-serverless:ListTableRestoreStatus": {}, - "redshift-serverless:ListUsageLimits": {}, - "redshift-serverless:ListWorkgroups": {}, - "redshift-serverless:PutResourcePolicy": {}, - "redshift-serverless:UpdateScheduledAction": {}, - "redshift-serverless:UpdateSnapshotCopyConfiguration": {}, - "redshift-serverless:UpdateUsageLimit": {}, - "redshift-serverless:span": {}, - "redshift:AcceptReservedNodeExchange": {}, - "redshift:AddPartner": {}, - "redshift:AuthorizeEndpointAccess": {}, - "redshift:CancelQuery": {}, - "redshift:CancelQuerySession": {}, - "redshift:CreateAuthenticationProfile": {}, - "redshift:CreateEndpointAccess": {}, - "redshift:CreateRedshiftIdcApplication": {}, - "redshift:CreateSavedQuery": {}, - "redshift:CreateScheduledAction": {}, - "redshift:DeleteAuthenticationProfile": {}, - "redshift:DeleteEndpointAccess": {}, - "redshift:DeletePartner": {}, - "redshift:DeleteSavedQueries": {}, - "redshift:DeleteScheduledAction": {}, - "redshift:DescribeAccountAttributes": {}, - "redshift:DescribeAuthenticationProfiles": {}, - "redshift:DescribeClusterDbRevisions": {}, - "redshift:DescribeClusterParameterGroups": {}, - "redshift:DescribeClusterSecurityGroups": {}, - "redshift:DescribeClusterSnapshots": {}, - "redshift:DescribeClusterSubnetGroups": {}, - "redshift:DescribeClusterTracks": {}, - "redshift:DescribeClusterVersions": {}, - "redshift:DescribeClusters": {}, - "redshift:DescribeCustomDomainAssociations": {}, - "redshift:DescribeDataShares": {}, - "redshift:DescribeDataSharesForConsumer": {}, - "redshift:DescribeDataSharesForProducer": {}, - "redshift:DescribeDefaultClusterParameters": {}, - "redshift:DescribeEndpointAccess": {}, - "redshift:DescribeEndpointAuthorization": {}, - "redshift:DescribeEventCategories": {}, - "redshift:DescribeEventSubscriptions": {}, - "redshift:DescribeEvents": {}, - "redshift:DescribeHsmClientCertificates": {}, - "redshift:DescribeHsmConfigurations": {}, - "redshift:DescribeInboundIntegrations": {}, - "redshift:DescribeNodeConfigurationOptions": {}, - "redshift:DescribeOrderableClusterOptions": {}, - "redshift:DescribePartners": {}, - "redshift:DescribeQuery": {}, - "redshift:DescribeReservedNodeExchangeStatus": {}, - "redshift:DescribeReservedNodeOfferings": {}, - "redshift:DescribeReservedNodes": {}, - "redshift:DescribeSavedQueries": {}, - "redshift:DescribeScheduledActions": {}, - "redshift:DescribeSnapshotCopyGrants": {}, - "redshift:DescribeStorage": {}, - "redshift:DescribeTable": {}, - "redshift:DescribeTableRestoreStatus": {}, - "redshift:ExecuteQuery": {}, - "redshift:FetchResults": {}, - "redshift:GetReservedNodeExchangeConfigurationOptions": {}, - "redshift:GetReservedNodeExchangeOfferings": {}, - "redshift:ListDatabases": {}, - "redshift:ListRecommendations": {}, - "redshift:ListSavedQueries": {}, - "redshift:ListSchemas": {}, - "redshift:ListTables": {}, - "redshift:ModifyAuthenticationProfile": {}, - "redshift:ModifyClusterMaintenance": {}, - "redshift:ModifyEndpointAccess": {}, - "redshift:ModifySavedQuery": {}, - "redshift:ModifyScheduledAction": {}, - "redshift:PurchaseReservedNodeOffering": {}, - "redshift:RevokeEndpointAccess": {}, - "redshift:UpdatePartnerStatus": {}, - "redshift:ViewQueriesFromConsole": {}, - "redshift:ViewQueriesInConsole": {}, - "refactor-spaces:CreateApplication": {}, - "refactor-spaces:CreateEnvironment": {}, - "refactor-spaces:CreateRoute": {}, - "refactor-spaces:CreateService": {}, - "refactor-spaces:DeleteResourcePolicy": {}, - "refactor-spaces:GetResourcePolicy": {}, - "refactor-spaces:ListEnvironments": {}, - "refactor-spaces:ListTagsForResource": {}, - "refactor-spaces:PutResourcePolicy": {}, - "rekognition:CompareFaces": {}, - "rekognition:CreateFaceLivenessSession": {}, - "rekognition:DescribeProjects": {}, - "rekognition:DetectFaces": {}, - "rekognition:DetectLabels": {}, - "rekognition:DetectProtectiveEquipment": {}, - "rekognition:DetectText": {}, - "rekognition:GetCelebrityInfo": {}, - "rekognition:GetCelebrityRecognition": {}, - "rekognition:GetContentModeration": {}, - "rekognition:GetFaceDetection": {}, - "rekognition:GetFaceLivenessSessionResults": {}, - "rekognition:GetFaceSearch": {}, - "rekognition:GetLabelDetection": {}, - "rekognition:GetMediaAnalysisJob": {}, - "rekognition:GetPersonTracking": {}, - "rekognition:GetSegmentDetection": {}, - "rekognition:GetTextDetection": {}, - "rekognition:ListCollections": {}, - "rekognition:ListMediaAnalysisJobs": {}, - "rekognition:RecognizeCelebrities": {}, - "rekognition:StartCelebrityRecognition": {}, - "rekognition:StartContentModeration": {}, - "rekognition:StartFaceDetection": {}, - "rekognition:StartFaceLivenessSession": {}, - "rekognition:StartLabelDetection": {}, - "rekognition:StartPersonTracking": {}, - "rekognition:StartSegmentDetection": {}, - "rekognition:StartTextDetection": {}, - "repostspace:CreateSpace": {}, - "repostspace:ListSpaces": {}, - "resiliencehub:CreateApp": {}, - "resiliencehub:CreateResiliencyPolicy": {}, - "resiliencehub:ListAppAssessments": {}, - "resiliencehub:ListApps": {}, - "resiliencehub:ListResiliencyPolicies": {}, - "resiliencehub:ListSuggestedResiliencyPolicies": {}, - "resiliencehub:ListTagsForResource": {}, - "resource-explorer-2:BatchGetView": {}, - "resource-explorer-2:CreateIndex": {}, - "resource-explorer-2:CreateView": {}, - "resource-explorer-2:DisassociateDefaultView": {}, - "resource-explorer-2:GetAccountLevelServiceConfiguration": {}, - "resource-explorer-2:GetDefaultView": {}, - "resource-explorer-2:GetIndex": {}, - "resource-explorer-2:ListIndexes": {}, - "resource-explorer-2:ListIndexesForMembers": {}, - "resource-explorer-2:ListSupportedResourceTypes": {}, - "resource-explorer-2:ListViews": {}, - "resource-explorer:ListResourceTypes": {}, - "resource-explorer:ListResources": {}, - "resource-explorer:ListTags": {}, - "resource-groups:CreateGroup": {}, - "resource-groups:GetAccountSettings": {}, - "resource-groups:ListGroups": {}, - "resource-groups:SearchResources": {}, - "resource-groups:UpdateAccountSettings": {}, - "rhelkb:GetRhelURL": {}, - "robomaker:BatchDeleteWorlds": {}, - "robomaker:BatchDescribeSimulationJob": {}, - "robomaker:CreateDeploymentJob": {}, - "robomaker:CreateFleet": {}, - "robomaker:CreateRobot": {}, - "robomaker:CreateRobotApplication": {}, - "robomaker:CreateSimulationApplication": {}, - "robomaker:CreateSimulationJob": {}, - "robomaker:CreateWorldTemplate": {}, - "robomaker:ListDeploymentJobs": {}, - "robomaker:ListFleets": {}, - "robomaker:ListRobotApplications": {}, - "robomaker:ListRobots": {}, - "robomaker:ListSimulationApplications": {}, - "robomaker:ListSimulationJobBatches": {}, - "robomaker:ListSimulationJobs": {}, - "robomaker:ListWorldExportJobs": {}, - "robomaker:ListWorldGenerationJobs": {}, - "robomaker:ListWorldTemplates": {}, - "robomaker:ListWorlds": {}, - "robomaker:StartSimulationJobBatch": {}, - "rolesanywhere:CreateProfile": {}, - "rolesanywhere:CreateTrustAnchor": {}, - "rolesanywhere:ImportCrl": {}, - "rolesanywhere:ListCrls": {}, - "rolesanywhere:ListProfiles": {}, - "rolesanywhere:ListSubjects": {}, - "rolesanywhere:ListTagsForResource": {}, - "rolesanywhere:ListTrustAnchors": {}, - "route53-recovery-cluster:ListRoutingControls": {}, - "route53-recovery-control-config:ListAssociatedRoute53HealthChecks": {}, - "route53-recovery-control-config:ListClusters": {}, - "route53-recovery-control-config:ListControlPanels": {}, - "route53-recovery-control-config:ListRoutingControls": {}, - "route53-recovery-control-config:ListTagsForResource": {}, - "route53-recovery-readiness:CreateCrossAccountAuthorization": {}, - "route53-recovery-readiness:DeleteCrossAccountAuthorization": {}, - "route53-recovery-readiness:ListCells": {}, - "route53-recovery-readiness:ListCrossAccountAuthorizations": {}, - "route53-recovery-readiness:ListReadinessChecks": {}, - "route53-recovery-readiness:ListRecoveryGroups": {}, - "route53-recovery-readiness:ListResourceSets": {}, - "route53-recovery-readiness:ListRules": {}, - "route53-recovery-readiness:ListTagsForResources": {}, - "route53:CreateCidrCollection": {}, - "route53:CreateHealthCheck": {}, - "route53:CreateHostedZone": {}, - "route53:CreateReusableDelegationSet": {}, - "route53:CreateTrafficPolicy": {}, - "route53:GetAccountLimit": {}, - "route53:GetCheckerIpRanges": {}, - "route53:GetGeoLocation": {}, - "route53:GetHealthCheckCount": {}, - "route53:GetHostedZoneCount": {}, - "route53:GetTrafficPolicyInstanceCount": {}, - "route53:ListCidrCollections": {}, - "route53:ListGeoLocations": {}, - "route53:ListHealthChecks": {}, - "route53:ListHostedZones": {}, - "route53:ListHostedZonesByName": {}, - "route53:ListHostedZonesByVPC": {}, - "route53:ListReusableDelegationSets": {}, - "route53:ListTrafficPolicies": {}, - "route53:ListTrafficPolicyInstances": {}, - "route53:TestDNSAnswer": {}, - "route53domains:AcceptDomainTransferFromAnotherAwsAccount": {}, - "route53domains:AssociateDelegationSignerToDomain": {}, - "route53domains:CancelDomainTransferToAnotherAwsAccount": {}, - "route53domains:CheckDomainAvailability": {}, - "route53domains:CheckDomainTransferability": {}, - "route53domains:DeleteDomain": {}, - "route53domains:DeleteTagsForDomain": {}, - "route53domains:DisableDomainAutoRenew": {}, - "route53domains:DisableDomainTransferLock": {}, - "route53domains:DisassociateDelegationSignerFromDomain": {}, - "route53domains:EnableDomainAutoRenew": {}, - "route53domains:EnableDomainTransferLock": {}, - "route53domains:GetContactReachabilityStatus": {}, - "route53domains:GetDomainDetail": {}, - "route53domains:GetDomainSuggestions": {}, - "route53domains:GetOperationDetail": {}, - "route53domains:ListDomains": {}, - "route53domains:ListOperations": {}, - "route53domains:ListPrices": {}, - "route53domains:ListTagsForDomain": {}, - "route53domains:PushDomain": {}, - "route53domains:RegisterDomain": {}, - "route53domains:RejectDomainTransferFromAnotherAwsAccount": {}, - "route53domains:RenewDomain": {}, - "route53domains:ResendContactReachabilityEmail": {}, - "route53domains:ResendOperationAuthorization": {}, - "route53domains:RetrieveDomainAuthCode": {}, - "route53domains:TransferDomain": {}, - "route53domains:TransferDomainToAnotherAwsAccount": {}, - "route53domains:UpdateDomainContact": {}, - "route53domains:UpdateDomainContactPrivacy": {}, - "route53domains:UpdateDomainNameservers": {}, - "route53domains:UpdateTagsForDomain": {}, - "route53domains:ViewBilling": {}, - "route53resolver:CreateResolverQueryLogConfig": {}, - "route53resolver:GetResolverQueryLogConfigAssociation": {}, - "route53resolver:ListFirewallConfigs": {}, - "route53resolver:ListFirewallDomainLists": {}, - "route53resolver:ListFirewallRuleGroupAssociations": {}, - "route53resolver:ListFirewallRuleGroups": {}, - "route53resolver:ListOutpostResolvers": {}, - "route53resolver:ListResolverEndpoints": {}, - "route53resolver:ListResolverQueryLogConfigAssociations": {}, - "route53resolver:ListResolverQueryLogConfigs": {}, - "route53resolver:ListResolverRuleAssociations": {}, - "route53resolver:ListResolverRules": {}, - "rum:ListAppMonitors": {}, - "rum:ListTagsForResource": {}, - "s3-outposts:GetAccessPoint": {}, - "s3-outposts:ListAccessPoints": {}, - "s3-outposts:ListEndpoints": {}, - "s3-outposts:ListOutpostsWithS3": {}, - "s3-outposts:ListRegionalBuckets": {}, - "s3-outposts:ListSharedEndpoints": {}, - "s3:CreateJob": {}, - "s3:CreateStorageLensGroup": {}, - "s3:GetAccessPoint": {}, - "s3:GetAccountPublicAccessBlock": {}, - "s3:ListAccessGrantsInstances": {}, - "s3:ListAccessPoints": {}, - "s3:ListAccessPointsForObjectLambda": {}, - "s3:ListAllMyBuckets": {}, - "s3:ListJobs": {}, - "s3:ListMultiRegionAccessPoints": {}, - "s3:ListStorageLensConfigurations": {}, - "s3:ListStorageLensGroups": {}, - "s3:PutAccessPointPublicAccessBlock": {}, - "s3:PutAccountPublicAccessBlock": {}, - "s3:PutStorageLensConfiguration": {}, - "s3express:ListAllMyDirectoryBuckets": {}, - "sagemaker-geospatial:ListEarthObservationJobs": {}, - "sagemaker-geospatial:ListRasterDataCollections": {}, - "sagemaker-geospatial:ListVectorEnrichmentJobs": {}, - "sagemaker-geospatial:SearchRasterDataCollection": {}, - "sagemaker-groundtruth-synthetic:CreateProject": {}, - "sagemaker-groundtruth-synthetic:DeleteProject": {}, - "sagemaker-groundtruth-synthetic:GetAccountDetails": {}, - "sagemaker-groundtruth-synthetic:GetBatch": {}, - "sagemaker-groundtruth-synthetic:GetProject": {}, - "sagemaker-groundtruth-synthetic:ListBatchDataTransfers": {}, - "sagemaker-groundtruth-synthetic:ListBatchSummaries": {}, - "sagemaker-groundtruth-synthetic:ListProjectDataTransfers": {}, - "sagemaker-groundtruth-synthetic:ListProjectSummaries": {}, - "sagemaker-groundtruth-synthetic:StartBatchDataTransfer": {}, - "sagemaker-groundtruth-synthetic:StartProjectDataTransfer": {}, - "sagemaker-groundtruth-synthetic:UpdateBatch": {}, - "sagemaker:CreateLineageGroupPolicy": {}, - "sagemaker:DeleteLineageGroupPolicy": {}, - "sagemaker:DescribeLineageGroup": {}, - "sagemaker:DisableSagemakerServicecatalogPortfolio": {}, - "sagemaker:EnableSagemakerServicecatalogPortfolio": {}, - "sagemaker:GetLineageGroupPolicy": {}, - "sagemaker:GetSagemakerServicecatalogPortfolioStatus": {}, - "sagemaker:GetSearchSuggestions": {}, - "sagemaker:ListActions": {}, - "sagemaker:ListAlgorithms": {}, - "sagemaker:ListAppImageConfigs": {}, - "sagemaker:ListApps": {}, - "sagemaker:ListArtifacts": {}, - "sagemaker:ListAssociations": {}, - "sagemaker:ListAutoMLJobs": {}, - "sagemaker:ListCandidatesForAutoMLJob": {}, - "sagemaker:ListClusters": {}, - "sagemaker:ListCodeRepositories": {}, - "sagemaker:ListCompilationJobs": {}, - "sagemaker:ListContexts": {}, - "sagemaker:ListDataQualityJobDefinitions": {}, - "sagemaker:ListDeviceFleets": {}, - "sagemaker:ListDevices": {}, - "sagemaker:ListDomains": {}, - "sagemaker:ListEdgeDeploymentPlans": {}, - "sagemaker:ListEdgePackagingJobs": {}, - "sagemaker:ListEndpointConfigs": {}, - "sagemaker:ListEndpoints": {}, - "sagemaker:ListExperiments": {}, - "sagemaker:ListFeatureGroups": {}, - "sagemaker:ListFlowDefinitions": {}, - "sagemaker:ListHubs": {}, - "sagemaker:ListHumanLoops": {}, - "sagemaker:ListHumanTaskUis": {}, - "sagemaker:ListHyperParameterTuningJobs": {}, - "sagemaker:ListImages": {}, - "sagemaker:ListInferenceComponents": {}, - "sagemaker:ListInferenceExperiments": {}, - "sagemaker:ListInferenceRecommendationsJobSteps": {}, - "sagemaker:ListInferenceRecommendationsJobs": {}, - "sagemaker:ListLabelingJobs": {}, - "sagemaker:ListLineageGroups": {}, - "sagemaker:ListModelBiasJobDefinitions": {}, - "sagemaker:ListModelCards": {}, - "sagemaker:ListModelExplainabilityJobDefinitions": {}, - "sagemaker:ListModelMetadata": {}, - "sagemaker:ListModelPackageGroups": {}, - "sagemaker:ListModelQualityJobDefinitions": {}, - "sagemaker:ListModels": {}, - "sagemaker:ListMonitoringAlertHistory": {}, - "sagemaker:ListMonitoringAlerts": {}, - "sagemaker:ListMonitoringExecutions": {}, - "sagemaker:ListMonitoringSchedules": {}, - "sagemaker:ListNotebookInstanceLifecycleConfigs": {}, - "sagemaker:ListNotebookInstances": {}, - "sagemaker:ListPipelines": {}, - "sagemaker:ListProcessingJobs": {}, - "sagemaker:ListProjects": {}, - "sagemaker:ListResourceCatalogs": {}, - "sagemaker:ListSharedModelEvents": {}, - "sagemaker:ListSharedModels": {}, - "sagemaker:ListSpaces": {}, - "sagemaker:ListStageDevices": {}, - "sagemaker:ListStudioLifecycleConfigs": {}, - "sagemaker:ListSubscribedWorkteams": {}, - "sagemaker:ListTrainingJobs": {}, - "sagemaker:ListTransformJobs": {}, - "sagemaker:ListTrialComponents": {}, - "sagemaker:ListTrials": {}, - "sagemaker:ListUserProfiles": {}, - "sagemaker:ListWorkforces": {}, - "sagemaker:ListWorkteams": {}, - "sagemaker:PutLineageGroupPolicy": {}, - "sagemaker:QueryLineage": {}, - "sagemaker:RenderUiTemplate": {}, - "sagemaker:Search": {}, - "savingsplans:CreateSavingsPlan": {}, - "savingsplans:DescribeSavingsPlansOfferingRates": {}, - "savingsplans:DescribeSavingsPlansOfferings": {}, - "scheduler:ListScheduleGroups": {}, - "scheduler:ListSchedules": {}, - "schemas:CreateDiscoverer": {}, - "schemas:GetDiscoveredSchema": {}, - "sdb:ListDomains": {}, - "secretsmanager:BatchGetSecretValue": {}, - "secretsmanager:GetRandomPassword": {}, - "secretsmanager:ListSecrets": {}, - "securityhub:BatchGetConfigurationPolicyAssociations": {}, - "securityhub:BatchGetSecurityControls": {}, - "securityhub:BatchGetStandardsControlAssociations": {}, - "securityhub:BatchUpdateStandardsControlAssociations": {}, - "securityhub:CreateAutomationRule": {}, - "securityhub:CreateConfigurationPolicy": {}, - "securityhub:CreateFindingAggregator": {}, - "securityhub:GetConfigurationPolicyAssociation": {}, - "securityhub:GetSecurityControlDefinition": {}, - "securityhub:ListAutomationRules": {}, - "securityhub:ListConfigurationPolicies": {}, - "securityhub:ListConfigurationPolicyAssociations": {}, - "securityhub:ListFindingAggregators": {}, - "securityhub:ListSecurityControlDefinitions": {}, - "securityhub:ListStandardsControlAssociations": {}, - "securityhub:UpdateSecurityControl": {}, - "securitylake:CreateDataLakeExceptionSubscription": {}, - "securitylake:CreateSubscriber": {}, - "securitylake:DeleteDataLakeExceptionSubscription": {}, - "securitylake:DeregisterDataLakeDelegatedAdministrator": {}, - "securitylake:GetDataLakeExceptionSubscription": {}, - "securitylake:ListDataLakeExceptions": {}, - "securitylake:ListDataLakes": {}, - "securitylake:ListLogSources": {}, - "securitylake:ListSubscribers": {}, - "securitylake:RegisterDataLakeDelegatedAdministrator": {}, - "securitylake:UpdateDataLakeExceptionSubscription": {}, - "serverlessrepo:CreateApplication": {}, - "serverlessrepo:ListApplications": {}, - "serverlessrepo:SearchApplications": {}, - "servicecatalog:AssociateBudgetWithResource": {}, - "servicecatalog:AssociateProductWithPortfolio": {}, - "servicecatalog:BatchAssociateServiceActionWithProvisioningArtifact": {}, + "aws-marketplace-management:PutBankAccountVerificationDetails": {}, + "aws-marketplace-management:PutSecondaryUserVerificationDetails": {}, + "aws-marketplace-management:PutSellerVerificationDetails": {}, + "aws-marketplace-management:uploadFiles": {}, + "aws-marketplace-management:viewMarketing": {}, + "aws-marketplace-management:viewReports": {}, + "aws-marketplace-management:viewSettings": {}, + "aws-marketplace-management:viewSupport": {}, + "aws-marketplace:AcceptAgreementApprovalRequest": {}, + "aws-marketplace:AcceptAgreementRequest": {}, + "aws-marketplace:AssociateProductsWithPrivateMarketplace": {}, + "aws-marketplace:BatchMeterUsage": {}, + "aws-marketplace:CancelAgreement": {}, + "aws-marketplace:CancelAgreementRequest": {}, + "aws-marketplace:CompleteTask": {}, + "aws-marketplace:CreateAgreementRequest": {}, + "aws-marketplace:CreatePrivateMarketplaceRequests": {}, + "aws-marketplace:DescribeAgreement": {}, + "aws-marketplace:DescribeBuilds": {}, + "aws-marketplace:DescribePrivateMarketplaceRequests": {}, + "aws-marketplace:DescribeProcurementSystemConfiguration": {}, + "aws-marketplace:DescribeTask": {}, + "aws-marketplace:DisassociateProductsFromPrivateMarketplace": {}, + "aws-marketplace:GetAgreementApprovalRequest": {}, + "aws-marketplace:GetAgreementRequest": {}, + "aws-marketplace:GetAgreementTerms": {}, + "aws-marketplace:ListAgreementApprovalRequests": {}, + "aws-marketplace:ListAgreementRequests": {}, + "aws-marketplace:ListBuilds": {}, + "aws-marketplace:ListChangeSets": {}, + "aws-marketplace:ListEntities": {}, + "aws-marketplace:ListEntitlementDetails": {}, + "aws-marketplace:ListPrivateListings": {}, + "aws-marketplace:ListPrivateMarketplaceRequests": {}, + "aws-marketplace:ListTasks": {}, + "aws-marketplace:MeterUsage": {}, + "aws-marketplace:PutProcurementSystemConfiguration": {}, + "aws-marketplace:RegisterUsage": {}, + "aws-marketplace:RejectAgreementApprovalRequest": {}, + "aws-marketplace:ResolveCustomer": {}, + "aws-marketplace:SearchAgreements": {}, + "aws-marketplace:StartBuild": {}, + "aws-marketplace:Subscribe": {}, + "aws-marketplace:Unsubscribe": {}, + "aws-marketplace:UpdateAgreementApprovalRequest": {}, + "aws-marketplace:UpdateTask": {}, + "aws-marketplace:ViewSubscriptions": {}, + "aws-portal:GetConsoleActionSetEnforced": {}, + "aws-portal:ModifyAccount": {}, + "aws-portal:ModifyBilling": {}, + "aws-portal:ModifyPaymentMethods": {}, + "aws-portal:UpdateConsoleActionSetEnforced": {}, + "aws-portal:ViewAccount": {}, + "aws-portal:ViewBilling": {}, + "aws-portal:ViewPaymentMethods": {}, + "aws-portal:ViewUsage": {}, + "awsconnector:GetConnectorHealth": {}, + "awsconnector:RegisterConnector": {}, + "awsconnector:ValidateConnectorId": {}, + "b2bi:CreateProfile": {}, + "b2bi:CreateTransformer": {}, + "b2bi:ListCapabilities": {}, + "b2bi:ListPartnerships": {}, + "b2bi:ListProfiles": {}, + "b2bi:ListTransformers": {}, + "backup-gateway:CreateGateway": {}, + "backup-gateway:ImportHypervisorConfiguration": {}, + "backup-gateway:ListGateways": {}, + "backup-gateway:ListHypervisors": {}, + "backup-gateway:ListVirtualMachines": {}, + "backup-storage:CommitBackupJob": {}, + "backup-storage:DeleteObjects": {}, + "backup-storage:DescribeBackupJob": {}, + "backup-storage:GetBaseBackup": {}, + "backup-storage:GetChunk": {}, + "backup-storage:GetIncrementalBaseBackup": {}, + "backup-storage:GetObjectMetadata": {}, + "backup-storage:ListChunks": {}, + "backup-storage:ListObjects": {}, + "backup-storage:MountCapsule": {}, + "backup-storage:NotifyObjectComplete": {}, + "backup-storage:PutChunk": {}, + "backup-storage:PutObject": {}, + "backup-storage:StartObject": {}, + "backup-storage:UpdateObjectComplete": {}, + "backup:DescribeBackupJob": {}, + "backup:DescribeCopyJob": {}, + "backup:DescribeGlobalSettings": {}, + "backup:DescribeProtectedResource": {}, + "backup:DescribeRegionSettings": {}, + "backup:DescribeReportJob": {}, + "backup:DescribeRestoreJob": {}, + "backup:ExportBackupPlanTemplate": {}, + "backup:GetBackupPlanFromJSON": {}, + "backup:GetBackupPlanFromTemplate": {}, + "backup:GetRestoreJobMetadata": {}, + "backup:GetRestoreTestingInferredMetadata": {}, + "backup:GetSupportedResourceTypes": {}, + "backup:ListBackupJobSummaries": {}, + "backup:ListBackupJobs": {}, + "backup:ListBackupPlanTemplates": {}, + "backup:ListBackupPlans": {}, + "backup:ListBackupVaults": {}, + "backup:ListCopyJobSummaries": {}, + "backup:ListCopyJobs": {}, + "backup:ListFrameworks": {}, + "backup:ListLegalHolds": {}, + "backup:ListProtectedResources": {}, + "backup:ListRecoveryPointsByResource": {}, + "backup:ListReportJobs": {}, + "backup:ListReportPlans": {}, + "backup:ListRestoreJobSummaries": {}, + "backup:ListRestoreJobs": {}, + "backup:ListRestoreJobsByProtectedResource": {}, + "backup:ListRestoreTestingPlans": {}, + "backup:PutRestoreValidationResult": {}, + "backup:StopBackupJob": {}, + "backup:UpdateGlobalSettings": {}, + "backup:UpdateRegionSettings": {}, + "batch:DescribeComputeEnvironments": {}, + "batch:DescribeJobDefinitions": {}, + "batch:DescribeJobQueues": {}, + "batch:DescribeJobs": {}, + "batch:DescribeSchedulingPolicies": {}, + "batch:ListJobs": {}, + "batch:ListSchedulingPolicies": {}, + "bcm-data-exports:ListExports": {}, + "bcm-data-exports:ListTables": {}, + "bedrock:AssociateThirdPartyKnowledgeBase": {}, + "bedrock:CreateAgent": {}, + "bedrock:CreateFoundationModelAgreement": {}, + "bedrock:CreateGuardrail": {}, + "bedrock:CreateKnowledgeBase": {}, + "bedrock:DeleteFoundationModelAgreement": {}, + "bedrock:DeleteModelInvocationLoggingConfiguration": {}, + "bedrock:GetFoundationModelAvailability": {}, + "bedrock:GetModelInvocationLoggingConfiguration": {}, + "bedrock:GetUseCaseForModelAccess": {}, + "bedrock:ListAgents": {}, + "bedrock:ListCustomModels": {}, + "bedrock:ListFoundationModelAgreementOffers": {}, + "bedrock:ListFoundationModels": {}, + "bedrock:ListKnowledgeBases": {}, + "bedrock:ListModelCustomizationJobs": {}, + "bedrock:ListModelEvaluationJobs": {}, + "bedrock:ListModelInvocationJobs": {}, + "bedrock:ListProvisionedModelThroughputs": {}, + "bedrock:PutFoundationModelEntitlement": {}, + "bedrock:PutModelInvocationLoggingConfiguration": {}, + "bedrock:PutUseCaseForModelAccess": {}, + "bedrock:RetrieveAndGenerate": {}, + "billing:GetBillingData": {}, + "billing:GetBillingDetails": {}, + "billing:GetBillingNotifications": {}, + "billing:GetBillingPreferences": {}, + "billing:GetContractInformation": {}, + "billing:GetCredits": {}, + "billing:GetIAMAccessPreference": {}, + "billing:GetSellerOfRecord": {}, + "billing:ListBillingViews": {}, + "billing:PutContractInformation": {}, + "billing:RedeemCredits": {}, + "billing:UpdateBillingPreferences": {}, + "billing:UpdateIAMAccessPreference": {}, + "billingconductor:CreatePricingRule": {}, + "billingconductor:ListAccountAssociations": {}, + "billingconductor:ListBillingGroupCostReports": {}, + "billingconductor:ListBillingGroups": {}, + "billingconductor:ListCustomLineItems": {}, + "billingconductor:ListPricingPlans": {}, + "billingconductor:ListPricingRules": {}, + "braket:AcceptUserAgreement": {}, + "braket:AccessBraketFeature": {}, + "braket:CreateJob": {}, + "braket:CreateQuantumTask": {}, + "braket:GetDevice": {}, + "braket:GetServiceLinkedRoleStatus": {}, + "braket:GetUserAgreementStatus": {}, + "braket:SearchDevices": {}, + "braket:SearchJobs": {}, + "braket:SearchQuantumTasks": {}, + "budgets:DescribeBudgetActionsForAccount": {}, + "bugbust:CreateEvent": {}, + "bugbust:ListEvents": {}, + "cases:CreateDomain": {}, + "cases:ListDomains": {}, + "cases:ListTagsForResource": {}, + "ce:CreateAnomalyMonitor": {}, + "ce:CreateAnomalySubscription": {}, + "ce:CreateCostCategoryDefinition": {}, + "ce:CreateNotificationSubscription": {}, + "ce:CreateReport": {}, + "ce:DeleteNotificationSubscription": {}, + "ce:DeleteReport": {}, + "ce:DescribeNotificationSubscription": {}, + "ce:DescribeReport": {}, + "ce:GetApproximateUsageRecords": {}, + "ce:GetConsoleActionSetEnforced": {}, + "ce:GetCostAndUsage": {}, + "ce:GetCostAndUsageWithResources": {}, + "ce:GetCostCategories": {}, + "ce:GetCostForecast": {}, + "ce:GetDimensionValues": {}, + "ce:GetPreferences": {}, + "ce:GetReservationCoverage": {}, + "ce:GetReservationPurchaseRecommendation": {}, + "ce:GetReservationUtilization": {}, + "ce:GetRightsizingRecommendation": {}, + "ce:GetSavingsPlanPurchaseRecommendationDetails": {}, + "ce:GetSavingsPlansCoverage": {}, + "ce:GetSavingsPlansPurchaseRecommendation": {}, + "ce:GetSavingsPlansUtilization": {}, + "ce:GetSavingsPlansUtilizationDetails": {}, + "ce:GetTags": {}, + "ce:GetUsageForecast": {}, + "ce:ListCostAllocationTags": {}, + "ce:ListCostCategoryDefinitions": {}, + "ce:ListSavingsPlansPurchaseRecommendationGeneration": {}, + "ce:ProvideAnomalyFeedback": {}, + "ce:StartSavingsPlansPurchaseRecommendationGeneration": {}, + "ce:UpdateConsoleActionSetEnforced": {}, + "ce:UpdateCostAllocationTagsStatus": {}, + "ce:UpdateNotificationSubscription": {}, + "ce:UpdatePreferences": {}, + "ce:UpdateReport": {}, + "chatbot:CreateChimeWebhookConfiguration": {}, + "chatbot:CreateMicrosoftTeamsChannelConfiguration": {}, + "chatbot:CreateSlackChannelConfiguration": {}, + "chatbot:DeleteMicrosoftTeamsChannelConfiguration": {}, + "chatbot:DeleteMicrosoftTeamsConfiguredTeam": {}, + "chatbot:DeleteMicrosoftTeamsUserIdentity": {}, + "chatbot:DeleteSlackUserIdentity": {}, + "chatbot:DeleteSlackWorkspaceAuthorization": {}, + "chatbot:DescribeChimeWebhookConfigurations": {}, + "chatbot:DescribeSlackChannelConfigurations": {}, + "chatbot:DescribeSlackChannels": {}, + "chatbot:DescribeSlackUserIdentities": {}, + "chatbot:DescribeSlackWorkspaces": {}, + "chatbot:GetAccountPreferences": {}, + "chatbot:GetMicrosoftTeamsChannelConfiguration": {}, + "chatbot:GetMicrosoftTeamsOauthParameters": {}, + "chatbot:GetSlackOauthParameters": {}, + "chatbot:ListMicrosoftTeamsChannelConfigurations": {}, + "chatbot:ListMicrosoftTeamsConfiguredTeams": {}, + "chatbot:ListMicrosoftTeamsUserIdentities": {}, + "chatbot:RedeemMicrosoftTeamsOauthCode": {}, + "chatbot:RedeemSlackOauthCode": {}, + "chatbot:UpdateAccountPreferences": {}, + "chatbot:UpdateMicrosoftTeamsChannelConfiguration": {}, + "chime:AcceptDelegate": {}, + "chime:ActivateUsers": {}, + "chime:AddDomain": {}, + "chime:AddOrUpdateGroups": {}, + "chime:AssociatePhoneNumberWithUser": {}, + "chime:AssociatePhoneNumbersWithVoiceConnectorGroup": {}, + "chime:AssociateSigninDelegateGroupsWithAccount": {}, + "chime:AuthorizeDirectory": {}, + "chime:BatchCreateRoomMembership": {}, + "chime:BatchDeletePhoneNumber": {}, + "chime:BatchSuspendUser": {}, + "chime:BatchUnsuspendUser": {}, + "chime:BatchUpdatePhoneNumber": {}, + "chime:BatchUpdateUser": {}, + "chime:ConnectDirectory": {}, + "chime:CreateAccount": {}, + "chime:CreateApiKey": {}, + "chime:CreateAppInstance": {}, + "chime:CreateAppInstanceBot": {}, + "chime:CreateAppInstanceUser": {}, + "chime:CreateBot": {}, + "chime:CreateCDRBucket": {}, + "chime:CreateMediaCapturePipeline": {}, + "chime:CreateMediaConcatenationPipeline": {}, + "chime:CreateMediaInsightsPipelineConfiguration": {}, + "chime:CreateMediaLiveConnectorPipeline": {}, + "chime:CreateMediaPipelineKinesisVideoStreamPool": {}, + "chime:CreateMeeting": {}, + "chime:CreateMeetingWithAttendees": {}, + "chime:CreatePhoneNumberOrder": {}, + "chime:CreateRoom": {}, + "chime:CreateRoomMembership": {}, + "chime:CreateSipMediaApplication": {}, + "chime:CreateUser": {}, + "chime:CreateVoiceConnector": {}, + "chime:CreateVoiceProfile": {}, + "chime:CreateVoiceProfileDomain": {}, + "chime:DeleteAccount": {}, + "chime:DeleteAccountOpenIdConfig": {}, + "chime:DeleteApiKey": {}, + "chime:DeleteCDRBucket": {}, + "chime:DeleteDelegate": {}, + "chime:DeleteDomain": {}, + "chime:DeleteEventsConfiguration": {}, + "chime:DeleteGroups": {}, + "chime:DeletePhoneNumber": {}, + "chime:DeleteRoom": {}, + "chime:DeleteRoomMembership": {}, + "chime:DeleteSipRule": {}, + "chime:DeleteVoiceConnectorGroup": {}, + "chime:DisassociatePhoneNumberFromUser": {}, + "chime:DisassociatePhoneNumbersFromVoiceConnectorGroup": {}, + "chime:DisassociateSigninDelegateGroupsFromAccount": {}, + "chime:DisconnectDirectory": {}, + "chime:GetAccount": {}, + "chime:GetAccountResource": {}, + "chime:GetAccountSettings": {}, + "chime:GetAccountWithOpenIdConfig": {}, + "chime:GetBot": {}, + "chime:GetCDRBucket": {}, + "chime:GetDomain": {}, + "chime:GetEventsConfiguration": {}, + "chime:GetGlobalSettings": {}, + "chime:GetMeetingDetail": {}, + "chime:GetMessagingSessionEndpoint": {}, + "chime:GetPhoneNumber": {}, + "chime:GetPhoneNumberOrder": {}, + "chime:GetPhoneNumberSettings": {}, + "chime:GetRetentionSettings": {}, + "chime:GetRoom": {}, + "chime:GetSipRule": {}, + "chime:GetTelephonyLimits": {}, + "chime:GetUser": {}, + "chime:GetUserActivityReportData": {}, + "chime:GetUserByEmail": {}, + "chime:GetUserSettings": {}, + "chime:GetVoiceConnectorGroup": {}, + "chime:InviteDelegate": {}, + "chime:InviteUsers": {}, + "chime:InviteUsersFromProvider": {}, + "chime:ListAccountUsageReportData": {}, + "chime:ListAccounts": {}, + "chime:ListApiKeys": {}, + "chime:ListAvailableVoiceConnectorRegions": {}, + "chime:ListBots": {}, + "chime:ListCDRBucket": {}, + "chime:ListCallingRegions": {}, + "chime:ListDelegates": {}, + "chime:ListDirectories": {}, + "chime:ListDomains": {}, + "chime:ListGroups": {}, + "chime:ListMediaCapturePipelines": {}, + "chime:ListMediaInsightsPipelineConfigurations": {}, + "chime:ListMediaPipelineKinesisVideoStreamPools": {}, + "chime:ListMediaPipelines": {}, + "chime:ListMeetingEvents": {}, + "chime:ListMeetings": {}, + "chime:ListMeetingsReportData": {}, + "chime:ListPhoneNumberOrders": {}, + "chime:ListPhoneNumbers": {}, + "chime:ListRoomMemberships": {}, + "chime:ListRooms": {}, + "chime:ListSipMediaApplications": {}, + "chime:ListSupportedPhoneNumberCountries": {}, + "chime:ListUsers": {}, + "chime:ListVoiceConnectorGroups": {}, + "chime:ListVoiceConnectors": {}, + "chime:ListVoiceProfileDomains": {}, + "chime:LogoutUser": {}, + "chime:PutEventsConfiguration": {}, + "chime:PutRetentionSettings": {}, + "chime:RedactConversationMessage": {}, + "chime:RedactRoomMessage": {}, + "chime:RegenerateSecurityToken": {}, + "chime:RenameAccount": {}, + "chime:RenewDelegate": {}, + "chime:ResetAccountResource": {}, + "chime:ResetPersonalPIN": {}, + "chime:RestorePhoneNumber": {}, + "chime:RetrieveDataExports": {}, + "chime:SearchAvailablePhoneNumbers": {}, + "chime:StartDataExport": {}, + "chime:StartMeetingTranscription": {}, + "chime:StopMeetingTranscription": {}, + "chime:SubmitSupportRequest": {}, + "chime:SuspendUsers": {}, + "chime:UnauthorizeDirectory": {}, + "chime:UpdateAccount": {}, + "chime:UpdateAccountOpenIdConfig": {}, + "chime:UpdateAccountResource": {}, + "chime:UpdateAccountSettings": {}, + "chime:UpdateBot": {}, + "chime:UpdateCDRSettings": {}, + "chime:UpdateGlobalSettings": {}, + "chime:UpdatePhoneNumber": {}, + "chime:UpdatePhoneNumberSettings": {}, + "chime:UpdateRoom": {}, + "chime:UpdateRoomMembership": {}, + "chime:UpdateSupportedLicenses": {}, + "chime:UpdateUser": {}, + "chime:UpdateUserLicenses": {}, + "chime:UpdateUserSettings": {}, + "chime:ValidateAccountResource": {}, + "chime:ValidateE911Address": {}, + "cleanrooms-ml:CreateTrainingDataset": {}, + "cleanrooms-ml:ListAudienceModels": {}, + "cleanrooms-ml:ListConfiguredAudienceModels": {}, + "cleanrooms-ml:ListTrainingDatasets": {}, + "cleanrooms:ListCollaborations": {}, + "cleanrooms:ListConfiguredTables": {}, + "cleanrooms:ListMemberships": {}, + "cloud9:CreateEnvironmentEC2": {}, + "cloud9:CreateEnvironmentSSH": {}, + "cloud9:GetMigrationExperiences": {}, + "cloud9:GetUserPublicKey": {}, + "cloud9:GetUserSettings": {}, + "cloud9:ListEnvironments": {}, + "cloud9:UpdateUserSettings": {}, + "cloud9:ValidateEnvironmentName": {}, + "clouddirectory:CreateSchema": {}, + "clouddirectory:ListDevelopmentSchemaArns": {}, + "clouddirectory:ListDirectories": {}, + "clouddirectory:ListManagedSchemaArns": {}, + "clouddirectory:ListPublishedSchemaArns": {}, + "clouddirectory:PutSchemaFromJson": {}, + "cloudformation:ActivateOrganizationsAccess": {}, + "cloudformation:ActivateType": {}, + "cloudformation:BatchDescribeTypeConfigurations": {}, + "cloudformation:CancelResourceRequest": {}, + "cloudformation:CreateGeneratedTemplate": {}, + "cloudformation:CreateResource": {}, + "cloudformation:CreateStackSet": {}, + "cloudformation:CreateUploadBucket": {}, + "cloudformation:DeactivateOrganizationsAccess": {}, + "cloudformation:DeactivateType": {}, + "cloudformation:DeleteGeneratedTemplate": {}, + "cloudformation:DeleteResource": {}, + "cloudformation:DeregisterType": {}, + "cloudformation:DescribeAccountLimits": {}, + "cloudformation:DescribeGeneratedTemplate": {}, + "cloudformation:DescribeOrganizationsAccess": {}, + "cloudformation:DescribePublisher": {}, + "cloudformation:DescribeResourceScan": {}, + "cloudformation:DescribeStackDriftDetectionStatus": {}, + "cloudformation:DescribeType": {}, + "cloudformation:DescribeTypeRegistration": {}, + "cloudformation:EstimateTemplateCost": {}, + "cloudformation:GetGeneratedTemplate": {}, + "cloudformation:GetResource": {}, + "cloudformation:GetResourceRequestStatus": {}, + "cloudformation:ListExports": {}, + "cloudformation:ListGeneratedTemplates": {}, + "cloudformation:ListImports": {}, + "cloudformation:ListResourceRequests": {}, + "cloudformation:ListResourceScanRelatedResources": {}, + "cloudformation:ListResourceScanResources": {}, + "cloudformation:ListResourceScans": {}, + "cloudformation:ListResources": {}, + "cloudformation:ListStackSets": {}, + "cloudformation:ListStacks": {}, + "cloudformation:ListTypeRegistrations": {}, + "cloudformation:ListTypeVersions": {}, + "cloudformation:ListTypes": {}, + "cloudformation:PublishType": {}, + "cloudformation:RegisterPublisher": {}, + "cloudformation:RegisterType": {}, + "cloudformation:SetTypeConfiguration": {}, + "cloudformation:SetTypeDefaultVersion": {}, + "cloudformation:StartResourceScan": {}, + "cloudformation:TestType": {}, + "cloudformation:UpdateGeneratedTemplate": {}, + "cloudformation:UpdateResource": {}, + "cloudformation:ValidateTemplate": {}, + "cloudfront:CreateFieldLevelEncryptionConfig": {}, + "cloudfront:CreateFieldLevelEncryptionProfile": {}, + "cloudfront:CreateKeyGroup": {}, + "cloudfront:CreateMonitoringSubscription": {}, + "cloudfront:CreateOriginAccessControl": {}, + "cloudfront:CreatePublicKey": {}, + "cloudfront:CreateSavingsPlan": {}, + "cloudfront:DeleteKeyGroup": {}, + "cloudfront:DeleteMonitoringSubscription": {}, + "cloudfront:DeletePublicKey": {}, + "cloudfront:GetKeyGroup": {}, + "cloudfront:GetKeyGroupConfig": {}, + "cloudfront:GetMonitoringSubscription": {}, + "cloudfront:GetPublicKey": {}, + "cloudfront:GetPublicKeyConfig": {}, + "cloudfront:GetSavingsPlan": {}, + "cloudfront:ListCachePolicies": {}, + "cloudfront:ListCloudFrontOriginAccessIdentities": {}, + "cloudfront:ListContinuousDeploymentPolicies": {}, + "cloudfront:ListDistributions": {}, + "cloudfront:ListDistributionsByCachePolicyId": {}, + "cloudfront:ListDistributionsByKeyGroup": {}, + "cloudfront:ListDistributionsByLambdaFunction": {}, + "cloudfront:ListDistributionsByOriginRequestPolicyId": {}, + "cloudfront:ListDistributionsByRealtimeLogConfig": {}, + "cloudfront:ListDistributionsByResponseHeadersPolicyId": {}, + "cloudfront:ListDistributionsByWebACLId": {}, + "cloudfront:ListFieldLevelEncryptionConfigs": {}, + "cloudfront:ListFieldLevelEncryptionProfiles": {}, + "cloudfront:ListFunctions": {}, + "cloudfront:ListKeyGroups": {}, + "cloudfront:ListKeyValueStores": {}, + "cloudfront:ListOriginAccessControls": {}, + "cloudfront:ListOriginRequestPolicies": {}, + "cloudfront:ListPublicKeys": {}, + "cloudfront:ListRateCards": {}, + "cloudfront:ListRealtimeLogConfigs": {}, + "cloudfront:ListResponseHeadersPolicies": {}, + "cloudfront:ListSavingsPlans": {}, + "cloudfront:ListStreamingDistributions": {}, + "cloudfront:ListUsages": {}, + "cloudfront:UpdateFieldLevelEncryptionConfig": {}, + "cloudfront:UpdateKeyGroup": {}, + "cloudfront:UpdatePublicKey": {}, + "cloudfront:UpdateSavingsPlan": {}, + "cloudhsm:AddTagsToResource": {}, + "cloudhsm:CreateHapg": {}, + "cloudhsm:CreateLunaClient": {}, + "cloudhsm:DeleteHapg": {}, + "cloudhsm:DeleteHsm": {}, + "cloudhsm:DeleteLunaClient": {}, + "cloudhsm:DescribeBackups": {}, + "cloudhsm:DescribeClusters": {}, + "cloudhsm:DescribeHapg": {}, + "cloudhsm:DescribeHsm": {}, + "cloudhsm:DescribeLunaClient": {}, + "cloudhsm:GetConfig": {}, + "cloudhsm:ListAvailableZones": {}, + "cloudhsm:ListHapgs": {}, + "cloudhsm:ListHsms": {}, + "cloudhsm:ListLunaClients": {}, + "cloudhsm:ListTagsForResource": {}, + "cloudhsm:ModifyHapg": {}, + "cloudhsm:ModifyHsm": {}, + "cloudhsm:ModifyLunaClient": {}, + "cloudhsm:RemoveTagsFromResource": {}, + "cloudshell:CreateEnvironment": {}, + "cloudtrail:DeregisterOrganizationDelegatedAdmin": {}, + "cloudtrail:DescribeTrails": {}, + "cloudtrail:GetImport": {}, + "cloudtrail:ListChannels": {}, + "cloudtrail:ListEventDataStores": {}, + "cloudtrail:ListImportFailures": {}, + "cloudtrail:ListImports": {}, + "cloudtrail:ListPublicKeys": {}, + "cloudtrail:ListServiceLinkedChannels": {}, + "cloudtrail:ListTrails": {}, + "cloudtrail:LookupEvents": {}, + "cloudtrail:RegisterOrganizationDelegatedAdmin": {}, + "cloudtrail:StartImport": {}, + "cloudtrail:StopImport": {}, + "cloudwatch:BatchGetServiceLevelIndicatorReport": {}, + "cloudwatch:CreateServiceLevelObjective": {}, + "cloudwatch:DeleteAnomalyDetector": {}, + "cloudwatch:DescribeAlarmsForMetric": {}, + "cloudwatch:DescribeAnomalyDetectors": {}, + "cloudwatch:DescribeInsightRules": {}, + "cloudwatch:EnableTopologyDiscovery": {}, + "cloudwatch:GenerateQuery": {}, + "cloudwatch:GetMetricData": {}, + "cloudwatch:GetMetricStatistics": {}, + "cloudwatch:GetMetricWidgetImage": {}, + "cloudwatch:GetTopologyDiscoveryStatus": {}, + "cloudwatch:GetTopologyMap": {}, + "cloudwatch:Link": {}, + "cloudwatch:ListDashboards": {}, + "cloudwatch:ListManagedInsightRules": {}, + "cloudwatch:ListMetricStreams": {}, + "cloudwatch:ListMetrics": {}, + "cloudwatch:ListServiceLevelObjectives": {}, + "cloudwatch:ListServices": {}, + "cloudwatch:PutAnomalyDetector": {}, + "cloudwatch:PutManagedInsightRules": {}, + "cloudwatch:PutMetricData": {}, + "codeartifact:CreateDomain": {}, + "codeartifact:CreateRepository": {}, + "codeartifact:ListDomains": {}, + "codeartifact:ListRepositories": {}, + "codebuild:DeleteOAuthToken": {}, + "codebuild:DeleteSourceCredentials": {}, + "codebuild:ImportSourceCredentials": {}, + "codebuild:ListBuildBatches": {}, + "codebuild:ListBuilds": {}, + "codebuild:ListConnectedOAuthAccounts": {}, + "codebuild:ListCuratedEnvironmentImages": {}, + "codebuild:ListFleets": {}, + "codebuild:ListProjects": {}, + "codebuild:ListReportGroups": {}, + "codebuild:ListReports": {}, + "codebuild:ListRepositories": {}, + "codebuild:ListSharedProjects": {}, + "codebuild:ListSharedReportGroups": {}, + "codebuild:ListSourceCredentials": {}, + "codebuild:PersistOAuthToken": {}, + "codecatalyst:AcceptConnection": {}, + "codecatalyst:CreateIdentityCenterApplication": {}, + "codecatalyst:CreateSpace": {}, + "codecatalyst:GetPendingConnection": {}, + "codecatalyst:ListConnections": {}, + "codecatalyst:ListIdentityCenterApplications": {}, + "codecatalyst:ListIdentityCenterApplicationsForSpace": {}, + "codecatalyst:RejectConnection": {}, + "codecommit:CreateApprovalRuleTemplate": {}, + "codecommit:DeleteApprovalRuleTemplate": {}, + "codecommit:GetApprovalRuleTemplate": {}, + "codecommit:ListApprovalRuleTemplates": {}, + "codecommit:ListRepositories": {}, + "codecommit:ListRepositoriesForApprovalRuleTemplate": {}, + "codecommit:UpdateApprovalRuleTemplateContent": {}, + "codecommit:UpdateApprovalRuleTemplateDescription": {}, + "codecommit:UpdateApprovalRuleTemplateName": {}, + "codedeploy-commands-secure:GetDeploymentSpecification": {}, + "codedeploy-commands-secure:PollHostCommand": {}, + "codedeploy-commands-secure:PutHostCommandAcknowledgement": {}, + "codedeploy-commands-secure:PutHostCommandComplete": {}, + "codedeploy:BatchGetDeploymentTargets": {}, + "codedeploy:ContinueDeployment": {}, + "codedeploy:DeleteGitHubAccountToken": {}, + "codedeploy:DeleteResourcesByExternalId": {}, + "codedeploy:GetDeploymentTarget": {}, + "codedeploy:ListApplications": {}, + "codedeploy:ListDeploymentConfigs": {}, + "codedeploy:ListDeploymentTargets": {}, + "codedeploy:ListGitHubAccountTokenNames": {}, + "codedeploy:ListOnPremisesInstances": {}, + "codedeploy:PutLifecycleEventHookExecutionStatus": {}, + "codedeploy:SkipWaitTimeForInstanceTermination": {}, + "codedeploy:StopDeployment": {}, + "codeguru-profiler:CreateProfilingGroup": {}, + "codeguru-profiler:GetFindingsReportAccountSummary": {}, + "codeguru-profiler:ListProfilingGroups": {}, + "codeguru-reviewer:AssociateRepository": {}, + "codeguru-reviewer:CreateConnectionToken": {}, + "codeguru-reviewer:GetMetricsData": {}, + "codeguru-reviewer:ListCodeReviews": {}, + "codeguru-reviewer:ListRepositoryAssociations": {}, + "codeguru-reviewer:ListThirdPartyRepositories": {}, + "codeguru-security:DeleteScansByCategory": {}, + "codeguru-security:GetAccountConfiguration": {}, + "codeguru-security:GetMetricsSummary": {}, + "codeguru-security:ListFindings": {}, + "codeguru-security:ListFindingsMetrics": {}, + "codeguru-security:ListScans": {}, + "codeguru-security:UpdateAccountConfiguration": {}, + "codeguru:GetCodeGuruFreeTrialSummary": {}, + "codepipeline:AcknowledgeJob": {}, + "codepipeline:AcknowledgeThirdPartyJob": {}, + "codepipeline:GetActionType": {}, + "codepipeline:GetJobDetails": {}, + "codepipeline:GetThirdPartyJobDetails": {}, + "codepipeline:ListActionTypes": {}, + "codepipeline:ListPipelines": {}, + "codepipeline:PollForThirdPartyJobs": {}, + "codepipeline:PutJobFailureResult": {}, + "codepipeline:PutJobSuccessResult": {}, + "codepipeline:PutThirdPartyJobFailureResult": {}, + "codepipeline:PutThirdPartyJobSuccessResult": {}, + "codestar-connections:CreateConnection": {}, + "codestar-connections:CreateHost": {}, + "codestar-connections:DeleteSyncConfiguration": {}, + "codestar-connections:GetIndividualAccessToken": {}, + "codestar-connections:GetInstallationUrl": {}, + "codestar-connections:GetResourceSyncStatus": {}, + "codestar-connections:GetSyncBlockerSummary": {}, + "codestar-connections:GetSyncConfiguration": {}, + "codestar-connections:ListHosts": {}, + "codestar-connections:ListInstallationTargets": {}, + "codestar-connections:ListRepositoryLinks": {}, + "codestar-connections:ListRepositorySyncDefinitions": {}, + "codestar-connections:ListSyncConfigurations": {}, + "codestar-connections:RegisterAppCode": {}, + "codestar-connections:StartAppRegistrationHandshake": {}, + "codestar-connections:StartOAuthHandshake": {}, + "codestar-connections:UpdateSyncBlocker": {}, + "codestar-connections:UpdateSyncConfiguration": {}, + "codestar-notifications:DeleteTarget": {}, + "codestar-notifications:ListEventTypes": {}, + "codestar-notifications:ListNotificationRules": {}, + "codestar-notifications:ListTargets": {}, + "codestar:CreateProject": {}, + "codestar:DescribeUserProfile": {}, + "codestar:ListProjects": {}, + "codestar:ListUserProfiles": {}, + "codewhisperer:GenerateRecommendations": {}, + "codewhisperer:ListProfiles": {}, + "cognito-identity:CreateIdentityPool": {}, + "cognito-identity:DeleteIdentities": {}, + "cognito-identity:DescribeIdentity": {}, + "cognito-identity:GetCredentialsForIdentity": {}, + "cognito-identity:GetId": {}, + "cognito-identity:GetOpenIdToken": {}, + "cognito-identity:ListIdentityPools": {}, + "cognito-identity:SetIdentityPoolRoles": {}, + "cognito-identity:SetPrincipalTagAttributeMap": {}, + "cognito-identity:UnlinkIdentity": {}, + "cognito-idp:AssociateSoftwareToken": {}, + "cognito-idp:ChangePassword": {}, + "cognito-idp:ConfirmDevice": {}, + "cognito-idp:ConfirmForgotPassword": {}, + "cognito-idp:ConfirmSignUp": {}, + "cognito-idp:CreateUserPool": {}, + "cognito-idp:DeleteUser": {}, + "cognito-idp:DeleteUserAttributes": {}, + "cognito-idp:DescribeUserPoolDomain": {}, + "cognito-idp:ForgetDevice": {}, + "cognito-idp:ForgotPassword": {}, + "cognito-idp:GetDevice": {}, + "cognito-idp:GetUser": {}, + "cognito-idp:GetUserAttributeVerificationCode": {}, + "cognito-idp:GlobalSignOut": {}, + "cognito-idp:InitiateAuth": {}, + "cognito-idp:ListDevices": {}, + "cognito-idp:ListUserPools": {}, + "cognito-idp:ResendConfirmationCode": {}, + "cognito-idp:RespondToAuthChallenge": {}, + "cognito-idp:RevokeToken": {}, + "cognito-idp:SetUserMFAPreference": {}, + "cognito-idp:SetUserSettings": {}, + "cognito-idp:SignUp": {}, + "cognito-idp:UpdateDeviceStatus": {}, + "cognito-idp:UpdateUserAttributes": {}, + "cognito-idp:VerifySoftwareToken": {}, + "cognito-idp:VerifyUserAttribute": {}, + "comprehend:BatchDetectDominantLanguage": {}, + "comprehend:BatchDetectEntities": {}, + "comprehend:BatchDetectKeyPhrases": {}, + "comprehend:BatchDetectSentiment": {}, + "comprehend:BatchDetectSyntax": {}, + "comprehend:BatchDetectTargetedSentiment": {}, + "comprehend:ContainsPiiEntities": {}, + "comprehend:DetectDominantLanguage": {}, + "comprehend:DetectKeyPhrases": {}, + "comprehend:DetectPiiEntities": {}, + "comprehend:DetectSentiment": {}, + "comprehend:DetectSyntax": {}, + "comprehend:DetectTargetedSentiment": {}, + "comprehend:DetectToxicContent": {}, + "comprehend:ListDocumentClassificationJobs": {}, + "comprehend:ListDocumentClassifierSummaries": {}, + "comprehend:ListDocumentClassifiers": {}, + "comprehend:ListDominantLanguageDetectionJobs": {}, + "comprehend:ListEndpoints": {}, + "comprehend:ListEntitiesDetectionJobs": {}, + "comprehend:ListEntityRecognizerSummaries": {}, + "comprehend:ListEntityRecognizers": {}, + "comprehend:ListEventsDetectionJobs": {}, + "comprehend:ListFlywheels": {}, + "comprehend:ListKeyPhrasesDetectionJobs": {}, + "comprehend:ListPiiEntitiesDetectionJobs": {}, + "comprehend:ListSentimentDetectionJobs": {}, + "comprehend:ListTargetedSentimentDetectionJobs": {}, + "comprehend:ListTopicsDetectionJobs": {}, + "comprehendmedical:DescribeEntitiesDetectionV2Job": {}, + "comprehendmedical:DescribeICD10CMInferenceJob": {}, + "comprehendmedical:DescribePHIDetectionJob": {}, + "comprehendmedical:DescribeRxNormInferenceJob": {}, + "comprehendmedical:DescribeSNOMEDCTInferenceJob": {}, + "comprehendmedical:DetectEntitiesV2": {}, + "comprehendmedical:DetectPHI": {}, + "comprehendmedical:InferICD10CM": {}, + "comprehendmedical:InferRxNorm": {}, + "comprehendmedical:InferSNOMEDCT": {}, + "comprehendmedical:ListEntitiesDetectionV2Jobs": {}, + "comprehendmedical:ListICD10CMInferenceJobs": {}, + "comprehendmedical:ListPHIDetectionJobs": {}, + "comprehendmedical:ListRxNormInferenceJobs": {}, + "comprehendmedical:ListSNOMEDCTInferenceJobs": {}, + "comprehendmedical:StartEntitiesDetectionV2Job": {}, + "comprehendmedical:StartICD10CMInferenceJob": {}, + "comprehendmedical:StartPHIDetectionJob": {}, + "comprehendmedical:StartRxNormInferenceJob": {}, + "comprehendmedical:StartSNOMEDCTInferenceJob": {}, + "comprehendmedical:StopEntitiesDetectionV2Job": {}, + "comprehendmedical:StopICD10CMInferenceJob": {}, + "comprehendmedical:StopPHIDetectionJob": {}, + "comprehendmedical:StopRxNormInferenceJob": {}, + "comprehendmedical:StopSNOMEDCTInferenceJob": {}, + "compute-optimizer:DeleteRecommendationPreferences": {}, + "compute-optimizer:DescribeRecommendationExportJobs": {}, + "compute-optimizer:ExportAutoScalingGroupRecommendations": {}, + "compute-optimizer:ExportEBSVolumeRecommendations": {}, + "compute-optimizer:ExportEC2InstanceRecommendations": {}, + "compute-optimizer:ExportECSServiceRecommendations": {}, + "compute-optimizer:ExportLambdaFunctionRecommendations": {}, + "compute-optimizer:ExportLicenseRecommendations": {}, + "compute-optimizer:GetAutoScalingGroupRecommendations": {}, + "compute-optimizer:GetEBSVolumeRecommendations": {}, + "compute-optimizer:GetEC2InstanceRecommendations": {}, + "compute-optimizer:GetEC2RecommendationProjectedMetrics": {}, + "compute-optimizer:GetECSServiceRecommendationProjectedMetrics": {}, + "compute-optimizer:GetECSServiceRecommendations": {}, + "compute-optimizer:GetEffectiveRecommendationPreferences": {}, + "compute-optimizer:GetEnrollmentStatus": {}, + "compute-optimizer:GetEnrollmentStatusesForOrganization": {}, + "compute-optimizer:GetLambdaFunctionRecommendations": {}, + "compute-optimizer:GetLicenseRecommendations": {}, + "compute-optimizer:GetRecommendationPreferences": {}, + "compute-optimizer:GetRecommendationSummaries": {}, + "compute-optimizer:PutRecommendationPreferences": {}, + "compute-optimizer:UpdateEnrollmentStatus": {}, + "config:BatchGetResourceConfig": {}, + "config:DeleteConfigurationRecorder": {}, + "config:DeleteDeliveryChannel": {}, + "config:DeletePendingAggregationRequest": {}, + "config:DeleteRemediationExceptions": {}, + "config:DeleteResourceConfig": {}, + "config:DeleteRetentionConfiguration": {}, + "config:DeliverConfigSnapshot": {}, + "config:DescribeAggregationAuthorizations": {}, + "config:DescribeComplianceByConfigRule": {}, + "config:DescribeComplianceByResource": {}, + "config:DescribeConfigRuleEvaluationStatus": {}, + "config:DescribeConfigRules": {}, + "config:DescribeConfigurationAggregators": {}, + "config:DescribeConfigurationRecorderStatus": {}, + "config:DescribeConfigurationRecorders": {}, + "config:DescribeConformancePackStatus": {}, + "config:DescribeConformancePacks": {}, + "config:DescribeDeliveryChannelStatus": {}, + "config:DescribeDeliveryChannels": {}, + "config:DescribeOrganizationConfigRuleStatuses": {}, + "config:DescribeOrganizationConfigRules": {}, + "config:DescribeOrganizationConformancePackStatuses": {}, + "config:DescribeOrganizationConformancePacks": {}, + "config:DescribePendingAggregationRequests": {}, + "config:DescribeRemediationExceptions": {}, + "config:DescribeRetentionConfigurations": {}, + "config:GetComplianceDetailsByResource": {}, + "config:GetComplianceSummaryByConfigRule": {}, + "config:GetComplianceSummaryByResourceType": {}, + "config:GetDiscoveredResourceCounts": {}, + "config:GetResourceConfigHistory": {}, + "config:GetResourceEvaluationSummary": {}, + "config:ListConformancePackComplianceScores": {}, + "config:ListDiscoveredResources": {}, + "config:ListResourceEvaluations": {}, + "config:ListStoredQueries": {}, + "config:PutConfigurationRecorder": {}, + "config:PutDeliveryChannel": {}, + "config:PutEvaluations": {}, + "config:PutRemediationExceptions": {}, + "config:PutResourceConfig": {}, + "config:PutRetentionConfiguration": {}, + "config:SelectResourceConfig": {}, + "config:StartConfigurationRecorder": {}, + "config:StartRemediationExecution": {}, + "config:StartResourceEvaluation": {}, + "config:StopConfigurationRecorder": {}, + "connect-campaigns:DeleteConnectInstanceConfig": {}, + "connect-campaigns:DeleteInstanceOnboardingJob": {}, + "connect-campaigns:GetConnectInstanceConfig": {}, + "connect-campaigns:GetInstanceOnboardingJobStatus": {}, + "connect-campaigns:ListCampaigns": {}, + "connect-campaigns:StartInstanceOnboardingJob": {}, + "connect:CreateInstance": {}, + "connect:ListInstances": {}, + "connect:SendChatIntegrationEvent": {}, + "consoleapp:ListDeviceIdentities": {}, + "consolidatedbilling:GetAccountBillingRole": {}, + "consolidatedbilling:ListLinkedAccounts": {}, + "controltower:CreateLandingZone": {}, + "controltower:CreateManagedAccount": {}, + "controltower:DeregisterManagedAccount": {}, + "controltower:DeregisterOrganizationalUnit": {}, + "controltower:DescribeAccountFactoryConfig": {}, + "controltower:DescribeCoreService": {}, + "controltower:DescribeGuardrail": {}, + "controltower:DescribeGuardrailForTarget": {}, + "controltower:DescribeLandingZoneConfiguration": {}, + "controltower:DescribeManagedAccount": {}, + "controltower:DescribeManagedOrganizationalUnit": {}, + "controltower:DescribeRegisterOrganizationalUnitOperation": {}, + "controltower:DescribeSingleSignOn": {}, + "controltower:DisableGuardrail": {}, + "controltower:EnableGuardrail": {}, + "controltower:GetAccountInfo": {}, + "controltower:GetAvailableUpdates": {}, + "controltower:GetControlOperation": {}, + "controltower:GetGuardrailComplianceStatus": {}, + "controltower:GetHomeRegion": {}, + "controltower:GetLandingZoneDriftStatus": {}, + "controltower:GetLandingZoneOperation": {}, + "controltower:GetLandingZoneStatus": {}, + "controltower:ListDirectoryGroups": {}, + "controltower:ListDriftDetails": {}, + "controltower:ListEnabledControls": {}, + "controltower:ListEnabledGuardrails": {}, + "controltower:ListExtendGovernancePrecheckDetails": {}, + "controltower:ListExternalConfigRuleCompliance": {}, + "controltower:ListGuardrailViolations": {}, + "controltower:ListGuardrails": {}, + "controltower:ListGuardrailsForTarget": {}, + "controltower:ListLandingZones": {}, + "controltower:ListManagedAccounts": {}, + "controltower:ListManagedAccountsForGuardrail": {}, + "controltower:ListManagedAccountsForParent": {}, + "controltower:ListManagedOrganizationalUnits": {}, + "controltower:ListManagedOrganizationalUnitsForGuardrail": {}, + "controltower:ManageOrganizationalUnit": {}, + "controltower:PerformPreLaunchChecks": {}, + "controltower:SetupLandingZone": {}, + "controltower:UpdateAccountFactoryConfig": {}, + "cost-optimization-hub:GetPreferences": {}, + "cost-optimization-hub:GetRecommendation": {}, + "cost-optimization-hub:ListEnrollmentStatuses": {}, + "cost-optimization-hub:ListRecommendationSummaries": {}, + "cost-optimization-hub:ListRecommendations": {}, + "cost-optimization-hub:UpdateEnrollmentStatus": {}, + "cost-optimization-hub:UpdatePreferences": {}, + "cur:DescribeReportDefinitions": {}, + "cur:GetClassicReport": {}, + "cur:GetClassicReportPreferences": {}, + "cur:GetUsageReport": {}, + "cur:PutClassicReportPreferences": {}, + "cur:ValidateReportDestination": {}, + "customer-verification:CreateCustomerVerificationDetails": {}, + "customer-verification:GetCustomerVerificationDetails": {}, + "customer-verification:GetCustomerVerificationEligibility": {}, + "customer-verification:UpdateCustomerVerificationDetails": {}, + "databrew:CreateDataset": {}, + "databrew:CreateProfileJob": {}, + "databrew:CreateProject": {}, + "databrew:CreateRecipe": {}, + "databrew:CreateRecipeJob": {}, + "databrew:CreateRuleset": {}, + "databrew:CreateSchedule": {}, + "databrew:ListDatasets": {}, + "databrew:ListJobs": {}, + "databrew:ListProjects": {}, + "databrew:ListRecipes": {}, + "databrew:ListRulesets": {}, + "databrew:ListSchedules": {}, + "dataexchange:CreateDataSet": {}, + "dataexchange:CreateEventAction": {}, + "dataexchange:CreateJob": {}, + "dataexchange:ListDataSets": {}, + "dataexchange:ListEventActions": {}, + "dataexchange:ListJobs": {}, + "datapipeline:CreatePipeline": {}, + "datapipeline:GetAccountLimits": {}, + "datapipeline:ListPipelines": {}, + "datapipeline:PollForTask": {}, + "datapipeline:PutAccountLimits": {}, + "datapipeline:ReportTaskRunnerHeartbeat": {}, + "datasync:CreateAgent": {}, + "datasync:CreateLocationAzureBlob": {}, + "datasync:CreateLocationEfs": {}, + "datasync:CreateLocationFsxLustre": {}, + "datasync:CreateLocationFsxOntap": {}, + "datasync:CreateLocationFsxOpenZfs": {}, + "datasync:CreateLocationFsxWindows": {}, + "datasync:CreateLocationHdfs": {}, + "datasync:CreateLocationNfs": {}, + "datasync:CreateLocationObjectStorage": {}, + "datasync:CreateLocationS3": {}, + "datasync:CreateLocationSmb": {}, + "datasync:ListAgents": {}, + "datasync:ListDiscoveryJobs": {}, + "datasync:ListLocations": {}, + "datasync:ListStorageSystems": {}, + "datasync:ListTaskExecutions": {}, + "datasync:ListTasks": {}, + "datazone:AcceptPredictions": {}, + "datazone:AcceptSubscriptionRequest": {}, + "datazone:CancelSubscription": {}, + "datazone:CreateAsset": {}, + "datazone:CreateAssetRevision": {}, + "datazone:CreateAssetType": {}, + "datazone:CreateDataSource": {}, + "datazone:CreateDomain": {}, + "datazone:CreateEnvironment": {}, + "datazone:CreateEnvironmentBlueprint": {}, + "datazone:CreateEnvironmentProfile": {}, + "datazone:CreateFormType": {}, + "datazone:CreateGlossary": {}, + "datazone:CreateGlossaryTerm": {}, + "datazone:CreateGroupProfile": {}, + "datazone:CreateListingChangeSet": {}, + "datazone:CreateProject": {}, + "datazone:CreateProjectMembership": {}, + "datazone:CreateSubscriptionGrant": {}, + "datazone:CreateSubscriptionRequest": {}, + "datazone:CreateSubscriptionTarget": {}, + "datazone:CreateUserProfile": {}, + "datazone:DeleteAsset": {}, + "datazone:DeleteAssetType": {}, + "datazone:DeleteDataSource": {}, + "datazone:DeleteDomainSharingPolicy": {}, + "datazone:DeleteEnvironment": {}, + "datazone:DeleteEnvironmentBlueprint": {}, + "datazone:DeleteEnvironmentBlueprintConfiguration": {}, + "datazone:DeleteEnvironmentProfile": {}, + "datazone:DeleteFormType": {}, + "datazone:DeleteGlossary": {}, + "datazone:DeleteGlossaryTerm": {}, + "datazone:DeleteListing": {}, + "datazone:DeleteProject": {}, + "datazone:DeleteProjectMembership": {}, + "datazone:DeleteSubscriptionGrant": {}, + "datazone:DeleteSubscriptionRequest": {}, + "datazone:DeleteSubscriptionTarget": {}, + "datazone:GetAsset": {}, + "datazone:GetAssetType": {}, + "datazone:GetDataSource": {}, + "datazone:GetDataSourceRun": {}, + "datazone:GetDomainSharingPolicy": {}, + "datazone:GetEnvironment": {}, + "datazone:GetEnvironmentActionLink": {}, + "datazone:GetEnvironmentBlueprint": {}, + "datazone:GetEnvironmentBlueprintConfiguration": {}, + "datazone:GetEnvironmentCredentials": {}, + "datazone:GetEnvironmentProfile": {}, + "datazone:GetFormType": {}, + "datazone:GetGlossary": {}, + "datazone:GetGlossaryTerm": {}, + "datazone:GetGroupProfile": {}, + "datazone:GetIamPortalLoginUrl": {}, + "datazone:GetListing": {}, + "datazone:GetMetadataGenerationRun": {}, + "datazone:GetProject": {}, + "datazone:GetSubscription": {}, + "datazone:GetSubscriptionEligibility": {}, + "datazone:GetSubscriptionGrant": {}, + "datazone:GetSubscriptionRequestDetails": {}, + "datazone:GetSubscriptionTarget": {}, + "datazone:GetUserProfile": {}, + "datazone:ListAccountEnvironments": {}, + "datazone:ListAssetRevisions": {}, + "datazone:ListDataSourceRunActivities": {}, + "datazone:ListDataSourceRuns": {}, + "datazone:ListDataSources": {}, + "datazone:ListDomains": {}, + "datazone:ListEnvironmentBlueprintConfigurationSummaries": {}, + "datazone:ListEnvironmentBlueprintConfigurations": {}, + "datazone:ListEnvironmentBlueprints": {}, + "datazone:ListEnvironmentProfiles": {}, + "datazone:ListEnvironments": {}, + "datazone:ListGroupsForUser": {}, + "datazone:ListMetadataGenerationRuns": {}, + "datazone:ListNotifications": {}, + "datazone:ListProjectMemberships": {}, + "datazone:ListProjects": {}, + "datazone:ListSubscriptionGrants": {}, + "datazone:ListSubscriptionRequests": {}, + "datazone:ListSubscriptionTargets": {}, + "datazone:ListSubscriptions": {}, + "datazone:ListWarehouseMetadata": {}, + "datazone:ProvisionDomain": {}, + "datazone:PutDomainSharingPolicy": {}, + "datazone:PutEnvironmentBlueprintConfiguration": {}, + "datazone:RefreshToken": {}, + "datazone:RejectPredictions": {}, + "datazone:RejectSubscriptionRequest": {}, + "datazone:RevokeSubscription": {}, + "datazone:Search": {}, + "datazone:SearchGroupProfiles": {}, + "datazone:SearchListings": {}, + "datazone:SearchTypes": {}, + "datazone:SearchUserProfiles": {}, + "datazone:SsoLogin": {}, + "datazone:SsoLogout": {}, + "datazone:StartDataSourceRun": {}, + "datazone:StartMetadataGenerationRun": {}, + "datazone:StopMetadataGenerationRun": {}, + "datazone:UpdateDataSource": {}, + "datazone:UpdateEnvironment": {}, + "datazone:UpdateEnvironmentBlueprint": {}, + "datazone:UpdateEnvironmentConfiguration": {}, + "datazone:UpdateEnvironmentDeploymentStatus": {}, + "datazone:UpdateEnvironmentProfile": {}, + "datazone:UpdateGlossary": {}, + "datazone:UpdateGlossaryTerm": {}, + "datazone:UpdateGroupProfile": {}, + "datazone:UpdateProject": {}, + "datazone:UpdateSubscriptionGrantStatus": {}, + "datazone:UpdateSubscriptionRequest": {}, + "datazone:UpdateSubscriptionTarget": {}, + "datazone:UpdateUserProfile": {}, + "datazone:ValidatePassRole": {}, + "dax:CreateParameterGroup": {}, + "dax:CreateSubnetGroup": {}, + "dax:DeleteParameterGroup": {}, + "dax:DeleteSubnetGroup": {}, + "dax:DescribeDefaultParameters": {}, + "dax:DescribeEvents": {}, + "dax:DescribeParameterGroups": {}, + "dax:DescribeParameters": {}, + "dax:DescribeSubnetGroups": {}, + "dax:UpdateParameterGroup": {}, + "dax:UpdateSubnetGroup": {}, + "dbqms:CreateFavoriteQuery": {}, + "dbqms:CreateTab": {}, + "dbqms:DeleteFavoriteQueries": {}, + "dbqms:DeleteQueryHistory": {}, + "dbqms:DeleteTab": {}, + "dbqms:DescribeFavoriteQueries": {}, + "dbqms:DescribeQueryHistory": {}, + "dbqms:DescribeTabs": {}, + "dbqms:GetQueryString": {}, + "dbqms:UpdateFavoriteQuery": {}, + "dbqms:UpdateQueryHistory": {}, + "dbqms:UpdateTab": {}, + "deepcomposer:AssociateCoupon": {}, + "deepracer:AdminGetAccountConfig": {}, + "deepracer:AdminListAssociatedResources": {}, + "deepracer:AdminListAssociatedUsers": {}, + "deepracer:AdminManageUser": {}, + "deepracer:AdminSetAccountConfig": {}, + "deepracer:CreateCar": {}, + "deepracer:CreateLeaderboard": {}, + "deepracer:GetAccountConfig": {}, + "deepracer:GetAlias": {}, + "deepracer:GetCars": {}, + "deepracer:ImportModel": {}, + "deepracer:ListLeaderboards": {}, + "deepracer:ListModels": {}, + "deepracer:ListPrivateLeaderboards": {}, + "deepracer:ListSubscribedPrivateLeaderboards": {}, + "deepracer:ListTracks": {}, + "deepracer:MigrateModels": {}, + "deepracer:SetAlias": {}, + "deepracer:TestRewardFunction": {}, + "detective:AcceptInvitation": {}, + "detective:BatchGetMembershipDatasources": {}, + "detective:CreateGraph": {}, + "detective:DisableOrganizationAdminAccount": {}, + "detective:DisassociateMembership": {}, + "detective:EnableOrganizationAdminAccount": {}, + "detective:GetPricingInformation": {}, + "detective:ListGraphs": {}, + "detective:ListInvitations": {}, + "detective:ListOrganizationAdminAccount": {}, + "detective:RejectInvitation": {}, + "devicefarm:CreateInstanceProfile": {}, + "devicefarm:CreateProject": {}, + "devicefarm:CreateTestGridProject": {}, + "devicefarm:CreateVPCEConfiguration": {}, + "devicefarm:GetAccountSettings": {}, + "devicefarm:GetOfferingStatus": {}, + "devicefarm:ListDeviceInstances": {}, + "devicefarm:ListDevices": {}, + "devicefarm:ListInstanceProfiles": {}, + "devicefarm:ListOfferingPromotions": {}, + "devicefarm:ListOfferingTransactions": {}, + "devicefarm:ListOfferings": {}, + "devicefarm:ListProjects": {}, + "devicefarm:ListTestGridProjects": {}, + "devicefarm:ListVPCEConfigurations": {}, + "devicefarm:PurchaseOffering": {}, + "devicefarm:RenewOffering": {}, + "devops-guru:DeleteInsight": {}, + "devops-guru:DescribeAccountHealth": {}, + "devops-guru:DescribeAccountOverview": {}, + "devops-guru:DescribeAnomaly": {}, + "devops-guru:DescribeEventSourcesConfig": {}, + "devops-guru:DescribeFeedback": {}, + "devops-guru:DescribeInsight": {}, + "devops-guru:DescribeOrganizationHealth": {}, + "devops-guru:DescribeOrganizationOverview": {}, + "devops-guru:DescribeOrganizationResourceCollectionHealth": {}, + "devops-guru:DescribeResourceCollectionHealth": {}, + "devops-guru:DescribeServiceIntegration": {}, + "devops-guru:GetCostEstimation": {}, + "devops-guru:GetResourceCollection": {}, + "devops-guru:ListAnomaliesForInsight": {}, + "devops-guru:ListAnomalousLogGroups": {}, + "devops-guru:ListEvents": {}, + "devops-guru:ListInsights": {}, + "devops-guru:ListMonitoredResources": {}, + "devops-guru:ListNotificationChannels": {}, + "devops-guru:ListOrganizationInsights": {}, + "devops-guru:ListRecommendations": {}, + "devops-guru:PutFeedback": {}, + "devops-guru:SearchInsights": {}, + "devops-guru:SearchOrganizationInsights": {}, + "devops-guru:StartCostEstimation": {}, + "devops-guru:UpdateEventSourcesConfig": {}, + "devops-guru:UpdateResourceCollection": {}, + "devops-guru:UpdateServiceIntegration": {}, + "directconnect:ConfirmCustomerAgreement": {}, + "directconnect:CreateDirectConnectGateway": {}, + "directconnect:DeleteDirectConnectGatewayAssociationProposal": {}, + "directconnect:DescribeCustomerMetadata": {}, + "directconnect:DescribeLocations": {}, + "directconnect:DescribeVirtualGateways": {}, + "directconnect:UpdateDirectConnectGatewayAssociation": {}, + "discovery:AssociateConfigurationItemsToApplication": {}, + "discovery:BatchDeleteAgents": {}, + "discovery:BatchDeleteImportData": {}, + "discovery:CreateApplication": {}, + "discovery:CreateTags": {}, + "discovery:DeleteApplications": {}, + "discovery:DeleteTags": {}, + "discovery:DescribeAgents": {}, + "discovery:DescribeBatchDeleteConfigurationTask": {}, + "discovery:DescribeConfigurations": {}, + "discovery:DescribeContinuousExports": {}, + "discovery:DescribeExportConfigurations": {}, + "discovery:DescribeExportTasks": {}, + "discovery:DescribeImportTasks": {}, + "discovery:DescribeTags": {}, + "discovery:DisassociateConfigurationItemsFromApplication": {}, + "discovery:ExportConfigurations": {}, + "discovery:GetDiscoverySummary": {}, + "discovery:GetNetworkConnectionGraph": {}, + "discovery:ListConfigurations": {}, + "discovery:ListServerNeighbors": {}, + "discovery:StartBatchDeleteConfigurationTask": {}, + "discovery:StartContinuousExport": {}, + "discovery:StartDataCollectionByAgentIds": {}, + "discovery:StartExportTask": {}, + "discovery:StartImportTask": {}, + "discovery:StopContinuousExport": {}, + "discovery:StopDataCollectionByAgentIds": {}, + "discovery:UpdateApplication": {}, + "dlm:CreateLifecyclePolicy": {}, + "dlm:GetLifecyclePolicies": {}, + "dms:BatchStartRecommendations": {}, + "dms:CreateDataProvider": {}, + "dms:CreateEndpoint": {}, + "dms:CreateEventSubscription": {}, + "dms:CreateFleetAdvisorCollector": {}, + "dms:CreateInstanceProfile": {}, + "dms:CreateReplicationInstance": {}, + "dms:CreateReplicationSubnetGroup": {}, + "dms:DeleteFleetAdvisorCollector": {}, + "dms:DeleteFleetAdvisorDatabases": {}, + "dms:DescribeAccountAttributes": {}, + "dms:DescribeCertificates": {}, + "dms:DescribeConnections": {}, + "dms:DescribeDataMigrations": {}, + "dms:DescribeEndpointSettings": {}, + "dms:DescribeEndpointTypes": {}, + "dms:DescribeEndpoints": {}, + "dms:DescribeEngineVersions": {}, + "dms:DescribeEventCategories": {}, + "dms:DescribeEventSubscriptions": {}, + "dms:DescribeEvents": {}, + "dms:DescribeFleetAdvisorCollectors": {}, + "dms:DescribeFleetAdvisorDatabases": {}, + "dms:DescribeFleetAdvisorLsaAnalysis": {}, + "dms:DescribeFleetAdvisorSchemaObjectSummary": {}, + "dms:DescribeFleetAdvisorSchemas": {}, + "dms:DescribeOrderableReplicationInstances": {}, + "dms:DescribePendingMaintenanceActions": {}, + "dms:DescribeRecommendationLimitations": {}, + "dms:DescribeRecommendations": {}, + "dms:DescribeReplicationConfigs": {}, + "dms:DescribeReplicationInstances": {}, + "dms:DescribeReplicationSubnetGroups": {}, + "dms:DescribeReplicationTasks": {}, + "dms:DescribeReplications": {}, + "dms:ImportCertificate": {}, + "dms:ModifyEventSubscription": {}, + "dms:ModifyFleetAdvisorCollector": {}, + "dms:ModifyFleetAdvisorCollectorStatuses": {}, + "dms:ModifyReplicationSubnetGroup": {}, + "dms:RunFleetAdvisorLsaAnalysis": {}, + "dms:StartRecommendations": {}, + "dms:UpdateSubscriptionsToEventBridge": {}, + "dms:UploadFileMetadataList": {}, + "docdb-elastic:CreateCluster": {}, + "docdb-elastic:ListClusterSnapshots": {}, + "docdb-elastic:ListClusters": {}, + "drs:BatchDeleteSnapshotRequestForDrs": {}, + "drs:CreateExtendedSourceServer": {}, + "drs:CreateLaunchConfigurationTemplate": {}, + "drs:CreateReplicationConfigurationTemplate": {}, + "drs:CreateSourceNetwork": {}, + "drs:CreateSourceServerForDrs": {}, + "drs:DescribeJobs": {}, + "drs:DescribeLaunchConfigurationTemplates": {}, + "drs:DescribeRecoveryInstances": {}, + "drs:DescribeReplicationConfigurationTemplates": {}, + "drs:DescribeReplicationServerAssociationsForDrs": {}, + "drs:DescribeSnapshotRequestsForDrs": {}, + "drs:DescribeSourceNetworks": {}, + "drs:DescribeSourceServers": {}, + "drs:GetAgentInstallationAssetsForDrs": {}, + "drs:GetChannelCommandsForDrs": {}, + "drs:InitializeService": {}, + "drs:ListExtensibleSourceServers": {}, + "drs:ListStagingAccounts": {}, + "drs:ListTagsForResource": {}, + "drs:SendChannelCommandResultForDrs": {}, + "drs:SendClientLogsForDrs": {}, + "drs:SendClientMetricsForDrs": {}, + "ds:CheckAlias": {}, + "ds:ConnectDirectory": {}, + "ds:CreateDirectory": {}, + "ds:CreateIdentityPoolDirectory": {}, + "ds:CreateMicrosoftAD": {}, + "ds:DescribeDirectories": {}, + "ds:DescribeSnapshots": {}, + "ds:DescribeTrusts": {}, + "ds:GetDirectoryLimits": {}, + "ds:ListLogSubscriptions": {}, + "dynamodb:DescribeEndpoints": {}, + "dynamodb:DescribeLimits": {}, + "dynamodb:DescribeReservedCapacity": {}, + "dynamodb:DescribeReservedCapacityOfferings": {}, + "dynamodb:ListBackups": {}, + "dynamodb:ListContributorInsights": {}, + "dynamodb:ListExports": {}, + "dynamodb:ListGlobalTables": {}, + "dynamodb:ListImports": {}, + "dynamodb:ListStreams": {}, + "dynamodb:ListTables": {}, + "dynamodb:PurchaseReservedCapacityOfferings": {}, + "ec2:AcceptReservedInstancesExchangeQuote": {}, + "ec2:AdvertiseByoipCidr": {}, + "ec2:AssociateIpamByoasn": {}, + "ec2:AssociateTrunkInterface": {}, + "ec2:BundleInstance": {}, + "ec2:CancelBundleTask": {}, + "ec2:CancelConversionTask": {}, + "ec2:CancelReservedInstancesListing": {}, + "ec2:ConfirmProductInstance": {}, + "ec2:CreateDefaultSubnet": {}, + "ec2:CreateDefaultVpc": {}, + "ec2:CreateReservedInstancesListing": {}, + "ec2:CreateSpotDatafeedSubscription": {}, + "ec2:CreateSubnetCidrReservation": {}, + "ec2:DeleteQueuedReservedInstances": {}, + "ec2:DeleteSpotDatafeedSubscription": {}, + "ec2:DeleteSubnetCidrReservation": {}, + "ec2:DeprovisionByoipCidr": {}, + "ec2:DeregisterInstanceEventNotificationAttributes": {}, + "ec2:DescribeAccountAttributes": {}, + "ec2:DescribeAddressTransfers": {}, + "ec2:DescribeAddresses": {}, + "ec2:DescribeAggregateIdFormat": {}, + "ec2:DescribeAvailabilityZones": {}, + "ec2:DescribeAwsNetworkPerformanceMetricSubscriptions": {}, + "ec2:DescribeBundleTasks": {}, + "ec2:DescribeByoipCidrs": {}, + "ec2:DescribeCapacityBlockOfferings": {}, + "ec2:DescribeCapacityReservationFleets": {}, + "ec2:DescribeCapacityReservations": {}, + "ec2:DescribeCarrierGateways": {}, + "ec2:DescribeClassicLinkInstances": {}, + "ec2:DescribeCoipPools": {}, + "ec2:DescribeConversionTasks": {}, + "ec2:DescribeCustomerGateways": {}, + "ec2:DescribeDhcpOptions": {}, + "ec2:DescribeEgressOnlyInternetGateways": {}, + "ec2:DescribeElasticGpus": {}, + "ec2:DescribeExportImageTasks": {}, + "ec2:DescribeExportTasks": {}, + "ec2:DescribeFastLaunchImages": {}, + "ec2:DescribeFastSnapshotRestores": {}, + "ec2:DescribeFleets": {}, + "ec2:DescribeFlowLogs": {}, + "ec2:DescribeFpgaImages": {}, + "ec2:DescribeHostReservationOfferings": {}, + "ec2:DescribeHostReservations": {}, + "ec2:DescribeHosts": {}, + "ec2:DescribeIamInstanceProfileAssociations": {}, + "ec2:DescribeIdFormat": {}, + "ec2:DescribeIdentityIdFormat": {}, + "ec2:DescribeImages": {}, + "ec2:DescribeImportImageTasks": {}, + "ec2:DescribeImportSnapshotTasks": {}, + "ec2:DescribeInstanceConnectEndpoints": {}, + "ec2:DescribeInstanceCreditSpecifications": {}, + "ec2:DescribeInstanceEventNotificationAttributes": {}, + "ec2:DescribeInstanceEventWindows": {}, + "ec2:DescribeInstanceStatus": {}, + "ec2:DescribeInstanceTopology": {}, + "ec2:DescribeInstanceTypeOfferings": {}, + "ec2:DescribeInstanceTypes": {}, + "ec2:DescribeInstances": {}, + "ec2:DescribeInternetGateways": {}, + "ec2:DescribeIpamByoasn": {}, + "ec2:DescribeIpamPools": {}, + "ec2:DescribeIpamResourceDiscoveries": {}, + "ec2:DescribeIpamResourceDiscoveryAssociations": {}, + "ec2:DescribeIpamScopes": {}, + "ec2:DescribeIpams": {}, + "ec2:DescribeIpv6Pools": {}, + "ec2:DescribeKeyPairs": {}, + "ec2:DescribeLaunchTemplateVersions": {}, + "ec2:DescribeLaunchTemplates": {}, + "ec2:DescribeLocalGatewayRouteTablePermissions": {}, + "ec2:DescribeLocalGatewayRouteTableVirtualInterfaceGroupAssociations": {}, + "ec2:DescribeLocalGatewayRouteTableVpcAssociations": {}, + "ec2:DescribeLocalGatewayRouteTables": {}, + "ec2:DescribeLocalGatewayVirtualInterfaceGroups": {}, + "ec2:DescribeLocalGatewayVirtualInterfaces": {}, + "ec2:DescribeLocalGateways": {}, + "ec2:DescribeLockedSnapshots": {}, + "ec2:DescribeManagedPrefixLists": {}, + "ec2:DescribeMovingAddresses": {}, + "ec2:DescribeNatGateways": {}, + "ec2:DescribeNetworkAcls": {}, + "ec2:DescribeNetworkInsightsAccessScopeAnalyses": {}, + "ec2:DescribeNetworkInsightsAccessScopes": {}, + "ec2:DescribeNetworkInsightsAnalyses": {}, + "ec2:DescribeNetworkInsightsPaths": {}, + "ec2:DescribeNetworkInterfaceAttribute": {}, + "ec2:DescribeNetworkInterfacePermissions": {}, + "ec2:DescribeNetworkInterfaces": {}, + "ec2:DescribePlacementGroups": {}, + "ec2:DescribePrefixLists": {}, + "ec2:DescribePrincipalIdFormat": {}, + "ec2:DescribePublicIpv4Pools": {}, + "ec2:DescribeRegions": {}, + "ec2:DescribeReplaceRootVolumeTasks": {}, + "ec2:DescribeReservedInstances": {}, + "ec2:DescribeReservedInstancesListings": {}, + "ec2:DescribeReservedInstancesModifications": {}, + "ec2:DescribeReservedInstancesOfferings": {}, + "ec2:DescribeRouteTables": {}, + "ec2:DescribeScheduledInstanceAvailability": {}, + "ec2:DescribeScheduledInstances": {}, + "ec2:DescribeSecurityGroupReferences": {}, + "ec2:DescribeSecurityGroupRules": {}, + "ec2:DescribeSecurityGroups": {}, + "ec2:DescribeSnapshotTierStatus": {}, + "ec2:DescribeSnapshots": {}, + "ec2:DescribeSpotDatafeedSubscription": {}, + "ec2:DescribeSpotFleetRequests": {}, + "ec2:DescribeSpotInstanceRequests": {}, + "ec2:DescribeSpotPriceHistory": {}, + "ec2:DescribeStaleSecurityGroups": {}, + "ec2:DescribeStoreImageTasks": {}, + "ec2:DescribeSubnets": {}, + "ec2:DescribeTags": {}, + "ec2:DescribeTrafficMirrorFilters": {}, + "ec2:DescribeTrafficMirrorSessions": {}, + "ec2:DescribeTrafficMirrorTargets": {}, + "ec2:DescribeTransitGatewayAttachments": {}, + "ec2:DescribeTransitGatewayConnectPeers": {}, + "ec2:DescribeTransitGatewayConnects": {}, + "ec2:DescribeTransitGatewayMulticastDomains": {}, + "ec2:DescribeTransitGatewayPeeringAttachments": {}, + "ec2:DescribeTransitGatewayPolicyTables": {}, + "ec2:DescribeTransitGatewayRouteTableAnnouncements": {}, + "ec2:DescribeTransitGatewayRouteTables": {}, + "ec2:DescribeTransitGatewayVpcAttachments": {}, + "ec2:DescribeTransitGateways": {}, + "ec2:DescribeTrunkInterfaceAssociations": {}, + "ec2:DescribeVerifiedAccessEndpoints": {}, + "ec2:DescribeVerifiedAccessGroups": {}, + "ec2:DescribeVerifiedAccessInstanceLoggingConfigurations": {}, + "ec2:DescribeVerifiedAccessInstanceWebAclAssociations": {}, + "ec2:DescribeVerifiedAccessInstances": {}, + "ec2:DescribeVerifiedAccessTrustProviders": {}, + "ec2:DescribeVolumeStatus": {}, + "ec2:DescribeVolumes": {}, + "ec2:DescribeVolumesModifications": {}, + "ec2:DescribeVpcClassicLink": {}, + "ec2:DescribeVpcClassicLinkDnsSupport": {}, + "ec2:DescribeVpcEndpointConnectionNotifications": {}, + "ec2:DescribeVpcEndpointConnections": {}, + "ec2:DescribeVpcEndpointServiceConfigurations": {}, + "ec2:DescribeVpcEndpointServices": {}, + "ec2:DescribeVpcEndpoints": {}, + "ec2:DescribeVpcPeeringConnections": {}, + "ec2:DescribeVpcs": {}, + "ec2:DescribeVpnConnections": {}, + "ec2:DescribeVpnGateways": {}, + "ec2:DisableAwsNetworkPerformanceMetricSubscription": {}, + "ec2:DisableEbsEncryptionByDefault": {}, + "ec2:DisableImageBlockPublicAccess": {}, + "ec2:DisableIpamOrganizationAdminAccount": {}, + "ec2:DisableSerialConsoleAccess": {}, + "ec2:DisableSnapshotBlockPublicAccess": {}, + "ec2:DisassociateIpamByoasn": {}, + "ec2:DisassociateTrunkInterface": {}, + "ec2:EnableAwsNetworkPerformanceMetricSubscription": {}, + "ec2:EnableEbsEncryptionByDefault": {}, + "ec2:EnableImageBlockPublicAccess": {}, + "ec2:EnableIpamOrganizationAdminAccount": {}, + "ec2:EnableReachabilityAnalyzerOrganizationSharing": {}, + "ec2:EnableSerialConsoleAccess": {}, + "ec2:EnableSnapshotBlockPublicAccess": {}, + "ec2:ExportTransitGatewayRoutes": {}, + "ec2:GetAssociatedIpv6PoolCidrs": {}, + "ec2:GetAwsNetworkPerformanceData": {}, + "ec2:GetDefaultCreditSpecification": {}, + "ec2:GetEbsDefaultKmsKeyId": {}, + "ec2:GetEbsEncryptionByDefault": {}, + "ec2:GetHostReservationPurchasePreview": {}, + "ec2:GetImageBlockPublicAccessState": {}, + "ec2:GetInstanceTypesFromInstanceRequirements": {}, + "ec2:GetReservedInstancesExchangeQuote": {}, + "ec2:GetSerialConsoleAccessStatus": {}, + "ec2:GetSnapshotBlockPublicAccessState": {}, + "ec2:GetSpotPlacementScores": {}, + "ec2:GetSubnetCidrReservations": {}, + "ec2:GetTransitGatewayAttachmentPropagations": {}, + "ec2:GetTransitGatewayPrefixListReferences": {}, + "ec2:GetTransitGatewayRouteTableAssociations": {}, + "ec2:GetTransitGatewayRouteTablePropagations": {}, + "ec2:GetVpnConnectionDeviceTypes": {}, + "ec2:InjectApiError": {}, + "ec2:ListImagesInRecycleBin": {}, + "ec2:ListSnapshotsInRecycleBin": {}, + "ec2:ModifyAvailabilityZoneGroup": {}, + "ec2:ModifyDefaultCreditSpecification": {}, + "ec2:ModifyEbsDefaultKmsKeyId": {}, + "ec2:ModifyIdFormat": {}, + "ec2:ModifyIdentityIdFormat": {}, + "ec2:MoveAddressToVpc": {}, + "ec2:ProvisionByoipCidr": {}, + "ec2:PurchaseReservedInstancesOffering": {}, + "ec2:PurchaseScheduledInstances": {}, + "ec2:RegisterInstanceEventNotificationAttributes": {}, + "ec2:ReportInstanceStatus": {}, + "ec2:ResetEbsDefaultKmsKeyId": {}, + "ec2:RestoreAddressToClassic": {}, + "ec2:RunScheduledInstances": {}, + "ec2:WithdrawByoipCidr": {}, + "ec2messages:AcknowledgeMessage": {}, + "ec2messages:DeleteMessage": {}, + "ec2messages:FailMessage": {}, + "ec2messages:GetEndpoint": {}, + "ec2messages:GetMessages": {}, + "ec2messages:SendReply": {}, + "ecr-public:GetAuthorizationToken": {}, + "ecr:BatchImportUpstreamImage": {}, + "ecr:CreatePullThroughCacheRule": {}, + "ecr:CreateRepository": {}, + "ecr:CreateRepositoryCreationTemplate": {}, + "ecr:DeletePullThroughCacheRule": {}, + "ecr:DeleteRegistryPolicy": {}, + "ecr:DeleteRepositoryCreationTemplate": {}, + "ecr:DescribePullThroughCacheRules": {}, + "ecr:DescribeRegistry": {}, + "ecr:DescribeRepositoryCreationTemplate": {}, + "ecr:GetAuthorizationToken": {}, + "ecr:GetRegistryPolicy": {}, + "ecr:GetRegistryScanningConfiguration": {}, + "ecr:PutRegistryPolicy": {}, + "ecr:PutRegistryScanningConfiguration": {}, + "ecr:PutReplicationConfiguration": {}, + "ecr:UpdatePullThroughCacheRule": {}, + "ecr:ValidatePullThroughCacheRule": {}, + "ecs:CreateCapacityProvider": {}, + "ecs:CreateCluster": {}, + "ecs:CreateTaskSet": {}, + "ecs:DeleteAccountSetting": {}, + "ecs:DeregisterTaskDefinition": {}, + "ecs:DescribeTaskDefinition": {}, + "ecs:DiscoverPollEndpoint": {}, + "ecs:ListAccountSettings": {}, + "ecs:ListClusters": {}, + "ecs:ListServices": {}, + "ecs:ListServicesByNamespace": {}, + "ecs:ListTaskDefinitionFamilies": {}, + "ecs:ListTaskDefinitions": {}, + "ecs:PutAccountSetting": {}, + "ecs:PutAccountSettingDefault": {}, + "ecs:RegisterTaskDefinition": {}, + "eks:CreateCluster": {}, + "eks:CreateEksAnywhereSubscription": {}, + "eks:DescribeAddonConfiguration": {}, + "eks:DescribeAddonVersions": {}, + "eks:ListAccessPolicies": {}, + "eks:ListClusters": {}, + "eks:ListEksAnywhereSubscriptions": {}, + "eks:RegisterCluster": {}, + "elasticache:DescribeCacheEngineVersions": {}, + "elasticache:DescribeEngineDefaultParameters": {}, + "elasticache:DescribeEvents": {}, + "elasticache:DescribeReservedCacheNodesOfferings": {}, + "elasticache:DescribeServiceUpdates": {}, + "elasticbeanstalk:CheckDNSAvailability": {}, + "elasticbeanstalk:CreateStorageLocation": {}, + "elasticbeanstalk:DescribeAccountAttributes": {}, + "elasticbeanstalk:ListPlatformBranches": {}, + "elasticfilesystem:CreateFileSystem": {}, + "elasticfilesystem:DescribeAccountPreferences": {}, + "elasticfilesystem:PutAccountPreferences": {}, + "elasticloadbalancing:DescribeAccountLimits": {}, + "elasticloadbalancing:DescribeInstanceHealth": {}, + "elasticloadbalancing:DescribeListenerCertificates": {}, + "elasticloadbalancing:DescribeListeners": {}, + "elasticloadbalancing:DescribeLoadBalancerAttributes": {}, + "elasticloadbalancing:DescribeLoadBalancerPolicies": {}, + "elasticloadbalancing:DescribeLoadBalancerPolicyTypes": {}, + "elasticloadbalancing:DescribeLoadBalancers": {}, + "elasticloadbalancing:DescribeRules": {}, + "elasticloadbalancing:DescribeSSLPolicies": {}, + "elasticloadbalancing:DescribeTags": {}, + "elasticloadbalancing:DescribeTargetGroupAttributes": {}, + "elasticloadbalancing:DescribeTargetGroups": {}, + "elasticloadbalancing:DescribeTargetHealth": {}, + "elasticloadbalancing:DescribeTrustStoreAssociations": {}, + "elasticloadbalancing:DescribeTrustStoreRevocations": {}, + "elasticloadbalancing:DescribeTrustStores": {}, + "elasticloadbalancing:SetWebAcl": {}, + "elasticmapreduce:CreateRepository": {}, + "elasticmapreduce:CreateSecurityConfiguration": {}, + "elasticmapreduce:CreateStudio": {}, + "elasticmapreduce:DeleteRepository": {}, + "elasticmapreduce:DeleteSecurityConfiguration": {}, + "elasticmapreduce:DescribeReleaseLabel": {}, + "elasticmapreduce:DescribeRepository": {}, + "elasticmapreduce:DescribeSecurityConfiguration": {}, + "elasticmapreduce:GetBlockPublicAccessConfiguration": {}, + "elasticmapreduce:LinkRepository": {}, + "elasticmapreduce:ListClusters": {}, + "elasticmapreduce:ListEditors": {}, + "elasticmapreduce:ListNotebookExecutions": {}, + "elasticmapreduce:ListReleaseLabels": {}, + "elasticmapreduce:ListRepositories": {}, + "elasticmapreduce:ListSecurityConfigurations": {}, + "elasticmapreduce:ListStudioSessionMappings": {}, + "elasticmapreduce:ListStudios": {}, + "elasticmapreduce:ListSupportedInstanceTypes": {}, + "elasticmapreduce:PutBlockPublicAccessConfiguration": {}, + "elasticmapreduce:RunJobFlow": {}, + "elasticmapreduce:UnlinkRepository": {}, + "elasticmapreduce:UpdateRepository": {}, + "elasticmapreduce:ViewEventsFromAllClustersInConsole": {}, + "elastictranscoder:CreatePipeline": {}, + "elastictranscoder:CreatePreset": {}, + "elastictranscoder:ListJobsByStatus": {}, + "elastictranscoder:ListPipelines": {}, + "elastictranscoder:ListPresets": {}, + "elastictranscoder:TestRole": {}, + "elemental-activations:CompleteAccountRegistration": {}, + "elemental-activations:CompleteFileUpload": {}, + "elemental-activations:DownloadSoftware": {}, + "elemental-activations:GenerateLicenses": {}, + "elemental-activations:StartAccountRegistration": {}, + "elemental-activations:StartFileUpload": {}, + "elemental-appliances-software:CompleteUpload": {}, + "elemental-appliances-software:CreateOrderV1": {}, + "elemental-appliances-software:GetAvsCorrectAddress": {}, + "elemental-appliances-software:GetBillingAddresses": {}, + "elemental-appliances-software:GetDeliveryAddressesV2": {}, + "elemental-appliances-software:GetOrder": {}, + "elemental-appliances-software:GetOrdersV2": {}, + "elemental-appliances-software:GetTaxes": {}, + "elemental-appliances-software:ListQuotes": {}, + "elemental-appliances-software:StartUpload": {}, + "elemental-appliances-software:SubmitOrderV1": {}, + "elemental-support-cases:CheckCasePermission": {}, + "elemental-support-cases:CreateCase": {}, + "elemental-support-cases:GetCase": {}, + "elemental-support-cases:GetCases": {}, + "elemental-support-cases:UpdateCase": {}, + "elemental-support-content:Query": {}, + "emr-containers:CreateJobTemplate": {}, + "emr-containers:CreateVirtualCluster": {}, + "emr-containers:ListJobTemplates": {}, + "emr-containers:ListVirtualClusters": {}, + "emr-serverless:CreateApplication": {}, + "emr-serverless:ListApplications": {}, + "entityresolution:CreateIdMappingWorkflow": {}, + "entityresolution:CreateMatchingWorkflow": {}, + "entityresolution:CreateSchemaMapping": {}, + "entityresolution:ListIdMappingWorkflows": {}, + "entityresolution:ListMatchingWorkflows": {}, + "entityresolution:ListSchemaMappings": {}, + "entityresolution:ListTagsForResource": {}, + "entityresolution:TagResource": {}, + "entityresolution:UntagResource": {}, + "es:AcceptInboundConnection": {}, + "es:AcceptInboundCrossClusterSearchConnection": {}, + "es:AuthorizeVpcEndpointAccess": {}, + "es:CreateElasticsearchServiceRole": {}, + "es:CreatePackage": {}, + "es:CreateServiceRole": {}, + "es:CreateVpcEndpoint": {}, + "es:DeleteElasticsearchServiceRole": {}, + "es:DeleteInboundConnection": {}, + "es:DeleteInboundCrossClusterSearchConnection": {}, + "es:DeleteOutboundConnection": {}, + "es:DeleteOutboundCrossClusterSearchConnection": {}, + "es:DeletePackage": {}, + "es:DeleteVpcEndpoint": {}, + "es:DescribeElasticsearchInstanceTypeLimits": {}, + "es:DescribeInboundConnections": {}, + "es:DescribeInboundCrossClusterSearchConnections": {}, + "es:DescribeInstanceTypeLimits": {}, + "es:DescribeOutboundConnections": {}, + "es:DescribeOutboundCrossClusterSearchConnections": {}, + "es:DescribePackages": {}, + "es:DescribeReservedElasticsearchInstanceOfferings": {}, + "es:DescribeReservedElasticsearchInstances": {}, + "es:DescribeReservedInstanceOfferings": {}, + "es:DescribeReservedInstances": {}, + "es:DescribeVpcEndpoints": {}, + "es:GetPackageVersionHistory": {}, + "es:ListDomainNames": {}, + "es:ListDomainsForPackage": {}, + "es:ListElasticsearchInstanceTypeDetails": {}, + "es:ListElasticsearchInstanceTypes": {}, + "es:ListElasticsearchVersions": {}, + "es:ListInstanceTypeDetails": {}, + "es:ListVersions": {}, + "es:ListVpcEndpointAccess": {}, + "es:ListVpcEndpoints": {}, + "es:ListVpcEndpointsForDomain": {}, + "es:PurchaseReservedElasticsearchInstanceOffering": {}, + "es:PurchaseReservedInstanceOffering": {}, + "es:RejectInboundConnection": {}, + "es:RejectInboundCrossClusterSearchConnection": {}, + "es:RevokeVpcEndpointAccess": {}, + "es:UpdatePackage": {}, + "es:UpdateVpcEndpoint": {}, + "events:ListApiDestinations": {}, + "events:ListArchives": {}, + "events:ListConnections": {}, + "events:ListEndpoints": {}, + "events:ListEventBuses": {}, + "events:ListEventSources": {}, + "events:ListPartnerEventSources": {}, + "events:ListReplays": {}, + "events:ListRuleNamesByTarget": {}, + "events:ListRules": {}, + "events:PutPartnerEvents": {}, + "events:PutPermission": {}, + "events:RemovePermission": {}, + "events:TestEventPattern": {}, + "evidently:CreateExperiment": {}, + "evidently:CreateFeature": {}, + "evidently:CreateLaunch": {}, + "evidently:CreateProject": {}, + "evidently:CreateSegment": {}, + "evidently:ListExperiments": {}, + "evidently:ListFeatures": {}, + "evidently:ListLaunches": {}, + "evidently:ListProjects": {}, + "evidently:ListSegmentReferences": {}, + "evidently:ListSegments": {}, + "evidently:ListTagsForResource": {}, + "evidently:TestSegmentPattern": {}, + "finspace:CreateKxEnvironment": {}, + "finspace:ListKxEnvironments": {}, + "firehose:ListDeliveryStreams": {}, + "fis:GetTargetResourceType": {}, + "fis:ListActions": {}, + "fis:ListExperimentTemplates": {}, + "fis:ListExperiments": {}, + "fis:ListTargetResourceTypes": {}, + "fms:AssociateAdminAccount": {}, + "fms:AssociateThirdPartyFirewall": {}, + "fms:DeleteNotificationChannel": {}, + "fms:DisassociateAdminAccount": {}, + "fms:DisassociateThirdPartyFirewall": {}, + "fms:GetAdminAccount": {}, + "fms:GetAdminScope": {}, + "fms:GetNotificationChannel": {}, + "fms:GetThirdPartyFirewallAssociationStatus": {}, + "fms:ListAdminAccountsForOrganization": {}, + "fms:ListAdminsManagingAccount": {}, + "fms:ListAppsLists": {}, + "fms:ListDiscoveredResources": {}, + "fms:ListMemberAccounts": {}, + "fms:ListPolicies": {}, + "fms:ListProtocolsLists": {}, + "fms:ListResourceSets": {}, + "fms:ListThirdPartyFirewallFirewallPolicies": {}, + "fms:PutAdminAccount": {}, + "fms:PutNotificationChannel": {}, + "forecast:CreateAutoPredictor": {}, + "forecast:ListDatasetGroups": {}, + "forecast:ListDatasetImportJobs": {}, + "forecast:ListDatasets": {}, + "forecast:ListExplainabilities": {}, + "forecast:ListExplainabilityExports": {}, + "forecast:ListForecastExportJobs": {}, + "forecast:ListForecasts": {}, + "forecast:ListMonitors": {}, + "forecast:ListPredictorBacktestExportJobs": {}, + "forecast:ListPredictors": {}, + "forecast:ListWhatIfAnalyses": {}, + "forecast:ListWhatIfForecastExports": {}, + "forecast:ListWhatIfForecasts": {}, + "frauddetector:BatchCreateVariable": {}, + "frauddetector:CreateList": {}, + "frauddetector:CreateVariable": {}, + "frauddetector:GetKMSEncryptionKey": {}, + "frauddetector:PutKMSEncryptionKey": {}, + "freertos:CreateSubscription": {}, + "freertos:DescribeHardwarePlatform": {}, + "freertos:GetEmpPatchUrl": {}, + "freertos:GetSoftwareURL": {}, + "freertos:GetSoftwareURLForConfiguration": {}, + "freertos:GetSubscriptionBillingAmount": {}, + "freertos:ListFreeRTOSVersions": {}, + "freertos:ListHardwarePlatforms": {}, + "freertos:ListHardwareVendors": {}, + "freertos:ListSoftwareConfigurations": {}, + "freertos:ListSoftwarePatches": {}, + "freertos:ListSubscriptionEmails": {}, + "freertos:ListSubscriptions": {}, + "freertos:UpdateEmailRecipients": {}, + "freertos:VerifyEmail": {}, + "freetier:GetFreeTierAlertPreference": {}, + "freetier:GetFreeTierUsage": {}, + "freetier:PutFreeTierAlertPreference": {}, + "fsx:DescribeBackups": {}, + "fsx:DescribeDataRepositoryAssociations": {}, + "fsx:DescribeDataRepositoryTasks": {}, + "fsx:DescribeFileCaches": {}, + "fsx:DescribeFileSystems": {}, + "fsx:DescribeSharedVpcConfiguration": {}, + "fsx:DescribeSnapshots": {}, + "fsx:DescribeStorageVirtualMachines": {}, + "fsx:DescribeVolumes": {}, + "fsx:UpdateSharedVpcConfiguration": {}, + "gamelift:AcceptMatch": {}, + "gamelift:CreateAlias": {}, + "gamelift:CreateBuild": {}, + "gamelift:CreateFleet": {}, + "gamelift:CreateGameServerGroup": {}, + "gamelift:CreateGameSession": {}, + "gamelift:CreateGameSessionQueue": {}, + "gamelift:CreateLocation": {}, + "gamelift:CreateMatchmakingConfiguration": {}, + "gamelift:CreateMatchmakingRuleSet": {}, + "gamelift:CreatePlayerSession": {}, + "gamelift:CreatePlayerSessions": {}, + "gamelift:CreateScript": {}, + "gamelift:CreateVpcPeeringAuthorization": {}, + "gamelift:CreateVpcPeeringConnection": {}, + "gamelift:DeleteVpcPeeringAuthorization": {}, + "gamelift:DeleteVpcPeeringConnection": {}, + "gamelift:DescribeEC2InstanceLimits": {}, + "gamelift:DescribeFleetAttributes": {}, + "gamelift:DescribeFleetCapacity": {}, + "gamelift:DescribeFleetUtilization": {}, + "gamelift:DescribeGameSessionDetails": {}, + "gamelift:DescribeGameSessionPlacement": {}, + "gamelift:DescribeGameSessionQueues": {}, + "gamelift:DescribeGameSessions": {}, + "gamelift:DescribeMatchmaking": {}, + "gamelift:DescribeMatchmakingConfigurations": {}, + "gamelift:DescribeMatchmakingRuleSets": {}, + "gamelift:DescribePlayerSessions": {}, + "gamelift:DescribeVpcPeeringAuthorizations": {}, + "gamelift:DescribeVpcPeeringConnections": {}, + "gamelift:GetGameSessionLogUrl": {}, + "gamelift:ListAliases": {}, + "gamelift:ListBuilds": {}, + "gamelift:ListFleets": {}, + "gamelift:ListGameServerGroups": {}, + "gamelift:ListLocations": {}, + "gamelift:ListScripts": {}, + "gamelift:SearchGameSessions": {}, + "gamelift:StartMatchBackfill": {}, + "gamelift:StartMatchmaking": {}, + "gamelift:StopGameSessionPlacement": {}, + "gamelift:StopMatchmaking": {}, + "gamelift:UpdateGameSession": {}, + "gamelift:ValidateMatchmakingRuleSet": {}, + "glacier:GetDataRetrievalPolicy": {}, + "glacier:ListProvisionedCapacity": {}, + "glacier:ListVaults": {}, + "glacier:PurchaseProvisionedCapacity": {}, + "glacier:SetDataRetrievalPolicy": {}, + "globalaccelerator:AdvertiseByoipCidr": {}, + "globalaccelerator:CreateAccelerator": {}, + "globalaccelerator:CreateCrossAccountAttachment": {}, + "globalaccelerator:CreateCustomRoutingAccelerator": {}, + "globalaccelerator:DeprovisionByoipCidr": {}, + "globalaccelerator:ListAccelerators": {}, + "globalaccelerator:ListByoipCidrs": {}, + "globalaccelerator:ListCrossAccountAttachments": {}, + "globalaccelerator:ListCrossAccountResourceAccounts": {}, + "globalaccelerator:ListCrossAccountResources": {}, + "globalaccelerator:ListCustomRoutingAccelerators": {}, + "globalaccelerator:ListCustomRoutingPortMappingsByDestination": {}, + "globalaccelerator:ProvisionByoipCidr": {}, + "globalaccelerator:WithdrawByoipCidr": {}, + "glue:CheckSchemaVersionValidity": {}, + "glue:CreateClassifier": {}, + "glue:CreateCrawler": {}, + "glue:CreateCustomEntityType": {}, + "glue:CreateDataQualityRuleset": {}, + "glue:CreateDevEndpoint": {}, + "glue:CreateMLTransform": {}, + "glue:CreateScript": {}, + "glue:CreateSecurityConfiguration": {}, + "glue:CreateSession": {}, + "glue:DeleteClassifier": {}, + "glue:DeleteSecurityConfiguration": {}, + "glue:DeregisterDataPreview": {}, + "glue:GetClassifier": {}, + "glue:GetClassifiers": {}, + "glue:GetColumnStatisticsTaskRun": {}, + "glue:GetColumnStatisticsTaskRuns": {}, + "glue:GetCrawlerMetrics": {}, + "glue:GetCrawlers": {}, + "glue:GetDataPreviewStatement": {}, + "glue:GetDataflowGraph": {}, + "glue:GetDevEndpoints": {}, + "glue:GetJobBookmark": {}, + "glue:GetJobs": {}, + "glue:GetMapping": {}, + "glue:GetNotebookInstanceStatus": {}, + "glue:GetPlan": {}, + "glue:GetSecurityConfiguration": {}, + "glue:GetSecurityConfigurations": {}, + "glue:GetTriggers": {}, + "glue:GlueNotebookAuthorize": {}, + "glue:GlueNotebookRefreshCredentials": {}, + "glue:ListBlueprints": {}, + "glue:ListColumnStatisticsTaskRuns": {}, + "glue:ListCrawlers": {}, + "glue:ListCrawls": {}, + "glue:ListCustomEntityTypes": {}, + "glue:ListDevEndpoints": {}, + "glue:ListJobs": {}, + "glue:ListRegistries": {}, + "glue:ListSessions": {}, + "glue:ListTriggers": {}, + "glue:ListWorkflows": {}, + "glue:ResetJobBookmark": {}, + "glue:RunDataPreviewStatement": {}, + "glue:SendFeedback": {}, + "glue:StartCompletion": {}, + "glue:StartCrawlerSchedule": {}, + "glue:StartNotebook": {}, + "glue:StopCrawlerSchedule": {}, + "glue:TerminateNotebook": {}, + "glue:TestConnection": {}, + "glue:UpdateClassifier": {}, + "glue:UpdateCrawlerSchedule": {}, + "glue:UseGlueStudio": {}, + "grafana:CreateWorkspace": {}, + "grafana:ListWorkspaces": {}, + "greengrass:AssociateServiceRoleToAccount": {}, + "greengrass:CreateConnectorDefinition": {}, + "greengrass:CreateCoreDefinition": {}, + "greengrass:CreateDeployment": {}, + "greengrass:CreateDeviceDefinition": {}, + "greengrass:CreateFunctionDefinition": {}, + "greengrass:CreateGroup": {}, + "greengrass:CreateLoggerDefinition": {}, + "greengrass:CreateResourceDefinition": {}, + "greengrass:CreateSoftwareUpdateJob": {}, + "greengrass:CreateSubscriptionDefinition": {}, + "greengrass:DisassociateServiceRoleFromAccount": {}, + "greengrass:GetServiceRoleForAccount": {}, + "greengrass:ListBulkDeployments": {}, + "greengrass:ListComponents": {}, + "greengrass:ListConnectorDefinitions": {}, + "greengrass:ListCoreDefinitions": {}, + "greengrass:ListCoreDevices": {}, + "greengrass:ListDeployments": {}, + "greengrass:ListDeviceDefinitions": {}, + "greengrass:ListFunctionDefinitions": {}, + "greengrass:ListGroups": {}, + "greengrass:ListLoggerDefinitions": {}, + "greengrass:ListResourceDefinitions": {}, + "greengrass:ListSubscriptionDefinitions": {}, + "greengrass:StartBulkDeployment": {}, + "groundstation:CreateConfig": {}, + "groundstation:CreateDataflowEndpointGroup": {}, + "groundstation:CreateEphemeris": {}, + "groundstation:CreateMissionProfile": {}, + "groundstation:GetMinuteUsage": {}, + "groundstation:ListConfigs": {}, + "groundstation:ListContacts": {}, + "groundstation:ListDataflowEndpointGroups": {}, + "groundstation:ListEphemerides": {}, + "groundstation:ListGroundStations": {}, + "groundstation:ListMissionProfiles": {}, + "groundstation:ListSatellites": {}, + "groundstation:RegisterAgent": {}, + "groundstation:ReserveContact": {}, + "groundtruthlabeling:AssociatePatchToManifestJob": {}, + "groundtruthlabeling:DescribeConsoleJob": {}, + "groundtruthlabeling:ListDatasetObjects": {}, + "groundtruthlabeling:RunFilterOrSampleDatasetJob": {}, + "groundtruthlabeling:RunGenerateManifestByCrawlingJob": {}, + "guardduty:AcceptAdministratorInvitation": {}, + "guardduty:AcceptInvitation": {}, + "guardduty:ArchiveFindings": {}, + "guardduty:CreateDetector": {}, + "guardduty:CreateIPSet": {}, + "guardduty:CreateMembers": {}, + "guardduty:CreatePublishingDestination": {}, + "guardduty:CreateSampleFindings": {}, + "guardduty:CreateThreatIntelSet": {}, + "guardduty:DeclineInvitations": {}, + "guardduty:DeleteInvitations": {}, + "guardduty:DeleteMembers": {}, + "guardduty:DescribeMalwareScans": {}, + "guardduty:DescribeOrganizationConfiguration": {}, + "guardduty:DisableOrganizationAdminAccount": {}, + "guardduty:DisassociateFromAdministratorAccount": {}, + "guardduty:DisassociateFromMasterAccount": {}, + "guardduty:DisassociateMembers": {}, + "guardduty:EnableOrganizationAdminAccount": {}, + "guardduty:GetAdministratorAccount": {}, + "guardduty:GetFindings": {}, + "guardduty:GetFindingsStatistics": {}, + "guardduty:GetInvitationsCount": {}, + "guardduty:GetMalwareScanSettings": {}, + "guardduty:GetMasterAccount": {}, + "guardduty:GetMemberDetectors": {}, + "guardduty:GetMembers": {}, + "guardduty:GetOrganizationStatistics": {}, + "guardduty:GetRemainingFreeTrialDays": {}, + "guardduty:GetUsageStatistics": {}, + "guardduty:InviteMembers": {}, + "guardduty:ListDetectors": {}, + "guardduty:ListFilters": {}, + "guardduty:ListFindings": {}, + "guardduty:ListIPSets": {}, + "guardduty:ListInvitations": {}, + "guardduty:ListMembers": {}, + "guardduty:ListOrganizationAdminAccounts": {}, + "guardduty:ListPublishingDestinations": {}, + "guardduty:ListThreatIntelSets": {}, + "guardduty:SendSecurityTelemetry": {}, + "guardduty:StartMalwareScan": {}, + "guardduty:StartMonitoringMembers": {}, + "guardduty:StopMonitoringMembers": {}, + "guardduty:UnarchiveFindings": {}, + "guardduty:UpdateFindingsFeedback": {}, + "guardduty:UpdateMalwareScanSettings": {}, + "guardduty:UpdateMemberDetectors": {}, + "guardduty:UpdateOrganizationConfiguration": {}, + "health:DescribeAffectedAccountsForOrganization": {}, + "health:DescribeAffectedEntitiesForOrganization": {}, + "health:DescribeEntityAggregates": {}, + "health:DescribeEntityAggregatesForOrganization": {}, + "health:DescribeEventAggregates": {}, + "health:DescribeEventDetailsForOrganization": {}, + "health:DescribeEventTypes": {}, + "health:DescribeEvents": {}, + "health:DescribeEventsForOrganization": {}, + "health:DescribeHealthServiceStatusForOrganization": {}, + "health:DisableHealthServiceAccessForOrganization": {}, + "health:EnableHealthServiceAccessForOrganization": {}, + "healthlake:CreateFHIRDatastore": {}, + "healthlake:ListFHIRDatastores": {}, + "honeycode:ApproveTeamAssociation": {}, + "honeycode:CreateTeam": {}, + "honeycode:CreateTenant": {}, + "honeycode:DeleteDomains": {}, + "honeycode:DeregisterGroups": {}, + "honeycode:DescribeTeam": {}, + "honeycode:ListDomains": {}, + "honeycode:ListGroups": {}, + "honeycode:ListTagsForResource": {}, + "honeycode:ListTeamAssociations": {}, + "honeycode:ListTenants": {}, + "honeycode:RegisterDomainForVerification": {}, + "honeycode:RegisterGroups": {}, + "honeycode:RejectTeamAssociation": {}, + "honeycode:RestartDomainVerification": {}, + "honeycode:TagResource": {}, + "honeycode:UntagResource": {}, + "honeycode:UpdateTeam": {}, + "iam:CreateAccountAlias": {}, + "iam:DeleteAccountAlias": {}, + "iam:DeleteAccountPasswordPolicy": {}, + "iam:DeleteCloudFrontPublicKey": {}, + "iam:GenerateCredentialReport": {}, + "iam:GetAccountAuthorizationDetails": {}, + "iam:GetAccountEmailAddress": {}, + "iam:GetAccountName": {}, + "iam:GetAccountPasswordPolicy": {}, + "iam:GetAccountSummary": {}, + "iam:GetCloudFrontPublicKey": {}, + "iam:GetContextKeysForCustomPolicy": {}, + "iam:GetCredentialReport": {}, + "iam:GetOrganizationsAccessReport": {}, + "iam:GetServiceLastAccessedDetails": {}, + "iam:GetServiceLastAccessedDetailsWithEntities": {}, + "iam:ListAccountAliases": {}, + "iam:ListCloudFrontPublicKeys": {}, + "iam:ListGroups": {}, + "iam:ListInstanceProfiles": {}, + "iam:ListOpenIDConnectProviders": {}, + "iam:ListPolicies": {}, + "iam:ListRoles": {}, + "iam:ListSAMLProviders": {}, + "iam:ListSTSRegionalEndpointsStatus": {}, + "iam:ListServerCertificates": {}, + "iam:ListUsers": {}, + "iam:ListVirtualMFADevices": {}, + "iam:SetSTSRegionalEndpointStatus": {}, + "iam:SetSecurityTokenServicePreferences": {}, + "iam:SimulateCustomPolicy": {}, + "iam:UpdateAccountEmailAddress": {}, + "iam:UpdateAccountName": {}, + "iam:UpdateAccountPasswordPolicy": {}, + "iam:UpdateCloudFrontPublicKey": {}, + "iam:UploadCloudFrontPublicKey": {}, + "identity-sync:CreateSyncProfile": {}, + "identitystore-auth:BatchDeleteSession": {}, + "identitystore-auth:BatchGetSession": {}, + "identitystore-auth:ListSessions": {}, + "imagebuilder:ListComponents": {}, + "imagebuilder:ListContainerRecipes": {}, + "imagebuilder:ListDistributionConfigurations": {}, + "imagebuilder:ListImagePipelines": {}, + "imagebuilder:ListImageRecipes": {}, + "imagebuilder:ListImages": {}, + "imagebuilder:ListInfrastructureConfigurations": {}, + "imagebuilder:ListLifecyclePolicies": {}, + "imagebuilder:ListWaitingWorkflowSteps": {}, + "imagebuilder:ListWorkflows": {}, + "importexport:CancelJob": {}, + "importexport:CreateJob": {}, + "importexport:GetShippingLabel": {}, + "importexport:GetStatus": {}, + "importexport:ListJobs": {}, + "importexport:UpdateJob": {}, + "inspector-scan:ScanSbom": {}, + "inspector2:AssociateMember": {}, + "inspector2:BatchGetAccountStatus": {}, + "inspector2:BatchGetCodeSnippet": {}, + "inspector2:BatchGetFindingDetails": {}, + "inspector2:BatchGetFreeTrialInfo": {}, + "inspector2:BatchGetMemberEc2DeepInspectionStatus": {}, + "inspector2:BatchUpdateMemberEc2DeepInspectionStatus": {}, + "inspector2:CancelFindingsReport": {}, + "inspector2:CancelSbomExport": {}, + "inspector2:CreateFindingsReport": {}, + "inspector2:CreateSbomExport": {}, + "inspector2:DescribeOrganizationConfiguration": {}, + "inspector2:Disable": {}, + "inspector2:DisableDelegatedAdminAccount": {}, + "inspector2:DisassociateMember": {}, + "inspector2:Enable": {}, + "inspector2:EnableDelegatedAdminAccount": {}, + "inspector2:GetCisScanReport": {}, + "inspector2:GetCisScanResultDetails": {}, + "inspector2:GetConfiguration": {}, + "inspector2:GetDelegatedAdminAccount": {}, + "inspector2:GetEc2DeepInspectionConfiguration": {}, + "inspector2:GetEncryptionKey": {}, + "inspector2:GetFindingsReportStatus": {}, + "inspector2:GetMember": {}, + "inspector2:GetSbomExport": {}, + "inspector2:ListAccountPermissions": {}, + "inspector2:ListCisScanConfigurations": {}, + "inspector2:ListCisScanResultsAggregatedByChecks": {}, + "inspector2:ListCisScanResultsAggregatedByTargetResource": {}, + "inspector2:ListCisScans": {}, + "inspector2:ListCoverage": {}, + "inspector2:ListCoverageStatistics": {}, + "inspector2:ListDelegatedAdminAccounts": {}, + "inspector2:ListFilters": {}, + "inspector2:ListFindingAggregations": {}, + "inspector2:ListFindings": {}, + "inspector2:ListMembers": {}, + "inspector2:ListTagsForResource": {}, + "inspector2:ListUsageTotals": {}, + "inspector2:ResetEncryptionKey": {}, + "inspector2:SearchVulnerabilities": {}, + "inspector2:SendCisSessionHealth": {}, + "inspector2:SendCisSessionTelemetry": {}, + "inspector2:StartCisSession": {}, + "inspector2:StopCisSession": {}, + "inspector2:UpdateConfiguration": {}, + "inspector2:UpdateEc2DeepInspectionConfiguration": {}, + "inspector2:UpdateEncryptionKey": {}, + "inspector2:UpdateOrgEc2DeepInspectionConfiguration": {}, + "inspector2:UpdateOrganizationConfiguration": {}, + "inspector:AddAttributesToFindings": {}, + "inspector:CreateAssessmentTarget": {}, + "inspector:CreateAssessmentTemplate": {}, + "inspector:CreateExclusionsPreview": {}, + "inspector:CreateResourceGroup": {}, + "inspector:DeleteAssessmentRun": {}, + "inspector:DeleteAssessmentTarget": {}, + "inspector:DeleteAssessmentTemplate": {}, + "inspector:DescribeAssessmentRuns": {}, + "inspector:DescribeAssessmentTargets": {}, + "inspector:DescribeAssessmentTemplates": {}, + "inspector:DescribeCrossAccountAccessRole": {}, + "inspector:DescribeExclusions": {}, + "inspector:DescribeFindings": {}, + "inspector:DescribeResourceGroups": {}, + "inspector:DescribeRulesPackages": {}, + "inspector:GetAssessmentReport": {}, + "inspector:GetExclusionsPreview": {}, + "inspector:GetTelemetryMetadata": {}, + "inspector:ListAssessmentRunAgents": {}, + "inspector:ListAssessmentRuns": {}, + "inspector:ListAssessmentTargets": {}, + "inspector:ListAssessmentTemplates": {}, + "inspector:ListEventSubscriptions": {}, + "inspector:ListExclusions": {}, + "inspector:ListFindings": {}, + "inspector:ListRulesPackages": {}, + "inspector:ListTagsForResource": {}, + "inspector:PreviewAgents": {}, + "inspector:RegisterCrossAccountAccessRole": {}, + "inspector:RemoveAttributesFromFindings": {}, + "inspector:SetTagsForResource": {}, + "inspector:StartAssessmentRun": {}, + "inspector:StopAssessmentRun": {}, + "inspector:SubscribeToEvent": {}, + "inspector:UnsubscribeFromEvent": {}, + "inspector:UpdateAssessmentTarget": {}, + "internetmonitor:ListMonitors": {}, + "invoicing:GetInvoiceEmailDeliveryPreferences": {}, + "invoicing:GetInvoicePDF": {}, + "invoicing:ListInvoiceSummaries": {}, + "invoicing:PutInvoiceEmailDeliveryPreferences": {}, + "iot-device-tester:CheckVersion": {}, + "iot-device-tester:DownloadTestSuite": {}, + "iot-device-tester:LatestIdt": {}, + "iot-device-tester:SendMetrics": {}, + "iot-device-tester:SupportedVersion": {}, + "iot1click:ClaimDevicesByClaimCode": {}, + "iot1click:ListDevices": {}, + "iot1click:ListProjects": {}, + "iot:AttachThingPrincipal": {}, + "iot:CancelAuditMitigationActionsTask": {}, + "iot:CancelAuditTask": {}, + "iot:CancelDetectMitigationActionsTask": {}, + "iot:ClearDefaultAuthorizer": {}, + "iot:CreateAuditSuppression": {}, + "iot:CreateCertificateFromCsr": {}, + "iot:CreateKeysAndCertificate": {}, + "iot:DeleteAccountAuditConfiguration": {}, + "iot:DeleteAuditSuppression": {}, + "iot:DeleteRegistrationCode": {}, + "iot:DeleteV2LoggingLevel": {}, + "iot:DescribeAccountAuditConfiguration": {}, + "iot:DescribeAuditFinding": {}, + "iot:DescribeAuditMitigationActionsTask": {}, + "iot:DescribeAuditSuppression": {}, + "iot:DescribeAuditTask": {}, + "iot:DescribeDefaultAuthorizer": {}, + "iot:DescribeDetectMitigationActionsTask": {}, + "iot:DescribeEndpoint": {}, + "iot:DescribeEventConfigurations": {}, + "iot:DescribeThingRegistrationTask": {}, + "iot:DetachThingPrincipal": {}, + "iot:GetIndexingConfiguration": {}, + "iot:GetLoggingOptions": {}, + "iot:GetPackageConfiguration": {}, + "iot:GetRegistrationCode": {}, + "iot:GetV2LoggingOptions": {}, + "iot:ListAttachedPolicies": {}, + "iot:ListAuditFindings": {}, + "iot:ListAuditMitigationActionsExecutions": {}, + "iot:ListAuditMitigationActionsTasks": {}, + "iot:ListAuditSuppressions": {}, + "iot:ListAuditTasks": {}, + "iot:ListAuthorizers": {}, + "iot:ListBillingGroups": {}, + "iot:ListCACertificates": {}, + "iot:ListCertificateProviders": {}, + "iot:ListCertificates": {}, + "iot:ListCertificatesByCA": {}, + "iot:ListCustomMetrics": {}, + "iot:ListDetectMitigationActionsTasks": {}, + "iot:ListDimensions": {}, + "iot:ListDomainConfigurations": {}, + "iot:ListFleetMetrics": {}, + "iot:ListIndices": {}, + "iot:ListJobTemplates": {}, + "iot:ListJobs": {}, + "iot:ListManagedJobTemplates": {}, + "iot:ListMitigationActions": {}, + "iot:ListOTAUpdates": {}, + "iot:ListOutgoingCertificates": {}, + "iot:ListPackageVersions": {}, + "iot:ListPackages": {}, + "iot:ListPolicies": {}, + "iot:ListPolicyPrincipals": {}, + "iot:ListPrincipalPolicies": {}, + "iot:ListPrincipalThings": {}, + "iot:ListProvisioningTemplates": {}, + "iot:ListRelatedResourcesForAuditFinding": {}, + "iot:ListRetainedMessages": {}, + "iot:ListRoleAliases": {}, + "iot:ListScheduledAudits": {}, + "iot:ListStreams": {}, + "iot:ListThingGroups": {}, + "iot:ListThingPrincipals": {}, + "iot:ListThingRegistrationTaskReports": {}, + "iot:ListThingRegistrationTasks": {}, + "iot:ListThingTypes": {}, + "iot:ListThings": {}, + "iot:ListTopicRuleDestinations": {}, + "iot:ListTopicRules": {}, + "iot:ListTunnels": {}, + "iot:ListV2LoggingLevels": {}, + "iot:OpenTunnel": {}, + "iot:PutVerificationStateOnViolation": {}, + "iot:RegisterCACertificate": {}, + "iot:RegisterCertificate": {}, + "iot:RegisterCertificateWithoutCA": {}, + "iot:RegisterThing": {}, + "iot:SetLoggingOptions": {}, + "iot:SetV2LoggingLevel": {}, + "iot:SetV2LoggingOptions": {}, + "iot:StartAuditMitigationActionsTask": {}, + "iot:StartOnDemandAuditTask": {}, + "iot:StartThingRegistrationTask": {}, + "iot:StopThingRegistrationTask": {}, + "iot:UpdateAccountAuditConfiguration": {}, + "iot:UpdateAuditSuppression": {}, + "iot:UpdateEventConfigurations": {}, + "iot:UpdateIndexingConfiguration": {}, + "iot:UpdatePackageConfiguration": {}, + "iot:ValidateSecurityProfileBehaviors": {}, + "iotanalytics:DescribeLoggingOptions": {}, + "iotanalytics:ListChannels": {}, + "iotanalytics:ListDatasets": {}, + "iotanalytics:ListDatastores": {}, + "iotanalytics:ListPipelines": {}, + "iotanalytics:PutLoggingOptions": {}, + "iotanalytics:RunPipelineActivity": {}, + "iotdeviceadvisor:CreateSuiteDefinition": {}, + "iotdeviceadvisor:GetEndpoint": {}, + "iotdeviceadvisor:ListSuiteDefinitions": {}, + "iotdeviceadvisor:StartSuiteRun": {}, + "iotevents:DescribeDetectorModelAnalysis": {}, + "iotevents:DescribeLoggingOptions": {}, + "iotevents:GetDetectorModelAnalysisResults": {}, + "iotevents:ListAlarmModels": {}, + "iotevents:ListDetectorModels": {}, + "iotevents:ListInputRoutings": {}, + "iotevents:ListInputs": {}, + "iotevents:PutLoggingOptions": {}, + "iotevents:StartDetectorModelAnalysis": {}, + "iotfleethub:CreateApplication": {}, + "iotfleethub:ListApplications": {}, + "iotfleetwise:GetEncryptionConfiguration": {}, + "iotfleetwise:GetLoggingOptions": {}, + "iotfleetwise:GetRegisterAccountStatus": {}, + "iotfleetwise:ListCampaigns": {}, + "iotfleetwise:ListDecoderManifests": {}, + "iotfleetwise:ListFleets": {}, + "iotfleetwise:ListModelManifests": {}, + "iotfleetwise:ListSignalCatalogs": {}, + "iotfleetwise:ListVehicles": {}, + "iotfleetwise:PutEncryptionConfiguration": {}, + "iotfleetwise:PutLoggingOptions": {}, + "iotfleetwise:RegisterAccount": {}, + "iotroborunner:CreateSite": {}, + "iotroborunner:ListSites": {}, + "iotsitewise:CreateAssetModel": {}, + "iotsitewise:CreateBulkImportJob": {}, + "iotsitewise:CreateGateway": {}, + "iotsitewise:CreatePortal": {}, + "iotsitewise:DescribeBulkImportJob": {}, + "iotsitewise:DescribeDefaultEncryptionConfiguration": {}, + "iotsitewise:DescribeLoggingOptions": {}, + "iotsitewise:DescribeStorageConfiguration": {}, + "iotsitewise:EnableSiteWiseIntegration": {}, + "iotsitewise:ExecuteQuery": {}, + "iotsitewise:ListAssetModels": {}, + "iotsitewise:ListBulkImportJobs": {}, + "iotsitewise:ListGateways": {}, + "iotsitewise:ListPortals": {}, + "iotsitewise:PutDefaultEncryptionConfiguration": {}, + "iotsitewise:PutLoggingOptions": {}, + "iotsitewise:PutStorageConfiguration": {}, + "iottwinmaker:CreateMetadataTransferJob": {}, + "iottwinmaker:CreateWorkspace": {}, + "iottwinmaker:GetPricingPlan": {}, + "iottwinmaker:ListMetadataTransferJobs": {}, + "iottwinmaker:ListWorkspaces": {}, + "iottwinmaker:UpdatePricingPlan": {}, + "iotwireless:AssociateAwsAccountWithPartnerAccount": {}, + "iotwireless:CreateDestination": {}, + "iotwireless:CreateDeviceProfile": {}, + "iotwireless:CreateFuotaTask": {}, + "iotwireless:CreateMulticastGroup": {}, + "iotwireless:CreateServiceProfile": {}, + "iotwireless:CreateWirelessDevice": {}, + "iotwireless:CreateWirelessGateway": {}, + "iotwireless:CreateWirelessGatewayTaskDefinition": {}, + "iotwireless:DeleteQueuedMessages": {}, + "iotwireless:GetEventConfigurationByResourceTypes": {}, + "iotwireless:GetLogLevelsByResourceTypes": {}, + "iotwireless:GetPositionEstimate": {}, + "iotwireless:GetServiceEndpoint": {}, + "iotwireless:ListDestinations": {}, + "iotwireless:ListDeviceProfiles": {}, + "iotwireless:ListEventConfigurations": {}, + "iotwireless:ListFuotaTasks": {}, + "iotwireless:ListMulticastGroups": {}, + "iotwireless:ListNetworkAnalyzerConfigurations": {}, + "iotwireless:ListPartnerAccounts": {}, + "iotwireless:ListPositionConfigurations": {}, + "iotwireless:ListQueuedMessages": {}, + "iotwireless:ListServiceProfiles": {}, + "iotwireless:ListWirelessDeviceImportTasks": {}, + "iotwireless:ListWirelessDevices": {}, + "iotwireless:ListWirelessGatewayTaskDefinitions": {}, + "iotwireless:ListWirelessGateways": {}, + "iotwireless:ResetAllResourceLogLevels": {}, + "iotwireless:StartSingleWirelessDeviceImportTask": {}, + "iotwireless:UpdateEventConfigurationByResourceTypes": {}, + "iotwireless:UpdateLogLevelsByResourceTypes": {}, + "iq:span": {}, + "ivs:ListEncoderConfigurations": {}, + "ivs:ListPlaybackRestrictionPolicies": {}, + "ivs:ListStorageConfigurations": {}, + "kafka:DescribeClusterOperation": {}, + "kafka:DescribeClusterOperationV2": {}, + "kafka:GetBootstrapBrokers": {}, + "kafka:GetCompatibleKafkaVersions": {}, + "kafka:ListClusters": {}, + "kafka:ListClustersV2": {}, + "kafka:ListConfigurations": {}, + "kafka:ListKafkaVersions": {}, + "kafka:ListReplicators": {}, + "kafka:ListVpcConnections": {}, + "kafkaconnect:CreateConnector": {}, + "kafkaconnect:CreateCustomPlugin": {}, + "kafkaconnect:CreateWorkerConfiguration": {}, + "kafkaconnect:DeleteConnector": {}, + "kafkaconnect:DeleteCustomPlugin": {}, + "kafkaconnect:ListConnectors": {}, + "kafkaconnect:ListCustomPlugins": {}, + "kafkaconnect:ListWorkerConfigurations": {}, + "kafkaconnect:UpdateConnector": {}, + "kendra-ranking:CreateRescoreExecutionPlan": {}, + "kendra-ranking:ListRescoreExecutionPlans": {}, + "kendra:CreateIndex": {}, + "kendra:ListIndices": {}, + "kinesis:DescribeLimits": {}, + "kinesis:DisableEnhancedMonitoring": {}, + "kinesis:EnableEnhancedMonitoring": {}, + "kinesis:ListStreams": {}, + "kinesis:UpdateShardCount": {}, + "kinesis:UpdateStreamMode": {}, + "kinesisanalytics:CreateApplication": {}, + "kinesisanalytics:DiscoverInputSchema": {}, + "kinesisanalytics:ListApplications": {}, + "kinesisvideo:ListEdgeAgentConfigurations": {}, + "kinesisvideo:ListSignalingChannels": {}, + "kinesisvideo:ListStreams": {}, + "kms:ConnectCustomKeyStore": {}, + "kms:CreateCustomKeyStore": {}, + "kms:CreateKey": {}, + "kms:DeleteCustomKeyStore": {}, + "kms:DescribeCustomKeyStores": {}, + "kms:DisconnectCustomKeyStore": {}, + "kms:GenerateRandom": {}, + "kms:ListAliases": {}, + "kms:ListKeys": {}, + "kms:ListRetirableGrants": {}, + "kms:UpdateCustomKeyStore": {}, + "lakeformation:AddLFTagsToResource": {}, + "lakeformation:BatchGrantPermissions": {}, + "lakeformation:BatchRevokePermissions": {}, + "lakeformation:CancelTransaction": {}, + "lakeformation:CommitTransaction": {}, + "lakeformation:CreateDataCellsFilter": {}, + "lakeformation:CreateLFTag": {}, + "lakeformation:CreateLakeFormationIdentityCenterConfiguration": {}, + "lakeformation:CreateLakeFormationOptIn": {}, + "lakeformation:DeleteDataCellsFilter": {}, + "lakeformation:DeleteLFTag": {}, + "lakeformation:DeleteLakeFormationIdentityCenterConfiguration": {}, + "lakeformation:DeleteLakeFormationOptIn": {}, + "lakeformation:DeleteObjectsOnCancel": {}, + "lakeformation:DeregisterResource": {}, + "lakeformation:DescribeLakeFormationIdentityCenterConfiguration": {}, + "lakeformation:DescribeResource": {}, + "lakeformation:DescribeTransaction": {}, + "lakeformation:ExtendTransaction": {}, + "lakeformation:GetDataAccess": {}, + "lakeformation:GetDataCellsFilter": {}, + "lakeformation:GetDataLakeSettings": {}, + "lakeformation:GetEffectivePermissionsForPath": {}, + "lakeformation:GetLFTag": {}, + "lakeformation:GetQueryState": {}, + "lakeformation:GetQueryStatistics": {}, + "lakeformation:GetResourceLFTags": {}, + "lakeformation:GetTableObjects": {}, + "lakeformation:GetWorkUnitResults": {}, + "lakeformation:GetWorkUnits": {}, + "lakeformation:GrantPermissions": {}, + "lakeformation:ListDataCellsFilter": {}, + "lakeformation:ListLFTags": {}, + "lakeformation:ListLakeFormationOptIns": {}, + "lakeformation:ListPermissions": {}, + "lakeformation:ListResources": {}, + "lakeformation:ListTableStorageOptimizers": {}, + "lakeformation:ListTransactions": {}, + "lakeformation:PutDataLakeSettings": {}, + "lakeformation:RegisterResource": {}, + "lakeformation:RemoveLFTagsFromResource": {}, + "lakeformation:RevokePermissions": {}, + "lakeformation:SearchDatabasesByLFTags": {}, + "lakeformation:SearchTablesByLFTags": {}, + "lakeformation:StartQueryPlanning": {}, + "lakeformation:StartTransaction": {}, + "lakeformation:UpdateDataCellsFilter": {}, + "lakeformation:UpdateLFTag": {}, + "lakeformation:UpdateLakeFormationIdentityCenterConfiguration": {}, + "lakeformation:UpdateResource": {}, + "lakeformation:UpdateTableObjects": {}, + "lakeformation:UpdateTableStorageOptimizer": {}, + "lambda:CreateCodeSigningConfig": {}, + "lambda:CreateEventSourceMapping": {}, + "lambda:GetAccountSettings": {}, + "lambda:ListCodeSigningConfigs": {}, + "lambda:ListEventSourceMappings": {}, + "lambda:ListFunctions": {}, + "lambda:ListLayerVersions": {}, + "lambda:ListLayers": {}, + "launchwizard:CreateAdditionalNode": {}, + "launchwizard:CreateDeployment": {}, + "launchwizard:CreateSettingsSet": {}, + "launchwizard:DeleteAdditionalNode": {}, + "launchwizard:DeleteApp": {}, + "launchwizard:DeleteDeployment": {}, + "launchwizard:DeleteSettingsSet": {}, + "launchwizard:DescribeAdditionalNode": {}, + "launchwizard:DescribeProvisionedApp": {}, + "launchwizard:DescribeProvisioningEvents": {}, + "launchwizard:DescribeSettingsSet": {}, + "launchwizard:GetDeployment": {}, + "launchwizard:GetInfrastructureSuggestion": {}, + "launchwizard:GetIpAddress": {}, + "launchwizard:GetResourceCostEstimate": {}, + "launchwizard:GetResourceRecommendation": {}, + "launchwizard:GetSettingsSet": {}, + "launchwizard:GetWorkload": {}, + "launchwizard:GetWorkloadAsset": {}, + "launchwizard:GetWorkloadAssets": {}, + "launchwizard:ListAdditionalNodes": {}, + "launchwizard:ListAllowedResources": {}, + "launchwizard:ListDeploymentEvents": {}, + "launchwizard:ListDeployments": {}, + "launchwizard:ListProvisionedApps": {}, + "launchwizard:ListResourceCostEstimates": {}, + "launchwizard:ListSettingsSets": {}, + "launchwizard:ListWorkloadDeploymentOptions": {}, + "launchwizard:ListWorkloadDeploymentPatterns": {}, + "launchwizard:ListWorkloads": {}, + "launchwizard:PutSettingsSet": {}, + "launchwizard:StartProvisioning": {}, + "launchwizard:UpdateSettingsSet": {}, + "lex:CreateTestSet": {}, + "lex:CreateUploadUrl": {}, + "lex:GetBotAliases": {}, + "lex:GetBots": {}, + "lex:GetBuiltinIntent": {}, + "lex:GetBuiltinIntents": {}, + "lex:GetBuiltinSlotTypes": {}, + "lex:GetImport": {}, + "lex:GetIntents": {}, + "lex:GetMigration": {}, + "lex:GetMigrations": {}, + "lex:GetSlotTypes": {}, + "lex:ListBots": {}, + "lex:ListBuiltInIntents": {}, + "lex:ListBuiltInSlotTypes": {}, + "lex:ListExports": {}, + "lex:ListImports": {}, + "lex:ListTestExecutions": {}, + "lex:ListTestSets": {}, + "lex:StartImport": {}, + "license-manager-linux-subscriptions:GetServiceSettings": {}, + "license-manager-linux-subscriptions:ListLinuxSubscriptionInstances": {}, + "license-manager-linux-subscriptions:ListLinuxSubscriptions": {}, + "license-manager-linux-subscriptions:UpdateServiceSettings": {}, + "license-manager-user-subscriptions:AssociateUser": {}, + "license-manager-user-subscriptions:DeregisterIdentityProvider": {}, + "license-manager-user-subscriptions:DisassociateUser": {}, + "license-manager-user-subscriptions:ListIdentityProviders": {}, + "license-manager-user-subscriptions:ListInstances": {}, + "license-manager-user-subscriptions:ListProductSubscriptions": {}, + "license-manager-user-subscriptions:ListUserAssociations": {}, + "license-manager-user-subscriptions:RegisterIdentityProvider": {}, + "license-manager-user-subscriptions:StartProductSubscription": {}, + "license-manager-user-subscriptions:StopProductSubscription": {}, + "license-manager-user-subscriptions:UpdateIdentityProviderSettings": {}, + "license-manager:CheckInLicense": {}, + "license-manager:CheckoutLicense": {}, + "license-manager:CreateLicense": {}, + "license-manager:CreateLicenseConfiguration": {}, + "license-manager:CreateLicenseConversionTaskForResource": {}, + "license-manager:CreateLicenseManagerReportGenerator": {}, + "license-manager:DeleteToken": {}, + "license-manager:ExtendLicenseConsumption": {}, + "license-manager:GetAccessToken": {}, + "license-manager:GetLicenseConversionTask": {}, + "license-manager:GetServiceSettings": {}, + "license-manager:ListDistributedGrants": {}, + "license-manager:ListLicenseConfigurations": {}, + "license-manager:ListLicenseConversionTasks": {}, + "license-manager:ListLicenseSpecificationsForResource": {}, + "license-manager:ListLicenses": {}, + "license-manager:ListReceivedGrants": {}, + "license-manager:ListReceivedGrantsForOrganization": {}, + "license-manager:ListReceivedLicenses": {}, + "license-manager:ListReceivedLicensesForOrganization": {}, + "license-manager:ListResourceInventory": {}, + "license-manager:ListTokens": {}, + "license-manager:UpdateServiceSettings": {}, + "lightsail:AllocateStaticIp": {}, + "lightsail:CopySnapshot": {}, + "lightsail:CreateBucket": {}, + "lightsail:CreateCertificate": {}, + "lightsail:CreateCloudFormationStack": {}, + "lightsail:CreateContactMethod": {}, + "lightsail:CreateContainerService": {}, + "lightsail:CreateContainerServiceRegistryLogin": {}, + "lightsail:CreateDisk": {}, + "lightsail:CreateDistribution": {}, + "lightsail:CreateDomain": {}, + "lightsail:CreateInstances": {}, + "lightsail:CreateKeyPair": {}, + "lightsail:CreateLoadBalancer": {}, + "lightsail:CreateRelationalDatabase": {}, + "lightsail:CreateRelationalDatabaseSnapshot": {}, + "lightsail:DeleteAutoSnapshot": {}, + "lightsail:DeleteContactMethod": {}, + "lightsail:DisableAddOn": {}, + "lightsail:DownloadDefaultKeyPair": {}, + "lightsail:EnableAddOn": {}, + "lightsail:GetActiveNames": {}, + "lightsail:GetAlarms": {}, + "lightsail:GetAutoSnapshots": {}, + "lightsail:GetBlueprints": {}, + "lightsail:GetBucketAccessKeys": {}, + "lightsail:GetBucketBundles": {}, + "lightsail:GetBucketMetricData": {}, + "lightsail:GetBuckets": {}, + "lightsail:GetBundles": {}, + "lightsail:GetCertificates": {}, + "lightsail:GetCloudFormationStackRecords": {}, + "lightsail:GetContactMethods": {}, + "lightsail:GetContainerAPIMetadata": {}, + "lightsail:GetContainerImages": {}, + "lightsail:GetContainerLog": {}, + "lightsail:GetContainerServiceDeployments": {}, + "lightsail:GetContainerServiceMetricData": {}, + "lightsail:GetContainerServicePowers": {}, + "lightsail:GetContainerServices": {}, + "lightsail:GetDisk": {}, + "lightsail:GetDiskSnapshot": {}, + "lightsail:GetDiskSnapshots": {}, + "lightsail:GetDisks": {}, + "lightsail:GetDistributionBundles": {}, + "lightsail:GetDistributionLatestCacheReset": {}, + "lightsail:GetDistributionMetricData": {}, + "lightsail:GetDistributions": {}, + "lightsail:GetDomain": {}, + "lightsail:GetDomains": {}, + "lightsail:GetExportSnapshotRecords": {}, + "lightsail:GetInstance": {}, + "lightsail:GetInstanceMetricData": {}, + "lightsail:GetInstancePortStates": {}, + "lightsail:GetInstanceSnapshot": {}, + "lightsail:GetInstanceSnapshots": {}, + "lightsail:GetInstanceState": {}, + "lightsail:GetInstances": {}, + "lightsail:GetKeyPair": {}, + "lightsail:GetKeyPairs": {}, + "lightsail:GetLoadBalancer": {}, + "lightsail:GetLoadBalancerMetricData": {}, + "lightsail:GetLoadBalancerTlsCertificates": {}, + "lightsail:GetLoadBalancerTlsPolicies": {}, + "lightsail:GetLoadBalancers": {}, + "lightsail:GetOperation": {}, + "lightsail:GetOperations": {}, + "lightsail:GetOperationsForResource": {}, + "lightsail:GetRegions": {}, + "lightsail:GetRelationalDatabase": {}, + "lightsail:GetRelationalDatabaseBlueprints": {}, + "lightsail:GetRelationalDatabaseBundles": {}, + "lightsail:GetRelationalDatabaseEvents": {}, + "lightsail:GetRelationalDatabaseLogEvents": {}, + "lightsail:GetRelationalDatabaseLogStreams": {}, + "lightsail:GetRelationalDatabaseMetricData": {}, + "lightsail:GetRelationalDatabaseParameters": {}, + "lightsail:GetRelationalDatabaseSnapshot": {}, + "lightsail:GetRelationalDatabaseSnapshots": {}, + "lightsail:GetRelationalDatabases": {}, + "lightsail:GetStaticIp": {}, + "lightsail:GetStaticIps": {}, + "lightsail:ImportKeyPair": {}, + "lightsail:IsVpcPeered": {}, + "lightsail:PeerVpc": {}, + "lightsail:SendContactMethodVerification": {}, + "lightsail:UnpeerVpc": {}, + "logs:CancelExportTask": {}, + "logs:CreateLogDelivery": {}, + "logs:DeleteAccountPolicy": {}, + "logs:DeleteLogDelivery": {}, + "logs:DeleteQueryDefinition": {}, + "logs:DeleteResourcePolicy": {}, + "logs:DescribeAccountPolicies": {}, + "logs:DescribeDeliveries": {}, + "logs:DescribeDeliveryDestinations": {}, + "logs:DescribeDeliverySources": {}, + "logs:DescribeDestinations": {}, + "logs:DescribeExportTasks": {}, + "logs:DescribeLogGroups": {}, + "logs:DescribeQueries": {}, + "logs:DescribeQueryDefinitions": {}, + "logs:DescribeResourcePolicies": {}, + "logs:GetLogDelivery": {}, + "logs:Link": {}, + "logs:ListLogDeliveries": {}, + "logs:PutAccountPolicy": {}, + "logs:PutQueryDefinition": {}, + "logs:PutResourcePolicy": {}, + "logs:StopLiveTail": {}, + "logs:StopQuery": {}, + "logs:TestMetricFilter": {}, + "logs:UpdateLogDelivery": {}, + "lookoutequipment:DescribeDataIngestionJob": {}, + "lookoutequipment:ListDatasets": {}, + "lookoutequipment:ListInferenceSchedulers": {}, + "lookoutequipment:ListModels": {}, + "lookoutequipment:ListRetrainingSchedulers": {}, + "lookoutmetrics:GetSampleData": {}, + "lookoutmetrics:ListAnomalyDetectors": {}, + "lookoutvision:CreateDataset": {}, + "lookoutvision:DeleteDataset": {}, + "lookoutvision:DescribeDataset": {}, + "lookoutvision:DescribeModelPackagingJob": {}, + "lookoutvision:DescribeTrialDetection": {}, + "lookoutvision:ListDatasetEntries": {}, + "lookoutvision:ListModelPackagingJobs": {}, + "lookoutvision:ListModels": {}, + "lookoutvision:ListProjects": {}, + "lookoutvision:ListTrialDetections": {}, + "lookoutvision:StartTrialDetection": {}, + "lookoutvision:UpdateDatasetEntries": {}, + "m2:CreateApplication": {}, + "m2:CreateEnvironment": {}, + "m2:GetSignedBluinsightsUrl": {}, + "m2:ListApplications": {}, + "m2:ListEngineVersions": {}, + "m2:ListEnvironments": {}, + "m2:ListTagsForResource": {}, + "machinelearning:DescribeBatchPredictions": {}, + "machinelearning:DescribeDataSources": {}, + "machinelearning:DescribeEvaluations": {}, + "machinelearning:DescribeMLModels": {}, + "macie2:AcceptInvitation": {}, + "macie2:CreateAllowList": {}, + "macie2:CreateInvitations": {}, + "macie2:CreateSampleFindings": {}, + "macie2:DeclineInvitations": {}, + "macie2:DeleteInvitations": {}, + "macie2:DescribeBuckets": {}, + "macie2:DescribeOrganizationConfiguration": {}, + "macie2:DisableMacie": {}, + "macie2:DisableOrganizationAdminAccount": {}, + "macie2:DisassociateFromAdministratorAccount": {}, + "macie2:DisassociateFromMasterAccount": {}, + "macie2:EnableMacie": {}, + "macie2:EnableOrganizationAdminAccount": {}, + "macie2:GetAdministratorAccount": {}, + "macie2:GetAutomatedDiscoveryConfiguration": {}, + "macie2:GetBucketStatistics": {}, + "macie2:GetClassificationExportConfiguration": {}, + "macie2:GetClassificationScope": {}, + "macie2:GetFindingStatistics": {}, + "macie2:GetFindings": {}, + "macie2:GetFindingsPublicationConfiguration": {}, + "macie2:GetInvitationsCount": {}, + "macie2:GetMacieSession": {}, + "macie2:GetMasterAccount": {}, + "macie2:GetResourceProfile": {}, + "macie2:GetRevealConfiguration": {}, + "macie2:GetSensitiveDataOccurrences": {}, + "macie2:GetSensitiveDataOccurrencesAvailability": {}, + "macie2:GetSensitivityInspectionTemplate": {}, + "macie2:GetUsageStatistics": {}, + "macie2:GetUsageTotals": {}, + "macie2:ListAllowLists": {}, + "macie2:ListClassificationJobs": {}, + "macie2:ListClassificationScopes": {}, + "macie2:ListCustomDataIdentifiers": {}, + "macie2:ListFindings": {}, + "macie2:ListFindingsFilters": {}, + "macie2:ListInvitations": {}, + "macie2:ListManagedDataIdentifiers": {}, + "macie2:ListMembers": {}, + "macie2:ListOrganizationAdminAccounts": {}, + "macie2:ListResourceProfileArtifacts": {}, + "macie2:ListResourceProfileDetections": {}, + "macie2:ListSensitivityInspectionTemplates": {}, + "macie2:PutClassificationExportConfiguration": {}, + "macie2:PutFindingsPublicationConfiguration": {}, + "macie2:SearchResources": {}, + "macie2:TestCustomDataIdentifier": {}, + "macie2:UpdateAutomatedDiscoveryConfiguration": {}, + "macie2:UpdateClassificationScope": {}, + "macie2:UpdateMacieSession": {}, + "macie2:UpdateMemberSession": {}, + "macie2:UpdateOrganizationConfiguration": {}, + "macie2:UpdateResourceProfile": {}, + "macie2:UpdateResourceProfileDetections": {}, + "macie2:UpdateRevealConfiguration": {}, + "macie2:UpdateSensitivityInspectionTemplate": {}, + "managedblockchain-query:BatchGetTokenBalance": {}, + "managedblockchain-query:GetAssetContract": {}, + "managedblockchain-query:GetTokenBalance": {}, + "managedblockchain-query:GetTransaction": {}, + "managedblockchain-query:ListAssetContracts": {}, + "managedblockchain-query:ListTokenBalances": {}, + "managedblockchain-query:ListTransactionEvents": {}, + "managedblockchain-query:ListTransactions": {}, + "managedblockchain:CreateAccessor": {}, + "managedblockchain:CreateNetwork": {}, + "managedblockchain:GET": {}, + "managedblockchain:Invoke": {}, + "managedblockchain:InvokeRpcBitcoinMainnet": {}, + "managedblockchain:InvokeRpcBitcoinTestnet": {}, + "managedblockchain:InvokeRpcPolygonMainnet": {}, + "managedblockchain:InvokeRpcPolygonMumbaiTestnet": {}, + "managedblockchain:ListAccessors": {}, + "managedblockchain:ListInvitations": {}, + "managedblockchain:ListNetworks": {}, + "managedblockchain:POST": {}, + "mechanicalturk:AcceptQualificationRequest": {}, + "mechanicalturk:ApproveAssignment": {}, + "mechanicalturk:AssociateQualificationWithWorker": {}, + "mechanicalturk:CreateAdditionalAssignmentsForHIT": {}, + "mechanicalturk:CreateHIT": {}, + "mechanicalturk:CreateHITType": {}, + "mechanicalturk:CreateHITWithHITType": {}, + "mechanicalturk:CreateQualificationType": {}, + "mechanicalturk:CreateWorkerBlock": {}, + "mechanicalturk:DeleteHIT": {}, + "mechanicalturk:DeleteQualificationType": {}, + "mechanicalturk:DeleteWorkerBlock": {}, + "mechanicalturk:DisassociateQualificationFromWorker": {}, + "mechanicalturk:GetAccountBalance": {}, + "mechanicalturk:GetAssignment": {}, + "mechanicalturk:GetFileUploadURL": {}, + "mechanicalturk:GetHIT": {}, + "mechanicalturk:GetQualificationScore": {}, + "mechanicalturk:GetQualificationType": {}, + "mechanicalturk:ListAssignmentsForHIT": {}, + "mechanicalturk:ListBonusPayments": {}, + "mechanicalturk:ListHITs": {}, + "mechanicalturk:ListHITsForQualificationType": {}, + "mechanicalturk:ListQualificationRequests": {}, + "mechanicalturk:ListQualificationTypes": {}, + "mechanicalturk:ListReviewPolicyResultsForHIT": {}, + "mechanicalturk:ListReviewableHITs": {}, + "mechanicalturk:ListWorkerBlocks": {}, + "mechanicalturk:ListWorkersWithQualificationType": {}, + "mechanicalturk:NotifyWorkers": {}, + "mechanicalturk:RejectAssignment": {}, + "mechanicalturk:RejectQualificationRequest": {}, + "mechanicalturk:SendBonus": {}, + "mechanicalturk:SendTestEventNotification": {}, + "mechanicalturk:UpdateExpirationForHIT": {}, + "mechanicalturk:UpdateHITReviewStatus": {}, + "mechanicalturk:UpdateHITTypeOfHIT": {}, + "mechanicalturk:UpdateNotificationSettings": {}, + "mechanicalturk:UpdateQualificationType": {}, + "mediaconnect:AddFlowMediaStreams": {}, + "mediaconnect:AddFlowOutputs": {}, + "mediaconnect:AddFlowSources": {}, + "mediaconnect:AddFlowVpcInterfaces": {}, + "mediaconnect:CreateFlow": {}, + "mediaconnect:DeleteFlow": {}, + "mediaconnect:DescribeFlow": {}, + "mediaconnect:DescribeFlowSourceMetadata": {}, + "mediaconnect:DescribeOffering": {}, + "mediaconnect:DescribeReservation": {}, + "mediaconnect:DiscoverGatewayPollEndpoint": {}, + "mediaconnect:GrantFlowEntitlements": {}, + "mediaconnect:ListEntitlements": {}, + "mediaconnect:ListFlows": {}, + "mediaconnect:ListGateways": {}, + "mediaconnect:ListOfferings": {}, + "mediaconnect:ListReservations": {}, + "mediaconnect:ListTagsForResource": {}, + "mediaconnect:PollGateway": {}, + "mediaconnect:PurchaseOffering": {}, + "mediaconnect:RemoveFlowMediaStream": {}, + "mediaconnect:RemoveFlowOutput": {}, + "mediaconnect:RemoveFlowSource": {}, + "mediaconnect:RemoveFlowVpcInterface": {}, + "mediaconnect:RevokeFlowEntitlement": {}, + "mediaconnect:StartFlow": {}, + "mediaconnect:StopFlow": {}, + "mediaconnect:SubmitGatewayStateChange": {}, + "mediaconnect:TagResource": {}, + "mediaconnect:UntagResource": {}, + "mediaconnect:UpdateFlow": {}, + "mediaconnect:UpdateFlowEntitlement": {}, + "mediaconnect:UpdateFlowMediaStream": {}, + "mediaconnect:UpdateFlowOutput": {}, + "mediaconnect:UpdateFlowSource": {}, + "mediaconvert:AssociateCertificate": {}, + "mediaconvert:CreatePreset": {}, + "mediaconvert:CreateQueue": {}, + "mediaconvert:DeletePolicy": {}, + "mediaconvert:DescribeEndpoints": {}, + "mediaconvert:DisassociateCertificate": {}, + "mediaconvert:GetPolicy": {}, + "mediaconvert:ListJobTemplates": {}, + "mediaconvert:ListPresets": {}, + "mediaconvert:ListQueues": {}, + "mediaconvert:PutPolicy": {}, + "mediaimport:CreateDatabaseBinarySnapshot": {}, + "medialive:BatchDelete": {}, + "medialive:BatchStart": {}, + "medialive:BatchStop": {}, + "medialive:DescribeAccountConfiguration": {}, + "medialive:ListChannels": {}, + "medialive:ListInputDeviceTransfers": {}, + "medialive:ListInputDevices": {}, + "medialive:ListInputSecurityGroups": {}, + "medialive:ListInputs": {}, + "medialive:ListMultiplexPrograms": {}, + "medialive:ListMultiplexes": {}, + "medialive:ListOfferings": {}, + "medialive:ListReservations": {}, + "medialive:UpdateAccountConfiguration": {}, + "mediapackage-vod:CreateAsset": {}, + "mediapackage-vod:CreatePackagingConfiguration": {}, + "mediapackage-vod:CreatePackagingGroup": {}, + "mediapackage-vod:ListAssets": {}, + "mediapackage-vod:ListPackagingConfigurations": {}, + "mediapackage-vod:ListPackagingGroups": {}, + "mediapackage:CreateChannel": {}, + "mediapackage:CreateHarvestJob": {}, + "mediapackage:CreateOriginEndpoint": {}, + "mediapackage:ListChannels": {}, + "mediapackage:ListHarvestJobs": {}, + "mediapackage:ListOriginEndpoints": {}, + "mediapackagev2:ListChannelGroups": {}, + "mediastore:CreateContainer": {}, + "mediastore:ListContainers": {}, + "mediatailor:CreateChannel": {}, + "mediatailor:CreateLiveSource": {}, + "mediatailor:CreateProgram": {}, + "mediatailor:CreateSourceLocation": {}, + "mediatailor:CreateVodSource": {}, + "mediatailor:ListAlerts": {}, + "mediatailor:ListChannels": {}, + "mediatailor:ListLiveSources": {}, + "mediatailor:ListPlaybackConfigurations": {}, + "mediatailor:ListSourceLocations": {}, + "mediatailor:ListVodSources": {}, + "mediatailor:PutPlaybackConfiguration": {}, + "medical-imaging:CreateDatastore": {}, + "medical-imaging:ListDatastores": {}, + "memorydb:CreateParameterGroup": {}, + "memorydb:CreateSubnetGroup": {}, + "memorydb:CreateUser": {}, + "memorydb:DescribeEngineVersions": {}, + "memorydb:DescribeEvents": {}, + "memorydb:DescribeReservedNodesOfferings": {}, + "memorydb:DescribeServiceUpdates": {}, + "mgh:CreateHomeRegionControl": {}, + "mgh:DeleteHomeRegionControl": {}, + "mgh:DescribeApplicationState": {}, + "mgh:DescribeHomeRegionControls": {}, + "mgh:GetHomeRegion": {}, + "mgh:ListApplicationStates": {}, + "mgh:ListMigrationTasks": {}, + "mgh:ListProgressUpdateStreams": {}, + "mgh:NotifyApplicationState": {}, + "mgn:BatchDeleteSnapshotRequestForMgn": {}, + "mgn:CreateApplication": {}, + "mgn:CreateConnector": {}, + "mgn:CreateLaunchConfigurationTemplate": {}, + "mgn:CreateReplicationConfigurationTemplate": {}, + "mgn:CreateVcenterClientForMgn": {}, + "mgn:CreateWave": {}, + "mgn:DescribeJobs": {}, + "mgn:DescribeLaunchConfigurationTemplates": {}, + "mgn:DescribeReplicationConfigurationTemplates": {}, + "mgn:DescribeReplicationServerAssociationsForMgn": {}, + "mgn:DescribeSnapshotRequestsForMgn": {}, + "mgn:DescribeSourceServers": {}, + "mgn:DescribeVcenterClients": {}, + "mgn:GetAgentInstallationAssetsForMgn": {}, + "mgn:GetChannelCommandsForMgn": {}, + "mgn:InitializeService": {}, + "mgn:ListApplications": {}, + "mgn:ListConnectors": {}, + "mgn:ListExports": {}, + "mgn:ListImports": {}, + "mgn:ListManagedAccounts": {}, + "mgn:ListTagsForResource": {}, + "mgn:ListWaves": {}, + "mgn:RegisterAgentForMgn": {}, + "mgn:SendChannelCommandResultForMgn": {}, + "mgn:SendClientLogsForMgn": {}, + "mgn:SendClientMetricsForMgn": {}, + "mgn:StartExport": {}, + "mgn:StartImport": {}, + "mgn:VerifyClientRoleForMgn": {}, + "migrationhub-orchestrator:CreateWorkflow": {}, + "migrationhub-orchestrator:GetMessage": {}, + "migrationhub-orchestrator:GetTemplate": {}, + "migrationhub-orchestrator:GetTemplateStep": {}, + "migrationhub-orchestrator:GetTemplateStepGroup": {}, + "migrationhub-orchestrator:ListPlugins": {}, + "migrationhub-orchestrator:ListTemplateStepGroups": {}, + "migrationhub-orchestrator:ListTemplateSteps": {}, + "migrationhub-orchestrator:ListTemplates": {}, + "migrationhub-orchestrator:ListWorkflows": {}, + "migrationhub-orchestrator:RegisterPlugin": {}, + "migrationhub-orchestrator:SendMessage": {}, + "migrationhub-strategy:GetAntiPattern": {}, + "migrationhub-strategy:GetApplicationComponentDetails": {}, + "migrationhub-strategy:GetApplicationComponentStrategies": {}, + "migrationhub-strategy:GetAssessment": {}, + "migrationhub-strategy:GetImportFileTask": {}, + "migrationhub-strategy:GetLatestAssessmentId": {}, + "migrationhub-strategy:GetMessage": {}, + "migrationhub-strategy:GetPortfolioPreferences": {}, + "migrationhub-strategy:GetPortfolioSummary": {}, + "migrationhub-strategy:GetRecommendationReportDetails": {}, + "migrationhub-strategy:GetServerDetails": {}, + "migrationhub-strategy:GetServerStrategies": {}, + "migrationhub-strategy:ListAnalyzableServers": {}, + "migrationhub-strategy:ListAntiPatterns": {}, + "migrationhub-strategy:ListApplicationComponents": {}, + "migrationhub-strategy:ListCollectors": {}, + "migrationhub-strategy:ListImportFileTask": {}, + "migrationhub-strategy:ListJarArtifacts": {}, + "migrationhub-strategy:ListServers": {}, + "migrationhub-strategy:PutPortfolioPreferences": {}, + "migrationhub-strategy:RegisterCollector": {}, + "migrationhub-strategy:SendMessage": {}, + "migrationhub-strategy:StartAssessment": {}, + "migrationhub-strategy:StartImportFileTask": {}, + "migrationhub-strategy:StartRecommendationReportGeneration": {}, + "migrationhub-strategy:StopAssessment": {}, + "migrationhub-strategy:UpdateApplicationComponentConfig": {}, + "migrationhub-strategy:UpdateCollectorConfiguration": {}, + "migrationhub-strategy:UpdateServerConfig": {}, + "mobileanalytics:PutEvents": {}, + "monitron:CreateProject": {}, + "monitron:ListProjects": {}, + "mq:CreateBroker": {}, + "mq:CreateConfiguration": {}, + "mq:DescribeBrokerEngineTypes": {}, + "mq:DescribeBrokerInstanceOptions": {}, + "mq:ListBrokers": {}, + "mq:ListConfigurations": {}, + "network-firewall:ListRuleGroups": {}, + "networkmanager-chat:CancelMessageResponse": {}, + "networkmanager-chat:CreateConversation": {}, + "networkmanager-chat:DeleteConversation": {}, + "networkmanager-chat:ListConversationMessages": {}, + "networkmanager-chat:ListConversations": {}, + "networkmanager-chat:NotifyConversationIsActive": {}, + "networkmanager-chat:SendConversationMessage": {}, + "networkmanager:CreateGlobalNetwork": {}, + "networkmanager:ListCoreNetworks": {}, + "networkmanager:ListOrganizationServiceAccessStatus": {}, + "networkmanager:ListPeerings": {}, + "networkmanager:StartOrganizationServiceAccessUpdate": {}, + "networkmonitor:CreateProbe": {}, + "networkmonitor:ListMonitors": {}, + "nimble:GetFeatureMap": {}, + "nimble:ListStudios": {}, + "notifications-contacts:CreateEmailContact": {}, + "notifications-contacts:ListEmailContacts": {}, + "notifications-contacts:ListTagsForResource": {}, + "notifications:CreateEventRule": {}, + "notifications:CreateNotificationConfiguration": {}, + "notifications:DeregisterNotificationHub": {}, + "notifications:ListChannels": {}, + "notifications:ListEventRules": {}, + "notifications:ListNotificationConfigurations": {}, + "notifications:ListNotificationEvents": {}, + "notifications:ListNotificationHubs": {}, + "notifications:ListTagsForResource": {}, + "notifications:RegisterNotificationHub": {}, + "oam:CreateSink": {}, + "oam:ListLinks": {}, + "oam:ListSinks": {}, + "omics:AcceptShare": {}, + "omics:CreateAnnotationStore": {}, + "omics:CreateReferenceStore": {}, + "omics:CreateRunGroup": {}, + "omics:CreateSequenceStore": {}, + "omics:CreateShare": {}, + "omics:CreateVariantStore": {}, + "omics:CreateWorkflow": {}, + "omics:DeleteShare": {}, + "omics:GetShare": {}, + "omics:ListAnnotationImportJobs": {}, + "omics:ListAnnotationStores": {}, + "omics:ListReferenceStores": {}, + "omics:ListRunGroups": {}, + "omics:ListRuns": {}, + "omics:ListSequenceStores": {}, + "omics:ListShares": {}, + "omics:ListTagsForResource": {}, + "omics:ListVariantImportJobs": {}, + "omics:ListVariantStores": {}, + "omics:ListWorkflows": {}, + "omics:StartAnnotationImportJob": {}, + "omics:StartRun": {}, + "omics:StartVariantImportJob": {}, + "one:CreateDeviceConfigurationTemplate": {}, + "one:CreateDeviceInstance": {}, + "one:CreateSite": {}, + "one:ListDeviceConfigurationTemplates": {}, + "one:ListDeviceInstances": {}, + "one:ListSites": {}, + "one:ListUsers": {}, + "opsworks-cm:AssociateNode": {}, + "opsworks-cm:CreateBackup": {}, + "opsworks-cm:CreateServer": {}, + "opsworks-cm:DeleteBackup": {}, + "opsworks-cm:DeleteServer": {}, + "opsworks-cm:DescribeAccountAttributes": {}, + "opsworks-cm:DescribeBackups": {}, + "opsworks-cm:DescribeEvents": {}, + "opsworks-cm:DescribeNodeAssociationStatus": {}, + "opsworks-cm:DescribeServers": {}, + "opsworks-cm:DisassociateNode": {}, + "opsworks-cm:ExportServerEngineAttribute": {}, + "opsworks-cm:ListTagsForResource": {}, + "opsworks-cm:RestoreServer": {}, + "opsworks-cm:StartMaintenance": {}, + "opsworks-cm:TagResource": {}, + "opsworks-cm:UntagResource": {}, + "opsworks-cm:UpdateServer": {}, + "opsworks-cm:UpdateServerEngineAttributes": {}, + "opsworks:CreateStack": {}, + "opsworks:CreateUserProfile": {}, + "opsworks:DeleteUserProfile": {}, + "opsworks:DescribeMyUserProfile": {}, + "opsworks:DescribeOperatingSystems": {}, + "opsworks:DescribeUserProfiles": {}, + "opsworks:UpdateMyUserProfile": {}, + "opsworks:UpdateUserProfile": {}, + "organizations:CreateAccount": {}, + "organizations:CreateGovCloudAccount": {}, + "organizations:CreateOrganization": {}, + "organizations:CreatePolicy": {}, + "organizations:DeleteOrganization": {}, + "organizations:DeleteResourcePolicy": {}, + "organizations:DescribeCreateAccountStatus": {}, + "organizations:DescribeOrganization": {}, + "organizations:DescribeResourcePolicy": {}, + "organizations:DisableAWSServiceAccess": {}, + "organizations:EnableAWSServiceAccess": {}, + "organizations:EnableAllFeatures": {}, + "organizations:LeaveOrganization": {}, + "organizations:ListAWSServiceAccessForOrganization": {}, + "organizations:ListAccounts": {}, + "organizations:ListCreateAccountStatus": {}, + "organizations:ListDelegatedAdministrators": {}, + "organizations:ListHandshakesForAccount": {}, + "organizations:ListHandshakesForOrganization": {}, + "organizations:ListPolicies": {}, + "organizations:ListRoots": {}, + "osis:CreatePipeline": {}, + "osis:ListPipelineBlueprints": {}, + "osis:ListPipelines": {}, + "osis:ValidatePipeline": {}, + "outposts:CancelOrder": {}, + "outposts:CreatePrivateConnectivityConfig": {}, + "outposts:CreateSite": {}, + "outposts:GetCatalogItem": {}, + "outposts:GetConnection": {}, + "outposts:GetOrder": {}, + "outposts:GetPrivateConnectivityConfig": {}, + "outposts:ListAssets": {}, + "outposts:ListCatalogItems": {}, + "outposts:ListOrders": {}, + "outposts:ListOutposts": {}, + "outposts:ListSites": {}, + "outposts:ListTagsForResource": {}, + "outposts:StartConnection": {}, + "panorama:CreateApplicationInstance": {}, + "panorama:CreateJobForDevices": {}, + "panorama:CreateNodeFromTemplateJob": {}, + "panorama:CreatePackage": {}, + "panorama:CreatePackageImportJob": {}, + "panorama:DescribeDeviceJob": {}, + "panorama:DescribeNode": {}, + "panorama:DescribeNodeFromTemplateJob": {}, + "panorama:DescribePackageImportJob": {}, + "panorama:DescribeSoftware": {}, + "panorama:GetWebSocketURL": {}, + "panorama:ListDevices": {}, + "panorama:ListNodeFromTemplateJobs": {}, + "panorama:ListNodes": {}, + "panorama:ListPackageImportJobs": {}, + "panorama:ListPackages": {}, + "panorama:ProvisionDevice": {}, + "partnercentral-account-management:AssociatePartnerAccount": {}, + "partnercentral-account-management:AssociatePartnerUser": {}, + "partnercentral-account-management:DisassociatePartnerUser": {}, + "payment-cryptography:CreateKey": {}, + "payment-cryptography:DecryptData": {}, + "payment-cryptography:EncryptData": {}, + "payment-cryptography:GenerateCardValidationData": {}, + "payment-cryptography:GenerateMac": {}, + "payment-cryptography:GeneratePinData": {}, + "payment-cryptography:GetParametersForExport": {}, + "payment-cryptography:GetParametersForImport": {}, + "payment-cryptography:ImportKey": {}, + "payment-cryptography:ListAliases": {}, + "payment-cryptography:ListKeys": {}, + "payment-cryptography:ReEncryptData": {}, + "payment-cryptography:TranslatePinData": {}, + "payment-cryptography:VerifyAuthRequestCryptogram": {}, + "payment-cryptography:VerifyCardValidationData": {}, + "payment-cryptography:VerifyMac": {}, + "payment-cryptography:VerifyPinData": {}, + "payments:CreatePaymentInstrument": {}, + "payments:DeletePaymentInstrument": {}, + "payments:GetPaymentInstrument": {}, + "payments:GetPaymentStatus": {}, + "payments:ListPaymentPreferences": {}, + "payments:MakePayment": {}, + "payments:UpdatePaymentPreferences": {}, + "pca-connector-ad:CreateConnector": {}, + "pca-connector-ad:CreateDirectoryRegistration": {}, + "pca-connector-ad:ListConnectors": {}, + "pca-connector-ad:ListDirectoryRegistrations": {}, + "pca-connector-ad:ListTagsForResource": {}, + "personalize:ListBatchInferenceJobs": {}, + "personalize:ListBatchSegmentJobs": {}, + "personalize:ListCampaigns": {}, + "personalize:ListDataInsightsJobs": {}, + "personalize:ListDatasetExportJobs": {}, + "personalize:ListDatasetGroups": {}, + "personalize:ListDatasetImportJobs": {}, + "personalize:ListDatasets": {}, + "personalize:ListEventTrackers": {}, + "personalize:ListFilters": {}, + "personalize:ListMetricAttributionMetrics": {}, + "personalize:ListMetricAttributions": {}, + "personalize:ListRecipes": {}, + "personalize:ListRecommenders": {}, + "personalize:ListSchemas": {}, + "personalize:ListSolutionVersions": {}, + "personalize:ListSolutions": {}, + "personalize:ListTagsForResource": {}, + "personalize:PutActionInteractions": {}, + "personalize:PutEvents": {}, + "personalize:TagResource": {}, + "personalize:UntagResource": {}, + "pipes:ListPipes": {}, + "polly:DescribeVoices": {}, + "polly:GetSpeechSynthesisTask": {}, + "polly:ListLexicons": {}, + "polly:ListSpeechSynthesisTasks": {}, + "pricing:DescribeServices": {}, + "pricing:GetAttributeValues": {}, + "pricing:GetPriceListFileUrl": {}, + "pricing:GetProducts": {}, + "pricing:ListPriceLists": {}, + "private-networks:ListNetworks": {}, + "private-networks:ListTagsForResource": {}, + "private-networks:Ping": {}, + "profile:GetProfileObjectTypeTemplate": {}, + "profile:ListAccountIntegrations": {}, + "profile:ListDomains": {}, + "profile:ListProfileObjectTypeTemplates": {}, + "proton:CreateEnvironmentAccountConnection": {}, + "proton:CreateServiceSyncConfig": {}, + "proton:CreateTemplateSyncConfig": {}, + "proton:DeleteAccountRoles": {}, + "proton:DeleteServiceSyncConfig": {}, + "proton:DeleteTemplateSyncConfig": {}, + "proton:GetAccountRoles": {}, + "proton:GetAccountSettings": {}, + "proton:GetRepositorySyncStatus": {}, + "proton:GetResourceTemplateVersionStatusCounts": {}, + "proton:GetResourcesSummary": {}, + "proton:GetServiceInstanceSyncStatus": {}, + "proton:GetServiceSyncBlockerSummary": {}, + "proton:GetServiceSyncConfig": {}, + "proton:GetTemplateSyncConfig": {}, + "proton:GetTemplateSyncStatus": {}, + "proton:ListDeployments": {}, + "proton:ListEnvironmentAccountConnections": {}, + "proton:ListEnvironmentTemplates": {}, + "proton:ListEnvironments": {}, + "proton:ListRepositories": {}, + "proton:ListRepositorySyncDefinitions": {}, + "proton:ListServiceInstances": {}, + "proton:ListServiceTemplates": {}, + "proton:ListServices": {}, + "proton:UpdateAccountRoles": {}, + "proton:UpdateAccountSettings": {}, + "proton:UpdateServiceSyncBlocker": {}, + "proton:UpdateServiceSyncConfig": {}, + "proton:UpdateTemplateSyncConfig": {}, + "purchase-orders:GetConsoleActionSetEnforced": {}, + "purchase-orders:ListPurchaseOrders": {}, + "purchase-orders:UpdateConsoleActionSetEnforced": {}, + "q:GetConversation": {}, + "q:GetTroubleshootingResults": {}, + "q:SendMessage": {}, + "q:StartConversation": {}, + "q:StartTroubleshootingAnalysis": {}, + "q:StartTroubleshootingResolutionExplanation": {}, + "qbusiness:AddUserLicenses": {}, + "qbusiness:CreateApplication": {}, + "qbusiness:CreateLicense": {}, + "qbusiness:ListApplications": {}, + "qbusiness:ListUserLicenses": {}, + "qbusiness:RemoveUserLicenses": {}, + "qldb:ListJournalS3Exports": {}, + "qldb:ListLedgers": {}, + "quicksight:AccountConfigurations": {}, + "quicksight:CreateAccountCustomization": {}, + "quicksight:CreateAccountSubscription": {}, + "quicksight:CreateCustomPermissions": {}, + "quicksight:CreateDataSource": {}, + "quicksight:CreateRoleMembership": {}, + "quicksight:CreateVPCConnection": {}, + "quicksight:DeleteCustomPermissions": {}, + "quicksight:DeleteIdentityPropagationConfig": {}, + "quicksight:DeleteRoleCustomPermission": {}, + "quicksight:DeleteRoleMembership": {}, + "quicksight:DescribeAccountSettings": {}, + "quicksight:DescribeCustomPermissions": {}, + "quicksight:DescribeIpRestriction": {}, + "quicksight:DescribeRoleCustomPermission": {}, + "quicksight:GetAnonymousUserEmbedUrl": {}, + "quicksight:GetGroupMapping": {}, + "quicksight:GetSessionEmbedUrl": {}, + "quicksight:ListCustomPermissions": {}, + "quicksight:ListCustomerManagedKeys": {}, + "quicksight:ListDataSets": {}, + "quicksight:ListDataSources": {}, + "quicksight:ListIdentityPropagationConfigs": {}, + "quicksight:ListIngestions": {}, + "quicksight:ListKMSKeysForUser": {}, + "quicksight:ListNamespaces": {}, + "quicksight:ListRefreshSchedules": {}, + "quicksight:ListRoleMemberships": {}, + "quicksight:ListTopicRefreshSchedules": {}, + "quicksight:ListTopics": {}, + "quicksight:ListVPCConnections": {}, + "quicksight:RegisterCustomerManagedKey": {}, + "quicksight:RemoveCustomerManagedKey": {}, + "quicksight:ScopeDownPolicy": {}, + "quicksight:SearchDirectoryGroups": {}, + "quicksight:SetGroupMapping": {}, + "quicksight:Subscribe": {}, + "quicksight:Unsubscribe": {}, + "quicksight:UpdateAccountSettings": {}, + "quicksight:UpdateCustomPermissions": {}, + "quicksight:UpdateIdentityPropagationConfig": {}, + "quicksight:UpdateIpRestriction": {}, + "quicksight:UpdatePublicSharingSettings": {}, + "quicksight:UpdateResourcePermissions": {}, + "quicksight:UpdateRoleCustomPermission": {}, + "ram:CreatePermission": {}, + "ram:CreateResourceShare": {}, + "ram:EnableSharingWithAwsOrganization": {}, + "ram:GetResourcePolicies": {}, + "ram:GetResourceShareAssociations": {}, + "ram:GetResourceShareInvitations": {}, + "ram:GetResourceShares": {}, + "ram:ListPermissionVersions": {}, + "ram:ListPermissions": {}, + "ram:ListPrincipals": {}, + "ram:ListReplacePermissionAssociationsWork": {}, + "ram:ListResourceTypes": {}, + "ram:ListResources": {}, + "rbin:ListRules": {}, + "rds:CancelExportTask": {}, + "rds:CreateDBProxy": {}, + "rds:CrossRegionCommunication": {}, + "rds:DescribeAccountAttributes": {}, + "rds:DescribeCertificates": {}, + "rds:DescribeDBEngineVersions": {}, + "rds:DescribeDBRecommendations": {}, + "rds:DescribeEngineDefaultClusterParameters": {}, + "rds:DescribeEngineDefaultParameters": {}, + "rds:DescribeEventCategories": {}, + "rds:DescribeEvents": {}, + "rds:DescribeExportTasks": {}, + "rds:DescribeOrderableDBInstanceOptions": {}, + "rds:DescribeRecommendationGroups": {}, + "rds:DescribeRecommendations": {}, + "rds:DescribeReservedDBInstancesOfferings": {}, + "rds:DescribeSourceRegions": {}, + "rds:ModifyCertificates": {}, + "rds:ModifyDBRecommendation": {}, + "rds:ModifyRecommendation": {}, + "rds:StartExportTask": {}, + "redshift-data:CancelStatement": {}, + "redshift-data:DescribeStatement": {}, + "redshift-data:GetStatementResult": {}, + "redshift-data:ListStatements": {}, + "redshift-serverless:CreateUsageLimit": {}, + "redshift-serverless:DeleteResourcePolicy": {}, + "redshift-serverless:DeleteScheduledAction": {}, + "redshift-serverless:DeleteSnapshotCopyConfiguration": {}, + "redshift-serverless:DeleteUsageLimit": {}, + "redshift-serverless:GetResourcePolicy": {}, + "redshift-serverless:GetScheduledAction": {}, + "redshift-serverless:GetTableRestoreStatus": {}, + "redshift-serverless:GetUsageLimit": {}, + "redshift-serverless:ListCustomDomainAssociations": {}, + "redshift-serverless:ListNamespaces": {}, + "redshift-serverless:ListScheduledActions": {}, + "redshift-serverless:ListTableRestoreStatus": {}, + "redshift-serverless:ListUsageLimits": {}, + "redshift-serverless:ListWorkgroups": {}, + "redshift-serverless:PutResourcePolicy": {}, + "redshift-serverless:UpdateScheduledAction": {}, + "redshift-serverless:UpdateSnapshotCopyConfiguration": {}, + "redshift-serverless:UpdateUsageLimit": {}, + "redshift-serverless:span": {}, + "redshift:AcceptReservedNodeExchange": {}, + "redshift:AddPartner": {}, + "redshift:AuthorizeEndpointAccess": {}, + "redshift:CancelQuery": {}, + "redshift:CancelQuerySession": {}, + "redshift:CreateAuthenticationProfile": {}, + "redshift:CreateEndpointAccess": {}, + "redshift:CreateRedshiftIdcApplication": {}, + "redshift:CreateSavedQuery": {}, + "redshift:CreateScheduledAction": {}, + "redshift:DeleteAuthenticationProfile": {}, + "redshift:DeleteEndpointAccess": {}, + "redshift:DeletePartner": {}, + "redshift:DeleteSavedQueries": {}, + "redshift:DeleteScheduledAction": {}, + "redshift:DescribeAccountAttributes": {}, + "redshift:DescribeAuthenticationProfiles": {}, + "redshift:DescribeClusterDbRevisions": {}, + "redshift:DescribeClusterParameterGroups": {}, + "redshift:DescribeClusterSecurityGroups": {}, + "redshift:DescribeClusterSnapshots": {}, + "redshift:DescribeClusterSubnetGroups": {}, + "redshift:DescribeClusterTracks": {}, + "redshift:DescribeClusterVersions": {}, + "redshift:DescribeClusters": {}, + "redshift:DescribeCustomDomainAssociations": {}, + "redshift:DescribeDataShares": {}, + "redshift:DescribeDataSharesForConsumer": {}, + "redshift:DescribeDataSharesForProducer": {}, + "redshift:DescribeDefaultClusterParameters": {}, + "redshift:DescribeEndpointAccess": {}, + "redshift:DescribeEndpointAuthorization": {}, + "redshift:DescribeEventCategories": {}, + "redshift:DescribeEventSubscriptions": {}, + "redshift:DescribeEvents": {}, + "redshift:DescribeHsmClientCertificates": {}, + "redshift:DescribeHsmConfigurations": {}, + "redshift:DescribeInboundIntegrations": {}, + "redshift:DescribeNodeConfigurationOptions": {}, + "redshift:DescribeOrderableClusterOptions": {}, + "redshift:DescribePartners": {}, + "redshift:DescribeQuery": {}, + "redshift:DescribeReservedNodeExchangeStatus": {}, + "redshift:DescribeReservedNodeOfferings": {}, + "redshift:DescribeReservedNodes": {}, + "redshift:DescribeSavedQueries": {}, + "redshift:DescribeScheduledActions": {}, + "redshift:DescribeSnapshotCopyGrants": {}, + "redshift:DescribeStorage": {}, + "redshift:DescribeTable": {}, + "redshift:DescribeTableRestoreStatus": {}, + "redshift:ExecuteQuery": {}, + "redshift:FetchResults": {}, + "redshift:GetReservedNodeExchangeConfigurationOptions": {}, + "redshift:GetReservedNodeExchangeOfferings": {}, + "redshift:ListDatabases": {}, + "redshift:ListRecommendations": {}, + "redshift:ListSavedQueries": {}, + "redshift:ListSchemas": {}, + "redshift:ListTables": {}, + "redshift:ModifyAuthenticationProfile": {}, + "redshift:ModifyClusterMaintenance": {}, + "redshift:ModifyEndpointAccess": {}, + "redshift:ModifySavedQuery": {}, + "redshift:ModifyScheduledAction": {}, + "redshift:PurchaseReservedNodeOffering": {}, + "redshift:RevokeEndpointAccess": {}, + "redshift:UpdatePartnerStatus": {}, + "redshift:ViewQueriesFromConsole": {}, + "redshift:ViewQueriesInConsole": {}, + "refactor-spaces:CreateApplication": {}, + "refactor-spaces:CreateEnvironment": {}, + "refactor-spaces:CreateRoute": {}, + "refactor-spaces:CreateService": {}, + "refactor-spaces:DeleteResourcePolicy": {}, + "refactor-spaces:GetResourcePolicy": {}, + "refactor-spaces:ListEnvironments": {}, + "refactor-spaces:ListTagsForResource": {}, + "refactor-spaces:PutResourcePolicy": {}, + "rekognition:CompareFaces": {}, + "rekognition:CreateFaceLivenessSession": {}, + "rekognition:DescribeProjects": {}, + "rekognition:DetectFaces": {}, + "rekognition:DetectLabels": {}, + "rekognition:DetectProtectiveEquipment": {}, + "rekognition:DetectText": {}, + "rekognition:GetCelebrityInfo": {}, + "rekognition:GetCelebrityRecognition": {}, + "rekognition:GetContentModeration": {}, + "rekognition:GetFaceDetection": {}, + "rekognition:GetFaceLivenessSessionResults": {}, + "rekognition:GetFaceSearch": {}, + "rekognition:GetLabelDetection": {}, + "rekognition:GetMediaAnalysisJob": {}, + "rekognition:GetPersonTracking": {}, + "rekognition:GetSegmentDetection": {}, + "rekognition:GetTextDetection": {}, + "rekognition:ListCollections": {}, + "rekognition:ListMediaAnalysisJobs": {}, + "rekognition:RecognizeCelebrities": {}, + "rekognition:StartCelebrityRecognition": {}, + "rekognition:StartContentModeration": {}, + "rekognition:StartFaceDetection": {}, + "rekognition:StartFaceLivenessSession": {}, + "rekognition:StartLabelDetection": {}, + "rekognition:StartPersonTracking": {}, + "rekognition:StartSegmentDetection": {}, + "rekognition:StartTextDetection": {}, + "repostspace:CreateSpace": {}, + "repostspace:ListSpaces": {}, + "resiliencehub:CreateApp": {}, + "resiliencehub:CreateResiliencyPolicy": {}, + "resiliencehub:ListAppAssessments": {}, + "resiliencehub:ListApps": {}, + "resiliencehub:ListResiliencyPolicies": {}, + "resiliencehub:ListSuggestedResiliencyPolicies": {}, + "resiliencehub:ListTagsForResource": {}, + "resource-explorer-2:BatchGetView": {}, + "resource-explorer-2:CreateIndex": {}, + "resource-explorer-2:CreateView": {}, + "resource-explorer-2:DisassociateDefaultView": {}, + "resource-explorer-2:GetAccountLevelServiceConfiguration": {}, + "resource-explorer-2:GetDefaultView": {}, + "resource-explorer-2:GetIndex": {}, + "resource-explorer-2:ListIndexes": {}, + "resource-explorer-2:ListIndexesForMembers": {}, + "resource-explorer-2:ListSupportedResourceTypes": {}, + "resource-explorer-2:ListViews": {}, + "resource-explorer:ListResourceTypes": {}, + "resource-explorer:ListResources": {}, + "resource-explorer:ListTags": {}, + "resource-groups:CreateGroup": {}, + "resource-groups:GetAccountSettings": {}, + "resource-groups:ListGroups": {}, + "resource-groups:SearchResources": {}, + "resource-groups:UpdateAccountSettings": {}, + "rhelkb:GetRhelURL": {}, + "robomaker:BatchDeleteWorlds": {}, + "robomaker:BatchDescribeSimulationJob": {}, + "robomaker:CreateDeploymentJob": {}, + "robomaker:CreateFleet": {}, + "robomaker:CreateRobot": {}, + "robomaker:CreateRobotApplication": {}, + "robomaker:CreateSimulationApplication": {}, + "robomaker:CreateSimulationJob": {}, + "robomaker:CreateWorldTemplate": {}, + "robomaker:ListDeploymentJobs": {}, + "robomaker:ListFleets": {}, + "robomaker:ListRobotApplications": {}, + "robomaker:ListRobots": {}, + "robomaker:ListSimulationApplications": {}, + "robomaker:ListSimulationJobBatches": {}, + "robomaker:ListSimulationJobs": {}, + "robomaker:ListWorldExportJobs": {}, + "robomaker:ListWorldGenerationJobs": {}, + "robomaker:ListWorldTemplates": {}, + "robomaker:ListWorlds": {}, + "robomaker:StartSimulationJobBatch": {}, + "rolesanywhere:CreateProfile": {}, + "rolesanywhere:CreateTrustAnchor": {}, + "rolesanywhere:ImportCrl": {}, + "rolesanywhere:ListCrls": {}, + "rolesanywhere:ListProfiles": {}, + "rolesanywhere:ListSubjects": {}, + "rolesanywhere:ListTagsForResource": {}, + "rolesanywhere:ListTrustAnchors": {}, + "route53-recovery-cluster:ListRoutingControls": {}, + "route53-recovery-control-config:ListAssociatedRoute53HealthChecks": {}, + "route53-recovery-control-config:ListClusters": {}, + "route53-recovery-control-config:ListControlPanels": {}, + "route53-recovery-control-config:ListRoutingControls": {}, + "route53-recovery-control-config:ListTagsForResource": {}, + "route53-recovery-readiness:CreateCrossAccountAuthorization": {}, + "route53-recovery-readiness:DeleteCrossAccountAuthorization": {}, + "route53-recovery-readiness:ListCells": {}, + "route53-recovery-readiness:ListCrossAccountAuthorizations": {}, + "route53-recovery-readiness:ListReadinessChecks": {}, + "route53-recovery-readiness:ListRecoveryGroups": {}, + "route53-recovery-readiness:ListResourceSets": {}, + "route53-recovery-readiness:ListRules": {}, + "route53-recovery-readiness:ListTagsForResources": {}, + "route53:CreateCidrCollection": {}, + "route53:CreateHealthCheck": {}, + "route53:CreateHostedZone": {}, + "route53:CreateReusableDelegationSet": {}, + "route53:CreateTrafficPolicy": {}, + "route53:GetAccountLimit": {}, + "route53:GetCheckerIpRanges": {}, + "route53:GetGeoLocation": {}, + "route53:GetHealthCheckCount": {}, + "route53:GetHostedZoneCount": {}, + "route53:GetTrafficPolicyInstanceCount": {}, + "route53:ListCidrCollections": {}, + "route53:ListGeoLocations": {}, + "route53:ListHealthChecks": {}, + "route53:ListHostedZones": {}, + "route53:ListHostedZonesByName": {}, + "route53:ListHostedZonesByVPC": {}, + "route53:ListReusableDelegationSets": {}, + "route53:ListTrafficPolicies": {}, + "route53:ListTrafficPolicyInstances": {}, + "route53:TestDNSAnswer": {}, + "route53domains:AcceptDomainTransferFromAnotherAwsAccount": {}, + "route53domains:AssociateDelegationSignerToDomain": {}, + "route53domains:CancelDomainTransferToAnotherAwsAccount": {}, + "route53domains:CheckDomainAvailability": {}, + "route53domains:CheckDomainTransferability": {}, + "route53domains:DeleteDomain": {}, + "route53domains:DeleteTagsForDomain": {}, + "route53domains:DisableDomainAutoRenew": {}, + "route53domains:DisableDomainTransferLock": {}, + "route53domains:DisassociateDelegationSignerFromDomain": {}, + "route53domains:EnableDomainAutoRenew": {}, + "route53domains:EnableDomainTransferLock": {}, + "route53domains:GetContactReachabilityStatus": {}, + "route53domains:GetDomainDetail": {}, + "route53domains:GetDomainSuggestions": {}, + "route53domains:GetOperationDetail": {}, + "route53domains:ListDomains": {}, + "route53domains:ListOperations": {}, + "route53domains:ListPrices": {}, + "route53domains:ListTagsForDomain": {}, + "route53domains:PushDomain": {}, + "route53domains:RegisterDomain": {}, + "route53domains:RejectDomainTransferFromAnotherAwsAccount": {}, + "route53domains:RenewDomain": {}, + "route53domains:ResendContactReachabilityEmail": {}, + "route53domains:ResendOperationAuthorization": {}, + "route53domains:RetrieveDomainAuthCode": {}, + "route53domains:TransferDomain": {}, + "route53domains:TransferDomainToAnotherAwsAccount": {}, + "route53domains:UpdateDomainContact": {}, + "route53domains:UpdateDomainContactPrivacy": {}, + "route53domains:UpdateDomainNameservers": {}, + "route53domains:UpdateTagsForDomain": {}, + "route53domains:ViewBilling": {}, + "route53resolver:CreateResolverQueryLogConfig": {}, + "route53resolver:GetResolverQueryLogConfigAssociation": {}, + "route53resolver:ListFirewallConfigs": {}, + "route53resolver:ListFirewallDomainLists": {}, + "route53resolver:ListFirewallRuleGroupAssociations": {}, + "route53resolver:ListFirewallRuleGroups": {}, + "route53resolver:ListOutpostResolvers": {}, + "route53resolver:ListResolverEndpoints": {}, + "route53resolver:ListResolverQueryLogConfigAssociations": {}, + "route53resolver:ListResolverQueryLogConfigs": {}, + "route53resolver:ListResolverRuleAssociations": {}, + "route53resolver:ListResolverRules": {}, + "rum:ListAppMonitors": {}, + "rum:ListTagsForResource": {}, + "s3-outposts:GetAccessPoint": {}, + "s3-outposts:ListAccessPoints": {}, + "s3-outposts:ListEndpoints": {}, + "s3-outposts:ListOutpostsWithS3": {}, + "s3-outposts:ListRegionalBuckets": {}, + "s3-outposts:ListSharedEndpoints": {}, + "s3:CreateJob": {}, + "s3:CreateStorageLensGroup": {}, + "s3:GetAccessPoint": {}, + "s3:GetAccountPublicAccessBlock": {}, + "s3:ListAccessGrantsInstances": {}, + "s3:ListAccessPoints": {}, + "s3:ListAccessPointsForObjectLambda": {}, + "s3:ListAllMyBuckets": {}, + "s3:ListJobs": {}, + "s3:ListMultiRegionAccessPoints": {}, + "s3:ListStorageLensConfigurations": {}, + "s3:ListStorageLensGroups": {}, + "s3:PutAccessPointPublicAccessBlock": {}, + "s3:PutAccountPublicAccessBlock": {}, + "s3:PutStorageLensConfiguration": {}, + "s3express:ListAllMyDirectoryBuckets": {}, + "sagemaker-geospatial:ListEarthObservationJobs": {}, + "sagemaker-geospatial:ListRasterDataCollections": {}, + "sagemaker-geospatial:ListVectorEnrichmentJobs": {}, + "sagemaker-geospatial:SearchRasterDataCollection": {}, + "sagemaker-groundtruth-synthetic:CreateProject": {}, + "sagemaker-groundtruth-synthetic:DeleteProject": {}, + "sagemaker-groundtruth-synthetic:GetAccountDetails": {}, + "sagemaker-groundtruth-synthetic:GetBatch": {}, + "sagemaker-groundtruth-synthetic:GetProject": {}, + "sagemaker-groundtruth-synthetic:ListBatchDataTransfers": {}, + "sagemaker-groundtruth-synthetic:ListBatchSummaries": {}, + "sagemaker-groundtruth-synthetic:ListProjectDataTransfers": {}, + "sagemaker-groundtruth-synthetic:ListProjectSummaries": {}, + "sagemaker-groundtruth-synthetic:StartBatchDataTransfer": {}, + "sagemaker-groundtruth-synthetic:StartProjectDataTransfer": {}, + "sagemaker-groundtruth-synthetic:UpdateBatch": {}, + "sagemaker:CreateLineageGroupPolicy": {}, + "sagemaker:DeleteLineageGroupPolicy": {}, + "sagemaker:DescribeLineageGroup": {}, + "sagemaker:DisableSagemakerServicecatalogPortfolio": {}, + "sagemaker:EnableSagemakerServicecatalogPortfolio": {}, + "sagemaker:GetLineageGroupPolicy": {}, + "sagemaker:GetSagemakerServicecatalogPortfolioStatus": {}, + "sagemaker:GetSearchSuggestions": {}, + "sagemaker:ListActions": {}, + "sagemaker:ListAlgorithms": {}, + "sagemaker:ListAppImageConfigs": {}, + "sagemaker:ListApps": {}, + "sagemaker:ListArtifacts": {}, + "sagemaker:ListAssociations": {}, + "sagemaker:ListAutoMLJobs": {}, + "sagemaker:ListCandidatesForAutoMLJob": {}, + "sagemaker:ListClusters": {}, + "sagemaker:ListCodeRepositories": {}, + "sagemaker:ListCompilationJobs": {}, + "sagemaker:ListContexts": {}, + "sagemaker:ListDataQualityJobDefinitions": {}, + "sagemaker:ListDeviceFleets": {}, + "sagemaker:ListDevices": {}, + "sagemaker:ListDomains": {}, + "sagemaker:ListEdgeDeploymentPlans": {}, + "sagemaker:ListEdgePackagingJobs": {}, + "sagemaker:ListEndpointConfigs": {}, + "sagemaker:ListEndpoints": {}, + "sagemaker:ListExperiments": {}, + "sagemaker:ListFeatureGroups": {}, + "sagemaker:ListFlowDefinitions": {}, + "sagemaker:ListHubs": {}, + "sagemaker:ListHumanLoops": {}, + "sagemaker:ListHumanTaskUis": {}, + "sagemaker:ListHyperParameterTuningJobs": {}, + "sagemaker:ListImages": {}, + "sagemaker:ListInferenceComponents": {}, + "sagemaker:ListInferenceExperiments": {}, + "sagemaker:ListInferenceRecommendationsJobSteps": {}, + "sagemaker:ListInferenceRecommendationsJobs": {}, + "sagemaker:ListLabelingJobs": {}, + "sagemaker:ListLineageGroups": {}, + "sagemaker:ListModelBiasJobDefinitions": {}, + "sagemaker:ListModelCards": {}, + "sagemaker:ListModelExplainabilityJobDefinitions": {}, + "sagemaker:ListModelMetadata": {}, + "sagemaker:ListModelPackageGroups": {}, + "sagemaker:ListModelQualityJobDefinitions": {}, + "sagemaker:ListModels": {}, + "sagemaker:ListMonitoringAlertHistory": {}, + "sagemaker:ListMonitoringAlerts": {}, + "sagemaker:ListMonitoringExecutions": {}, + "sagemaker:ListMonitoringSchedules": {}, + "sagemaker:ListNotebookInstanceLifecycleConfigs": {}, + "sagemaker:ListNotebookInstances": {}, + "sagemaker:ListPipelines": {}, + "sagemaker:ListProcessingJobs": {}, + "sagemaker:ListProjects": {}, + "sagemaker:ListResourceCatalogs": {}, + "sagemaker:ListSharedModelEvents": {}, + "sagemaker:ListSharedModels": {}, + "sagemaker:ListSpaces": {}, + "sagemaker:ListStageDevices": {}, + "sagemaker:ListStudioLifecycleConfigs": {}, + "sagemaker:ListSubscribedWorkteams": {}, + "sagemaker:ListTrainingJobs": {}, + "sagemaker:ListTransformJobs": {}, + "sagemaker:ListTrialComponents": {}, + "sagemaker:ListTrials": {}, + "sagemaker:ListUserProfiles": {}, + "sagemaker:ListWorkforces": {}, + "sagemaker:ListWorkteams": {}, + "sagemaker:PutLineageGroupPolicy": {}, + "sagemaker:QueryLineage": {}, + "sagemaker:RenderUiTemplate": {}, + "sagemaker:Search": {}, + "savingsplans:CreateSavingsPlan": {}, + "savingsplans:DescribeSavingsPlansOfferingRates": {}, + "savingsplans:DescribeSavingsPlansOfferings": {}, + "scheduler:ListScheduleGroups": {}, + "scheduler:ListSchedules": {}, + "schemas:CreateDiscoverer": {}, + "schemas:GetDiscoveredSchema": {}, + "sdb:ListDomains": {}, + "secretsmanager:BatchGetSecretValue": {}, + "secretsmanager:GetRandomPassword": {}, + "secretsmanager:ListSecrets": {}, + "securityhub:BatchGetConfigurationPolicyAssociations": {}, + "securityhub:BatchGetSecurityControls": {}, + "securityhub:BatchGetStandardsControlAssociations": {}, + "securityhub:BatchUpdateStandardsControlAssociations": {}, + "securityhub:CreateAutomationRule": {}, + "securityhub:CreateConfigurationPolicy": {}, + "securityhub:CreateFindingAggregator": {}, + "securityhub:GetConfigurationPolicyAssociation": {}, + "securityhub:GetSecurityControlDefinition": {}, + "securityhub:ListAutomationRules": {}, + "securityhub:ListConfigurationPolicies": {}, + "securityhub:ListConfigurationPolicyAssociations": {}, + "securityhub:ListFindingAggregators": {}, + "securityhub:ListSecurityControlDefinitions": {}, + "securityhub:ListStandardsControlAssociations": {}, + "securityhub:UpdateSecurityControl": {}, + "securitylake:CreateDataLakeExceptionSubscription": {}, + "securitylake:CreateSubscriber": {}, + "securitylake:DeleteDataLakeExceptionSubscription": {}, + "securitylake:DeregisterDataLakeDelegatedAdministrator": {}, + "securitylake:GetDataLakeExceptionSubscription": {}, + "securitylake:ListDataLakeExceptions": {}, + "securitylake:ListDataLakes": {}, + "securitylake:ListLogSources": {}, + "securitylake:ListSubscribers": {}, + "securitylake:RegisterDataLakeDelegatedAdministrator": {}, + "securitylake:UpdateDataLakeExceptionSubscription": {}, + "serverlessrepo:CreateApplication": {}, + "serverlessrepo:ListApplications": {}, + "serverlessrepo:SearchApplications": {}, + "servicecatalog:AssociateBudgetWithResource": {}, + "servicecatalog:AssociateProductWithPortfolio": {}, + "servicecatalog:BatchAssociateServiceActionWithProvisioningArtifact": {}, "servicecatalog:BatchDisassociateServiceActionFromProvisioningArtifact": {}, - "servicecatalog:CopyProduct": {}, - "servicecatalog:CreateProvisionedProductPlan": {}, - "servicecatalog:CreateServiceAction": {}, - "servicecatalog:CreateTagOption": {}, - "servicecatalog:DeleteConstraint": {}, - "servicecatalog:DeleteProvisionedProductPlan": {}, - "servicecatalog:DeleteServiceAction": {}, - "servicecatalog:DeleteTagOption": {}, - "servicecatalog:DescribeConstraint": {}, - "servicecatalog:DescribeCopyProductStatus": {}, - "servicecatalog:DescribePortfolioShareStatus": {}, - "servicecatalog:DescribeProductView": {}, - "servicecatalog:DescribeProvisionedProduct": {}, - "servicecatalog:DescribeProvisionedProductPlan": {}, - "servicecatalog:DescribeRecord": {}, - "servicecatalog:DescribeServiceAction": {}, - "servicecatalog:DescribeServiceActionExecutionParameters": {}, - "servicecatalog:DescribeTagOption": {}, - "servicecatalog:DisableAWSOrganizationsAccess": {}, - "servicecatalog:DisassociateBudgetFromResource": {}, - "servicecatalog:DisassociateProductFromPortfolio": {}, - "servicecatalog:EnableAWSOrganizationsAccess": {}, - "servicecatalog:ExecuteProvisionedProductPlan": {}, - "servicecatalog:ExecuteProvisionedProductServiceAction": {}, - "servicecatalog:GetAWSOrganizationsAccessStatus": {}, - "servicecatalog:GetConfiguration": {}, - "servicecatalog:GetProvisionedProductOutputs": {}, - "servicecatalog:ListAcceptedPortfolioShares": {}, - "servicecatalog:ListApplications": {}, - "servicecatalog:ListAttributeGroups": {}, - "servicecatalog:ListBudgetsForResource": {}, - "servicecatalog:ListConstraintsForPortfolio": {}, - "servicecatalog:ListOrganizationPortfolioAccess": {}, - "servicecatalog:ListPortfolios": {}, - "servicecatalog:ListProvisionedProductPlans": {}, - "servicecatalog:ListProvisioningArtifactsForServiceAction": {}, - "servicecatalog:ListRecordHistory": {}, - "servicecatalog:ListResourcesForTagOption": {}, - "servicecatalog:ListServiceActions": {}, - "servicecatalog:ListStackInstancesForProvisionedProduct": {}, - "servicecatalog:ListTagOptions": {}, - "servicecatalog:NotifyProvisionProductEngineWorkflowResult": {}, - "servicecatalog:NotifyTerminateProvisionedProductEngineWorkflowResult": {}, - "servicecatalog:NotifyUpdateProvisionedProductEngineWorkflowResult": {}, - "servicecatalog:PutConfiguration": {}, - "servicecatalog:ScanProvisionedProducts": {}, - "servicecatalog:SearchProducts": {}, - "servicecatalog:SearchProductsAsAdmin": {}, - "servicecatalog:SearchProvisionedProducts": {}, - "servicecatalog:SyncResource": {}, - "servicecatalog:TerminateProvisionedProduct": {}, - "servicecatalog:UpdateConstraint": {}, - "servicecatalog:UpdateProvisionedProduct": {}, - "servicecatalog:UpdateProvisionedProductProperties": {}, - "servicecatalog:UpdateServiceAction": {}, - "servicecatalog:UpdateTagOption": {}, - "servicediscovery:CreateHttpNamespace": {}, - "servicediscovery:CreatePrivateDnsNamespace": {}, - "servicediscovery:CreatePublicDnsNamespace": {}, - "servicediscovery:DiscoverInstances": {}, - "servicediscovery:DiscoverInstancesRevision": {}, - "servicediscovery:GetInstance": {}, - "servicediscovery:GetInstancesHealthStatus": {}, - "servicediscovery:GetOperation": {}, - "servicediscovery:ListInstances": {}, - "servicediscovery:ListNamespaces": {}, - "servicediscovery:ListOperations": {}, - "servicediscovery:ListServices": {}, - "servicediscovery:ListTagsForResource": {}, - "servicediscovery:TagResource": {}, - "servicediscovery:UntagResource": {}, - "servicediscovery:UpdateInstanceCustomHealthStatus": {}, - "serviceextract:GetConfig": {}, - "servicequotas:AssociateServiceQuotaTemplate": {}, - "servicequotas:DeleteServiceQuotaIncreaseRequestFromTemplate": {}, - "servicequotas:DisassociateServiceQuotaTemplate": {}, - "servicequotas:GetAWSDefaultServiceQuota": {}, - "servicequotas:GetAssociationForServiceQuotaTemplate": {}, - "servicequotas:GetRequestedServiceQuotaChange": {}, - "servicequotas:GetServiceQuota": {}, - "servicequotas:GetServiceQuotaIncreaseRequestFromTemplate": {}, - "servicequotas:ListAWSDefaultServiceQuotas": {}, - "servicequotas:ListRequestedServiceQuotaChangeHistory": {}, - "servicequotas:ListRequestedServiceQuotaChangeHistoryByQuota": {}, - "servicequotas:ListServiceQuotaIncreaseRequestsInTemplate": {}, - "servicequotas:ListServiceQuotas": {}, - "servicequotas:ListServices": {}, - "servicequotas:ListTagsForResource": {}, - "servicequotas:TagResource": {}, - "servicequotas:UntagResource": {}, - "ses:CloneReceiptRuleSet": {}, - "ses:CreateConfigurationSet": {}, - "ses:CreateConfigurationSetEventDestination": {}, - "ses:CreateConfigurationSetTrackingOptions": {}, - "ses:CreateCustomVerificationEmailTemplate": {}, - "ses:CreateDedicatedIpPool": {}, - "ses:CreateEmailIdentity": {}, - "ses:CreateExportJob": {}, - "ses:CreateImportJob": {}, - "ses:CreateReceiptFilter": {}, - "ses:CreateReceiptRule": {}, - "ses:CreateReceiptRuleSet": {}, - "ses:CreateTemplate": {}, - "ses:DeleteConfigurationSet": {}, - "ses:DeleteConfigurationSetEventDestination": {}, - "ses:DeleteConfigurationSetTrackingOptions": {}, - "ses:DeleteCustomVerificationEmailTemplate": {}, - "ses:DeleteIdentity": {}, - "ses:DeleteIdentityPolicy": {}, - "ses:DeleteReceiptFilter": {}, - "ses:DeleteReceiptRule": {}, - "ses:DeleteReceiptRuleSet": {}, - "ses:DeleteSuppressedDestination": {}, - "ses:DeleteTemplate": {}, - "ses:DeleteVerifiedEmailAddress": {}, - "ses:DescribeActiveReceiptRuleSet": {}, - "ses:DescribeConfigurationSet": {}, - "ses:DescribeReceiptRule": {}, - "ses:DescribeReceiptRuleSet": {}, - "ses:GetAccount": {}, - "ses:GetAccountSendingEnabled": {}, - "ses:GetBlacklistReports": {}, - "ses:GetCustomVerificationEmailTemplate": {}, - "ses:GetDedicatedIp": {}, - "ses:GetDeliverabilityDashboardOptions": {}, - "ses:GetDomainDeliverabilityCampaign": {}, - "ses:GetIdentityDkimAttributes": {}, - "ses:GetIdentityMailFromDomainAttributes": {}, - "ses:GetIdentityNotificationAttributes": {}, - "ses:GetIdentityPolicies": {}, - "ses:GetIdentityVerificationAttributes": {}, - "ses:GetMessageInsights": {}, - "ses:GetSendQuota": {}, - "ses:GetSendStatistics": {}, - "ses:GetSuppressedDestination": {}, - "ses:GetTemplate": {}, - "ses:ListConfigurationSets": {}, - "ses:ListContactLists": {}, - "ses:ListCustomVerificationEmailTemplates": {}, - "ses:ListDedicatedIpPools": {}, - "ses:ListDeliverabilityTestReports": {}, - "ses:ListDomainDeliverabilityCampaigns": {}, - "ses:ListEmailIdentities": {}, - "ses:ListEmailTemplates": {}, - "ses:ListExportJobs": {}, - "ses:ListIdentities": {}, - "ses:ListIdentityPolicies": {}, - "ses:ListImportJobs": {}, - "ses:ListReceiptFilters": {}, - "ses:ListReceiptRuleSets": {}, - "ses:ListSuppressedDestinations": {}, - "ses:ListTemplates": {}, - "ses:ListVerifiedEmailAddresses": {}, - "ses:PutAccountDedicatedIpWarmupAttributes": {}, - "ses:PutAccountDetails": {}, - "ses:PutAccountSendingAttributes": {}, - "ses:PutAccountSuppressionAttributes": {}, - "ses:PutAccountVdmAttributes": {}, - "ses:PutConfigurationSetDeliveryOptions": {}, - "ses:PutDedicatedIpWarmupAttributes": {}, - "ses:PutDeliverabilityDashboardOption": {}, - "ses:PutIdentityPolicy": {}, - "ses:PutSuppressedDestination": {}, - "ses:ReorderReceiptRuleSet": {}, - "ses:SetActiveReceiptRuleSet": {}, - "ses:SetIdentityDkimEnabled": {}, - "ses:SetIdentityFeedbackForwardingEnabled": {}, - "ses:SetIdentityHeadersInNotificationsEnabled": {}, - "ses:SetIdentityMailFromDomain": {}, - "ses:SetIdentityNotificationTopic": {}, - "ses:SetReceiptRulePosition": {}, - "ses:TestRenderTemplate": {}, - "ses:UpdateAccountSendingEnabled": {}, - "ses:UpdateConfigurationSetEventDestination": {}, - "ses:UpdateConfigurationSetReputationMetricsEnabled": {}, - "ses:UpdateConfigurationSetSendingEnabled": {}, - "ses:UpdateConfigurationSetTrackingOptions": {}, - "ses:UpdateCustomVerificationEmailTemplate": {}, - "ses:UpdateReceiptRule": {}, - "ses:UpdateTemplate": {}, - "ses:VerifyDomainDkim": {}, - "ses:VerifyDomainIdentity": {}, - "ses:VerifyEmailAddress": {}, - "ses:VerifyEmailIdentity": {}, - "shield:AssociateDRTLogBucket": {}, - "shield:AssociateDRTRole": {}, - "shield:AssociateProactiveEngagementDetails": {}, - "shield:CreateProtection": {}, - "shield:CreateProtectionGroup": {}, - "shield:CreateSubscription": {}, - "shield:DeleteSubscription": {}, - "shield:DescribeAttackStatistics": {}, - "shield:DescribeDRTAccess": {}, - "shield:DescribeEmergencyContactSettings": {}, - "shield:DescribeSubscription": {}, - "shield:DisableApplicationLayerAutomaticResponse": {}, - "shield:DisableProactiveEngagement": {}, - "shield:DisassociateDRTLogBucket": {}, - "shield:DisassociateDRTRole": {}, - "shield:EnableApplicationLayerAutomaticResponse": {}, - "shield:EnableProactiveEngagement": {}, - "shield:GetSubscriptionState": {}, - "shield:ListAttacks": {}, - "shield:ListProtectionGroups": {}, - "shield:ListProtections": {}, - "shield:UpdateApplicationLayerAutomaticResponse": {}, - "shield:UpdateEmergencyContactSettings": {}, - "shield:UpdateSubscription": {}, - "signer:GetSigningPlatform": {}, - "signer:ListSigningJobs": {}, - "signer:ListSigningPlatforms": {}, - "signer:ListSigningProfiles": {}, - "signer:PutSigningProfile": {}, - "simspaceweaver:ListSimulations": {}, - "simspaceweaver:ListTagsForResource": {}, - "simspaceweaver:StartSimulation": {}, - "sms-voice:CreateConfigurationSet": {}, - "sms-voice:CreateConfigurationSetEventDestination": {}, - "sms-voice:CreateOptOutList": {}, - "sms-voice:CreateRegistration": {}, - "sms-voice:CreateRegistrationAttachment": {}, - "sms-voice:CreateVerifiedDestinationNumber": {}, - "sms-voice:DeleteConfigurationSet": {}, - "sms-voice:DeleteConfigurationSetEventDestination": {}, - "sms-voice:DeleteTextMessageSpendLimitOverride": {}, - "sms-voice:DeleteVoiceMessageSpendLimitOverride": {}, - "sms-voice:DescribeAccountAttributes": {}, - "sms-voice:DescribeAccountLimits": {}, - "sms-voice:DescribeRegistrationFieldDefinitions": {}, - "sms-voice:DescribeRegistrationSectionDefinitions": {}, - "sms-voice:DescribeRegistrationTypeDefinitions": {}, - "sms-voice:DescribeSpendLimits": {}, - "sms-voice:GetConfigurationSetEventDestinations": {}, - "sms-voice:ListConfigurationSets": {}, - "sms-voice:RequestSenderId": {}, - "sms-voice:SendVoiceMessage": {}, - "sms-voice:SetTextMessageSpendLimitOverride": {}, - "sms-voice:SetVoiceMessageSpendLimitOverride": {}, - "sms-voice:UpdateConfigurationSetEventDestination": {}, - "sms:CreateApp": {}, - "sms:CreateReplicationJob": {}, - "sms:DeleteApp": {}, - "sms:DeleteAppLaunchConfiguration": {}, - "sms:DeleteAppReplicationConfiguration": {}, - "sms:DeleteAppValidationConfiguration": {}, - "sms:DeleteReplicationJob": {}, - "sms:DeleteServerCatalog": {}, - "sms:DisassociateConnector": {}, - "sms:GenerateChangeSet": {}, - "sms:GenerateTemplate": {}, - "sms:GetApp": {}, - "sms:GetAppLaunchConfiguration": {}, - "sms:GetAppReplicationConfiguration": {}, - "sms:GetAppValidationConfiguration": {}, - "sms:GetAppValidationOutput": {}, - "sms:GetConnectors": {}, - "sms:GetReplicationJobs": {}, - "sms:GetReplicationRuns": {}, - "sms:GetServers": {}, - "sms:ImportAppCatalog": {}, - "sms:ImportServerCatalog": {}, - "sms:LaunchApp": {}, - "sms:ListApps": {}, - "sms:NotifyAppValidationOutput": {}, - "sms:PutAppLaunchConfiguration": {}, - "sms:PutAppReplicationConfiguration": {}, - "sms:PutAppValidationConfiguration": {}, - "sms:StartAppReplication": {}, - "sms:StartOnDemandAppReplication": {}, - "sms:StartOnDemandReplicationRun": {}, - "sms:StopAppReplication": {}, - "sms:TerminateApp": {}, - "sms:UpdateApp": {}, - "sms:UpdateReplicationJob": {}, - "snow-device-management:CreateTask": {}, - "snow-device-management:DescribeExecution": {}, - "snow-device-management:ListDevices": {}, - "snow-device-management:ListExecutions": {}, - "snow-device-management:ListTagsForResource": {}, - "snow-device-management:ListTasks": {}, - "snowball:CancelCluster": {}, - "snowball:CancelJob": {}, - "snowball:CreateAddress": {}, - "snowball:CreateCluster": {}, - "snowball:CreateJob": {}, - "snowball:CreateLongTermPricing": {}, - "snowball:CreateReturnShippingLabel": {}, - "snowball:DescribeAddress": {}, - "snowball:DescribeAddresses": {}, - "snowball:DescribeCluster": {}, - "snowball:DescribeJob": {}, - "snowball:DescribeReturnShippingLabel": {}, - "snowball:GetJobManifest": {}, - "snowball:GetJobUnlockCode": {}, - "snowball:GetSnowballUsage": {}, - "snowball:GetSoftwareUpdates": {}, - "snowball:ListClusterJobs": {}, - "snowball:ListClusters": {}, - "snowball:ListCompatibleImages": {}, - "snowball:ListJobs": {}, - "snowball:ListLongTermPricing": {}, - "snowball:ListPickupLocations": {}, - "snowball:ListServiceVersions": {}, - "snowball:UpdateCluster": {}, - "snowball:UpdateJob": {}, - "snowball:UpdateJobShipmentState": {}, - "snowball:UpdateLongTermPricing": {}, - "sns:CheckIfPhoneNumberIsOptedOut": {}, - "sns:CreatePlatformApplication": {}, - "sns:CreatePlatformEndpoint": {}, - "sns:CreateSMSSandboxPhoneNumber": {}, - "sns:DeleteEndpoint": {}, - "sns:DeletePlatformApplication": {}, - "sns:DeleteSMSSandboxPhoneNumber": {}, - "sns:GetEndpointAttributes": {}, - "sns:GetPlatformApplicationAttributes": {}, - "sns:GetSMSAttributes": {}, - "sns:GetSMSSandboxAccountStatus": {}, - "sns:GetSubscriptionAttributes": {}, - "sns:ListEndpointsByPlatformApplication": {}, - "sns:ListOriginationNumbers": {}, - "sns:ListPhoneNumbersOptedOut": {}, - "sns:ListPlatformApplications": {}, - "sns:ListSMSSandboxPhoneNumbers": {}, - "sns:ListSubscriptions": {}, - "sns:ListTopics": {}, - "sns:OptInPhoneNumber": {}, - "sns:SetEndpointAttributes": {}, - "sns:SetPlatformApplicationAttributes": {}, - "sns:SetSMSAttributes": {}, - "sns:SetSubscriptionAttributes": {}, - "sns:Unsubscribe": {}, - "sns:VerifySMSSandboxPhoneNumber": {}, - "sqlworkbench:BatchDeleteFolder": {}, - "sqlworkbench:CreateAccount": {}, - "sqlworkbench:CreateFolder": {}, - "sqlworkbench:DeleteTab": {}, - "sqlworkbench:GenerateSession": {}, - "sqlworkbench:GetAccountInfo": {}, - "sqlworkbench:GetAccountSettings": {}, - "sqlworkbench:GetAutocompletionMetadata": {}, - "sqlworkbench:GetAutocompletionResource": {}, - "sqlworkbench:GetQSqlRecommendations": {}, - "sqlworkbench:GetQueryExecutionHistory": {}, - "sqlworkbench:GetSchemaInference": {}, - "sqlworkbench:GetUserInfo": {}, - "sqlworkbench:GetUserWorkspaceSettings": {}, - "sqlworkbench:ListConnections": {}, - "sqlworkbench:ListDatabases": {}, - "sqlworkbench:ListFiles": {}, - "sqlworkbench:ListNotebooks": {}, - "sqlworkbench:ListQueryExecutionHistory": {}, - "sqlworkbench:ListRedshiftClusters": {}, - "sqlworkbench:ListSampleDatabases": {}, - "sqlworkbench:ListTabs": {}, - "sqlworkbench:ListTaggedResources": {}, - "sqlworkbench:PutTab": {}, - "sqlworkbench:PutUserWorkspaceSettings": {}, - "sqlworkbench:UpdateAccountConnectionSettings": {}, - "sqlworkbench:UpdateAccountExportSettings": {}, - "sqlworkbench:UpdateAccountGeneralSettings": {}, - "sqlworkbench:UpdateAccountQSqlSettings": {}, - "sqlworkbench:UpdateFolder": {}, - "sqs:ListQueues": {}, - "ssm-contacts:ListContacts": {}, - "ssm-contacts:ListEngagements": {}, - "ssm-contacts:ListRotations": {}, - "ssm-guiconnect:CancelConnection": {}, - "ssm-guiconnect:GetConnection": {}, - "ssm-guiconnect:StartConnection": {}, - "ssm-incidents:CreateReplicationSet": {}, - "ssm-incidents:CreateResponsePlan": {}, - "ssm-incidents:ListIncidentRecords": {}, - "ssm-incidents:ListReplicationSets": {}, - "ssm-incidents:ListResponsePlans": {}, - "ssm-sap:BackupDatabase": {}, - "ssm-sap:DeleteResourcePermission": {}, - "ssm-sap:GetApplication": {}, - "ssm-sap:GetDatabase": {}, - "ssm-sap:GetOperation": {}, - "ssm-sap:GetResourcePermission": {}, - "ssm-sap:ListApplications": {}, - "ssm-sap:ListDatabases": {}, - "ssm-sap:ListOperations": {}, - "ssm-sap:ListTagsForResource": {}, - "ssm-sap:PutResourcePermission": {}, - "ssm-sap:RegisterApplication": {}, - "ssm-sap:RestoreDatabase": {}, - "ssm-sap:UpdateHANABackupSettings": {}, - "ssm:CancelCommand": {}, - "ssm:CreateActivation": {}, - "ssm:CreateMaintenanceWindow": {}, - "ssm:CreateOpsItem": {}, - "ssm:CreateOpsMetadata": {}, - "ssm:CreatePatchBaseline": {}, - "ssm:DeleteActivation": {}, - "ssm:DeleteInventory": {}, - "ssm:DescribeActivations": {}, - "ssm:DescribeAutomationExecutions": {}, - "ssm:DescribeAvailablePatches": {}, - "ssm:DescribeInstanceInformation": {}, - "ssm:DescribeInstancePatchStates": {}, - "ssm:DescribeInstancePatchStatesForPatchGroup": {}, - "ssm:DescribeInstancePatches": {}, - "ssm:DescribeInstanceProperties": {}, - "ssm:DescribeInventoryDeletions": {}, - "ssm:DescribeMaintenanceWindowExecutionTaskInvocations": {}, - "ssm:DescribeMaintenanceWindowSchedule": {}, - "ssm:DescribeMaintenanceWindows": {}, - "ssm:DescribeMaintenanceWindowsForTarget": {}, - "ssm:DescribeOpsItems": {}, - "ssm:DescribeParameters": {}, - "ssm:DescribePatchBaselines": {}, - "ssm:DescribePatchGroupState": {}, - "ssm:DescribePatchGroups": {}, - "ssm:DescribePatchProperties": {}, - "ssm:DescribeSessions": {}, - "ssm:GetCommandInvocation": {}, - "ssm:GetDeployablePatchSnapshotForInstance": {}, - "ssm:GetInventory": {}, - "ssm:GetInventorySchema": {}, - "ssm:GetMaintenanceWindowExecution": {}, - "ssm:GetMaintenanceWindowExecutionTask": {}, - "ssm:GetMaintenanceWindowExecutionTaskInvocation": {}, - "ssm:GetManifest": {}, - "ssm:ListAssociations": {}, - "ssm:ListCommandInvocations": {}, - "ssm:ListCommands": {}, - "ssm:ListComplianceItems": {}, - "ssm:ListComplianceSummaries": {}, - "ssm:ListDocuments": {}, - "ssm:ListInventoryEntries": {}, - "ssm:ListOpsItemEvents": {}, - "ssm:ListOpsItemRelatedItems": {}, - "ssm:ListOpsMetadata": {}, - "ssm:ListResourceComplianceSummaries": {}, - "ssm:ListResourceDataSync": {}, - "ssm:PutConfigurePackageResult": {}, - "ssm:PutInventory": {}, - "ssm:RegisterManagedInstance": {}, - "ssmmessages:CreateControlChannel": {}, - "ssmmessages:CreateDataChannel": {}, - "ssmmessages:OpenControlChannel": {}, - "ssmmessages:OpenDataChannel": {}, - "sso-directory:AddMemberToGroup": {}, - "sso-directory:CompleteVirtualMfaDeviceRegistration": {}, - "sso-directory:CompleteWebAuthnDeviceRegistration": {}, - "sso-directory:CreateAlias": {}, - "sso-directory:CreateBearerToken": {}, - "sso-directory:CreateExternalIdPConfigurationForDirectory": {}, - "sso-directory:CreateGroup": {}, - "sso-directory:CreateProvisioningTenant": {}, - "sso-directory:CreateUser": {}, - "sso-directory:DeleteBearerToken": {}, - "sso-directory:DeleteExternalIdPCertificate": {}, - "sso-directory:DeleteExternalIdPConfigurationForDirectory": {}, - "sso-directory:DeleteGroup": {}, - "sso-directory:DeleteMfaDeviceForUser": {}, - "sso-directory:DeleteProvisioningTenant": {}, - "sso-directory:DeleteUser": {}, - "sso-directory:DescribeDirectory": {}, - "sso-directory:DescribeGroup": {}, - "sso-directory:DescribeGroups": {}, - "sso-directory:DescribeProvisioningTenant": {}, - "sso-directory:DescribeUser": {}, - "sso-directory:DescribeUserByUniqueAttribute": {}, - "sso-directory:DescribeUsers": {}, - "sso-directory:DisableExternalIdPConfigurationForDirectory": {}, - "sso-directory:DisableUser": {}, - "sso-directory:EnableExternalIdPConfigurationForDirectory": {}, - "sso-directory:EnableUser": {}, - "sso-directory:GetAWSSPConfigurationForDirectory": {}, - "sso-directory:GetUserPoolInfo": {}, - "sso-directory:ImportExternalIdPCertificate": {}, - "sso-directory:IsMemberInGroup": {}, - "sso-directory:ListBearerTokens": {}, - "sso-directory:ListExternalIdPCertificates": {}, - "sso-directory:ListExternalIdPConfigurationsForDirectory": {}, - "sso-directory:ListGroupsForMember": {}, - "sso-directory:ListGroupsForUser": {}, - "sso-directory:ListMembersInGroup": {}, - "sso-directory:ListMfaDevicesForUser": {}, - "sso-directory:ListProvisioningTenants": {}, - "sso-directory:RemoveMemberFromGroup": {}, - "sso-directory:SearchGroups": {}, - "sso-directory:SearchUsers": {}, - "sso-directory:StartVirtualMfaDeviceRegistration": {}, - "sso-directory:StartWebAuthnDeviceRegistration": {}, - "sso-directory:UpdateExternalIdPConfigurationForDirectory": {}, - "sso-directory:UpdateGroup": {}, - "sso-directory:UpdateGroupDisplayName": {}, - "sso-directory:UpdateMfaDeviceForUser": {}, - "sso-directory:UpdatePassword": {}, - "sso-directory:UpdateUser": {}, - "sso-directory:UpdateUserName": {}, - "sso-directory:VerifyEmail": {}, - "sso:AssociateDirectory": {}, - "sso:AssociateProfile": {}, - "sso:CreateApplicationInstance": {}, - "sso:CreateApplicationInstanceCertificate": {}, - "sso:CreateManagedApplicationInstance": {}, - "sso:CreateProfile": {}, - "sso:CreateTrust": {}, - "sso:DeleteApplicationInstance": {}, - "sso:DeleteApplicationInstanceCertificate": {}, - "sso:DeleteManagedApplicationInstance": {}, - "sso:DeletePermissionsPolicy": {}, - "sso:DeleteProfile": {}, - "sso:DescribeDirectories": {}, - "sso:DescribePermissionsPolicies": {}, - "sso:DescribeRegisteredRegions": {}, - "sso:DescribeTrusts": {}, - "sso:DisassociateDirectory": {}, - "sso:DisassociateProfile": {}, - "sso:GetApplicationInstance": {}, - "sso:GetApplicationTemplate": {}, - "sso:GetManagedApplicationInstance": {}, - "sso:GetMfaDeviceManagementForDirectory": {}, - "sso:GetPermissionSet": {}, - "sso:GetPermissionsPolicy": {}, - "sso:GetProfile": {}, - "sso:GetSSOStatus": {}, - "sso:GetSharedSsoConfiguration": {}, - "sso:GetSsoConfiguration": {}, - "sso:GetTrust": {}, - "sso:ImportApplicationInstanceServiceProviderMetadata": {}, - "sso:ListApplicationInstanceCertificates": {}, - "sso:ListApplicationInstances": {}, - "sso:ListApplicationTemplates": {}, - "sso:ListApplications": {}, - "sso:ListDirectoryAssociations": {}, - "sso:ListInstances": {}, - "sso:ListProfileAssociations": {}, - "sso:ListProfiles": {}, - "sso:PutMfaDeviceManagementForDirectory": {}, - "sso:PutPermissionsPolicy": {}, - "sso:SearchGroups": {}, - "sso:SearchUsers": {}, - "sso:StartSSO": {}, - "sso:UpdateApplicationInstanceActiveCertificate": {}, - "sso:UpdateApplicationInstanceDisplayData": {}, - "sso:UpdateApplicationInstanceResponseConfiguration": {}, - "sso:UpdateApplicationInstanceResponseSchemaConfiguration": {}, - "sso:UpdateApplicationInstanceSecurityConfiguration": {}, - "sso:UpdateApplicationInstanceServiceProviderConfiguration": {}, - "sso:UpdateApplicationInstanceStatus": {}, - "sso:UpdateDirectoryAssociation": {}, - "sso:UpdateManagedApplicationInstanceStatus": {}, - "sso:UpdateProfile": {}, - "sso:UpdateSSOConfiguration": {}, - "sso:UpdateTrust": {}, - "states:InvokeHTTPEndpoint": {}, - "states:ListActivities": {}, - "states:ListStateMachines": {}, - "states:RevealSecrets": {}, - "states:SendTaskFailure": {}, - "states:SendTaskHeartbeat": {}, - "states:SendTaskSuccess": {}, - "states:TestState": {}, - "storagegateway:ActivateGateway": {}, - "storagegateway:CreateTapePool": {}, - "storagegateway:DeleteTapeArchive": {}, - "storagegateway:DescribeTapeArchives": {}, - "storagegateway:ListAutomaticTapeCreationPolicies": {}, - "storagegateway:ListFileShares": {}, - "storagegateway:ListFileSystemAssociations": {}, - "storagegateway:ListGateways": {}, - "storagegateway:ListTapePools": {}, - "storagegateway:ListTapes": {}, - "storagegateway:ListVolumes": {}, - "sts:DecodeAuthorizationMessage": {}, - "sts:GetAccessKeyInfo": {}, - "sts:GetCallerIdentity": {}, - "sts:GetServiceBearerToken": {}, - "sts:GetSessionToken": {}, - "support:AddAttachmentsToSet": {}, - "support:AddCommunicationToCase": {}, - "support:CreateCase": {}, - "support:DescribeAttachment": {}, - "support:DescribeCaseAttributes": {}, - "support:DescribeCases": {}, - "support:DescribeCommunication": {}, - "support:DescribeCommunications": {}, - "support:DescribeCreateCaseOptions": {}, - "support:DescribeIssueTypes": {}, - "support:DescribeServices": {}, - "support:DescribeSeverityLevels": {}, - "support:DescribeSupportLevel": {}, - "support:DescribeSupportedLanguages": {}, - "support:DescribeTrustedAdvisorCheckRefreshStatuses": {}, - "support:DescribeTrustedAdvisorCheckResult": {}, - "support:DescribeTrustedAdvisorCheckSummaries": {}, - "support:DescribeTrustedAdvisorChecks": {}, - "support:InitiateCallForCase": {}, - "support:InitiateChatForCase": {}, - "support:PutCaseAttributes": {}, - "support:RateCaseCommunication": {}, - "support:RefreshTrustedAdvisorCheck": {}, - "support:ResolveCase": {}, - "support:SearchForCases": {}, - "supportapp:CreateSlackChannelConfiguration": {}, - "supportapp:DeleteAccountAlias": {}, - "supportapp:DeleteSlackChannelConfiguration": {}, - "supportapp:DeleteSlackWorkspaceConfiguration": {}, - "supportapp:DescribeSlackChannels": {}, - "supportapp:GetAccountAlias": {}, - "supportapp:GetSlackOauthParameters": {}, - "supportapp:ListSlackChannelConfigurations": {}, - "supportapp:ListSlackWorkspaceConfigurations": {}, - "supportapp:PutAccountAlias": {}, - "supportapp:RedeemSlackOauthCode": {}, - "supportapp:RegisterSlackWorkspaceForOrganization": {}, - "supportapp:UpdateSlackChannelConfiguration": {}, - "supportplans:CreateSupportPlanSchedule": {}, - "supportplans:GetSupportPlan": {}, - "supportplans:GetSupportPlanUpdateStatus": {}, - "supportplans:StartSupportPlanUpdate": {}, - "sustainability:GetCarbonFootprintSummary": {}, - "swf:ListDomains": {}, - "swf:RegisterDomain": {}, - "synthetics:CreateCanary": {}, - "synthetics:CreateGroup": {}, - "synthetics:DescribeCanaries": {}, - "synthetics:DescribeCanariesLastRun": {}, - "synthetics:DescribeRuntimeVersions": {}, - "synthetics:ListGroups": {}, - "tag:DescribeReportCreation": {}, - "tag:GetComplianceSummary": {}, - "tag:GetResources": {}, - "tag:GetTagKeys": {}, - "tag:GetTagValues": {}, - "tag:StartReportCreation": {}, - "tag:TagResources": {}, - "tag:UntagResources": {}, - "tax:BatchPutTaxRegistration": {}, - "tax:DeleteTaxRegistration": {}, - "tax:GetExemptions": {}, - "tax:GetTaxInfoReportingDocument": {}, - "tax:GetTaxInheritance": {}, - "tax:GetTaxInterview": {}, - "tax:GetTaxRegistration": {}, - "tax:GetTaxRegistrationDocument": {}, - "tax:ListTaxRegistrations": {}, - "tax:PutTaxInheritance": {}, - "tax:PutTaxInterview": {}, - "tax:PutTaxRegistration": {}, - "tax:UpdateExemptions": {}, - "textract:AnalyzeDocument": {}, - "textract:AnalyzeExpense": {}, - "textract:AnalyzeID": {}, - "textract:CreateAdapter": {}, - "textract:DetectDocumentText": {}, - "textract:GetDocumentAnalysis": {}, - "textract:GetDocumentTextDetection": {}, - "textract:GetExpenseAnalysis": {}, - "textract:GetLendingAnalysis": {}, - "textract:GetLendingAnalysisSummary": {}, - "textract:ListAdapterVersions": {}, - "textract:ListAdapters": {}, - "textract:StartDocumentAnalysis": {}, - "textract:StartDocumentTextDetection": {}, - "textract:StartExpenseAnalysis": {}, - "textract:StartLendingAnalysis": {}, - "thinclient:CreateEnvironment": {}, - "thinclient:ListDeviceSessions": {}, - "thinclient:ListDevices": {}, - "thinclient:ListEnvironments": {}, - "thinclient:ListSoftwareSets": {}, - "thinclient:ListTagsForResource": {}, - "timestream:CancelQuery": {}, - "timestream:CreateScheduledQuery": {}, - "timestream:DescribeBatchLoadTask": {}, - "timestream:DescribeEndpoints": {}, - "timestream:GetAwsBackupStatus": {}, - "timestream:GetAwsRestoreStatus": {}, - "timestream:ListBatchLoadTasks": {}, - "timestream:ListDatabases": {}, - "timestream:ListScheduledQueries": {}, - "timestream:ResumeBatchLoadTask": {}, - "timestream:SelectValues": {}, - "tiros:CreateQuery": {}, - "tiros:ExtendQuery": {}, - "tiros:GetQueryAnswer": {}, - "tiros:GetQueryExplanation": {}, - "tiros:GetQueryExtensionAccounts": {}, - "tnb:ListTagsForResource": {}, - "transcribe:CreateCallAnalyticsCategory": {}, - "transcribe:CreateLanguageModel": {}, - "transcribe:CreateMedicalVocabulary": {}, - "transcribe:CreateVocabulary": {}, - "transcribe:CreateVocabularyFilter": {}, - "transcribe:DeleteCallAnalyticsCategory": {}, - "transcribe:DeleteCallAnalyticsJob": {}, - "transcribe:GetCallAnalyticsCategory": {}, - "transcribe:GetCallAnalyticsJob": {}, - "transcribe:ListCallAnalyticsCategories": {}, - "transcribe:ListCallAnalyticsJobs": {}, - "transcribe:ListLanguageModels": {}, - "transcribe:ListMedicalScribeJobs": {}, - "transcribe:ListMedicalTranscriptionJobs": {}, - "transcribe:ListMedicalVocabularies": {}, - "transcribe:ListTagsForResource": {}, - "transcribe:ListTranscriptionJobs": {}, - "transcribe:ListVocabularies": {}, - "transcribe:ListVocabularyFilters": {}, - "transcribe:StartCallAnalyticsJob": {}, - "transcribe:StartCallAnalyticsStreamTranscription": {}, - "transcribe:StartCallAnalyticsStreamTranscriptionWebSocket": {}, - "transcribe:StartMedicalScribeJob": {}, - "transcribe:StartMedicalStreamTranscription": {}, - "transcribe:StartMedicalStreamTranscriptionWebSocket": {}, - "transcribe:StartMedicalTranscriptionJob": {}, - "transcribe:StartStreamTranscription": {}, - "transcribe:StartStreamTranscriptionWebSocket": {}, - "transcribe:StartTranscriptionJob": {}, - "transcribe:TagResource": {}, - "transcribe:UntagResource": {}, - "transcribe:UpdateCallAnalyticsCategory": {}, - "transfer:CreateConnector": {}, - "transfer:CreateProfile": {}, - "transfer:CreateServer": {}, - "transfer:CreateWorkflow": {}, - "transfer:DescribeSecurityPolicy": {}, - "transfer:ImportCertificate": {}, - "transfer:ListCertificates": {}, - "transfer:ListConnectors": {}, - "transfer:ListProfiles": {}, - "transfer:ListSecurityPolicies": {}, - "transfer:ListServers": {}, - "transfer:ListWorkflows": {}, - "transfer:UpdateAccess": {}, - "translate:DescribeTextTranslationJob": {}, - "translate:ListLanguages": {}, - "translate:ListParallelData": {}, - "translate:ListTerminologies": {}, - "translate:ListTextTranslationJobs": {}, - "translate:StopTextTranslationJob": {}, - "trustedadvisor:CreateEngagement": {}, - "trustedadvisor:CreateEngagementAttachment": {}, - "trustedadvisor:CreateEngagementCommunication": {}, - "trustedadvisor:DeleteNotificationConfigurationForDelegatedAdmin": {}, - "trustedadvisor:DescribeAccount": {}, - "trustedadvisor:DescribeAccountAccess": {}, - "trustedadvisor:DescribeChecks": {}, - "trustedadvisor:DescribeNotificationConfigurations": {}, - "trustedadvisor:DescribeNotificationPreferences": {}, - "trustedadvisor:DescribeOrganization": {}, - "trustedadvisor:DescribeOrganizationAccounts": {}, - "trustedadvisor:DescribeReports": {}, - "trustedadvisor:DescribeRisk": {}, - "trustedadvisor:DescribeRiskResources": {}, - "trustedadvisor:DescribeRisks": {}, - "trustedadvisor:DescribeServiceMetadata": {}, - "trustedadvisor:DownloadRisk": {}, - "trustedadvisor:GenerateReport": {}, - "trustedadvisor:GetEngagement": {}, - "trustedadvisor:GetEngagementAttachment": {}, - "trustedadvisor:GetEngagementType": {}, - "trustedadvisor:GetOrganizationRecommendation": {}, - "trustedadvisor:GetRecommendation": {}, - "trustedadvisor:ListAccountsForParent": {}, - "trustedadvisor:ListChecks": {}, - "trustedadvisor:ListEngagementCommunications": {}, - "trustedadvisor:ListEngagementTypes": {}, - "trustedadvisor:ListEngagements": {}, - "trustedadvisor:ListOrganizationRecommendationAccounts": {}, - "trustedadvisor:ListOrganizationRecommendationResources": {}, - "trustedadvisor:ListOrganizationRecommendations": {}, - "trustedadvisor:ListOrganizationalUnitsForParent": {}, - "trustedadvisor:ListRecommendationResources": {}, - "trustedadvisor:ListRecommendations": {}, - "trustedadvisor:ListRoots": {}, - "trustedadvisor:SetAccountAccess": {}, - "trustedadvisor:SetOrganizationAccess": {}, - "trustedadvisor:UpdateEngagement": {}, - "trustedadvisor:UpdateEngagementStatus": {}, - "trustedadvisor:UpdateNotificationConfigurations": {}, - "trustedadvisor:UpdateNotificationPreferences": {}, - "trustedadvisor:UpdateOrganizationRecommendationLifecycle": {}, - "trustedadvisor:UpdateRecommendationLifecycle": {}, - "trustedadvisor:UpdateRiskStatus": {}, - "ts:ListExecutions": {}, - "ts:ListTools": {}, - "ts:StartExecution": {}, - "vendor-insights:CreateDataSource": {}, - "vendor-insights:CreateSecurityProfile": {}, - "vendor-insights:GetProfileAccessTerms": {}, - "vendor-insights:ListDataSources": {}, - "vendor-insights:ListEntitledSecurityProfiles": {}, - "vendor-insights:ListSecurityProfiles": {}, - "verified-access:AllowVerifiedAccess": {}, - "verifiedpermissions:CreatePolicyStore": {}, - "verifiedpermissions:ListPolicyStores": {}, - "voiceid:CreateDomain": {}, - "voiceid:DescribeComplianceConsent": {}, - "voiceid:ListDomains": {}, - "voiceid:RegisterComplianceConsent": {}, - "vpc-lattice:ListAccessLogSubscriptions": {}, - "vpc-lattice:ListListeners": {}, - "vpc-lattice:ListRules": {}, - "vpc-lattice:ListServiceNetworkServiceAssociations": {}, - "vpc-lattice:ListServiceNetworkVpcAssociations": {}, - "vpc-lattice:ListServiceNetworks": {}, - "vpc-lattice:ListServices": {}, - "vpc-lattice:ListTagsForResource": {}, - "vpc-lattice:ListTargetGroups": {}, - "waf-regional:GetChangeToken": {}, - "waf-regional:GetChangeTokenStatus": {}, - "waf-regional:ListActivatedRulesInRuleGroup": {}, - "waf-regional:ListByteMatchSets": {}, - "waf-regional:ListGeoMatchSets": {}, - "waf-regional:ListIPSets": {}, - "waf-regional:ListLoggingConfigurations": {}, - "waf-regional:ListRateBasedRules": {}, - "waf-regional:ListRegexMatchSets": {}, - "waf-regional:ListRegexPatternSets": {}, - "waf-regional:ListRuleGroups": {}, - "waf-regional:ListRules": {}, - "waf-regional:ListSizeConstraintSets": {}, - "waf-regional:ListSqlInjectionMatchSets": {}, - "waf-regional:ListSubscribedRuleGroups": {}, - "waf-regional:ListWebACLs": {}, - "waf-regional:ListXssMatchSets": {}, - "waf:GetChangeToken": {}, - "waf:GetChangeTokenStatus": {}, - "waf:ListActivatedRulesInRuleGroup": {}, - "waf:ListByteMatchSets": {}, - "waf:ListGeoMatchSets": {}, - "waf:ListIPSets": {}, - "waf:ListLoggingConfigurations": {}, - "waf:ListRateBasedRules": {}, - "waf:ListRegexMatchSets": {}, - "waf:ListRegexPatternSets": {}, - "waf:ListRuleGroups": {}, - "waf:ListRules": {}, - "waf:ListSizeConstraintSets": {}, - "waf:ListSqlInjectionMatchSets": {}, - "waf:ListSubscribedRuleGroups": {}, - "waf:ListWebACLs": {}, - "waf:ListXssMatchSets": {}, - "wafv2:CheckCapacity": {}, - "wafv2:CreateAPIKey": {}, - "wafv2:DescribeAllManagedProducts": {}, - "wafv2:DescribeManagedProductsByVendor": {}, - "wafv2:DescribeManagedRuleGroup": {}, - "wafv2:GenerateMobileSdkReleaseUrl": {}, - "wafv2:GetDecryptedAPIKey": {}, - "wafv2:GetMobileSdkRelease": {}, - "wafv2:ListAPIKeys": {}, - "wafv2:ListAvailableManagedRuleGroupVersions": {}, - "wafv2:ListAvailableManagedRuleGroups": {}, - "wafv2:ListIPSets": {}, - "wafv2:ListLoggingConfigurations": {}, - "wafv2:ListManagedRuleSets": {}, - "wafv2:ListMobileSdkReleases": {}, - "wafv2:ListRegexPatternSets": {}, - "wafv2:ListRuleGroups": {}, - "wafv2:ListWebACLs": {}, - "wam:AuthenticatePackager": {}, - "wellarchitected:CreateProfile": {}, - "wellarchitected:CreateReviewTemplate": {}, - "wellarchitected:CreateWorkload": {}, - "wellarchitected:GetConsolidatedReport": {}, - "wellarchitected:GetProfileTemplate": {}, - "wellarchitected:ImportLens": {}, - "wellarchitected:ListLenses": {}, - "wellarchitected:ListNotifications": {}, - "wellarchitected:ListProfileNotifications": {}, - "wellarchitected:ListProfiles": {}, - "wellarchitected:ListReviewTemplates": {}, - "wellarchitected:ListShareInvitations": {}, - "wellarchitected:ListWorkloads": {}, - "wellarchitected:UpdateGlobalSettings": {}, - "wellarchitected:UpdateShareInvitation": {}, - "wickr:CreateNetwork": {}, - "wickr:ListNetworks": {}, - "wickr:ListTagsForResource": {}, - "wisdom:CreateAssistant": {}, - "wisdom:CreateKnowledgeBase": {}, - "wisdom:ListAssistants": {}, - "wisdom:ListKnowledgeBases": {}, - "wisdom:ListTagsForResource": {}, - "workdocs:AbortDocumentVersionUpload": {}, - "workdocs:ActivateUser": {}, - "workdocs:AddNotificationPermissions": {}, - "workdocs:AddResourcePermissions": {}, - "workdocs:AddUserToGroup": {}, - "workdocs:CheckAlias": {}, - "workdocs:CreateComment": {}, - "workdocs:CreateCustomMetadata": {}, - "workdocs:CreateFolder": {}, - "workdocs:CreateInstance": {}, - "workdocs:CreateLabels": {}, - "workdocs:CreateNotificationSubscription": {}, - "workdocs:CreateUser": {}, - "workdocs:DeactivateUser": {}, - "workdocs:DeleteComment": {}, - "workdocs:DeleteCustomMetadata": {}, - "workdocs:DeleteDocument": {}, - "workdocs:DeleteDocumentVersion": {}, - "workdocs:DeleteFolder": {}, - "workdocs:DeleteFolderContents": {}, - "workdocs:DeleteInstance": {}, - "workdocs:DeleteLabels": {}, - "workdocs:DeleteNotificationPermissions": {}, - "workdocs:DeleteNotificationSubscription": {}, - "workdocs:DeleteUser": {}, - "workdocs:DeregisterDirectory": {}, - "workdocs:DescribeActivities": {}, - "workdocs:DescribeAvailableDirectories": {}, - "workdocs:DescribeComments": {}, - "workdocs:DescribeDocumentVersions": {}, - "workdocs:DescribeFolderContents": {}, - "workdocs:DescribeGroups": {}, - "workdocs:DescribeInstances": {}, - "workdocs:DescribeNotificationPermissions": {}, - "workdocs:DescribeNotificationSubscriptions": {}, - "workdocs:DescribeResourcePermissions": {}, - "workdocs:DescribeRootFolders": {}, - "workdocs:DescribeUsers": {}, - "workdocs:DownloadDocumentVersion": {}, - "workdocs:GetCurrentUser": {}, - "workdocs:GetDocument": {}, - "workdocs:GetDocumentPath": {}, - "workdocs:GetDocumentVersion": {}, - "workdocs:GetFolder": {}, - "workdocs:GetFolderPath": {}, - "workdocs:GetGroup": {}, - "workdocs:GetResources": {}, - "workdocs:InitiateDocumentVersionUpload": {}, - "workdocs:RegisterDirectory": {}, - "workdocs:RemoveAllResourcePermissions": {}, - "workdocs:RemoveResourcePermission": {}, - "workdocs:RestoreDocumentVersions": {}, - "workdocs:SearchResources": {}, - "workdocs:UpdateDocument": {}, - "workdocs:UpdateDocumentVersion": {}, - "workdocs:UpdateFolder": {}, - "workdocs:UpdateInstanceAlias": {}, - "workdocs:UpdateUser": {}, - "workdocs:UpdateUserAdministrativeSettings": {}, - "worklink:CreateFleet": {}, - "worklink:ListFleets": {}, - "workmail:CreateOrganization": {}, - "workmail:DescribeDirectories": {}, - "workmail:DescribeKmsKeys": {}, - "workmail:DescribeOrganizations": {}, - "workmail:ListOrganizations": {}, - "workspaces-web:CreateBrowserSettings": {}, - "workspaces-web:CreateIpAccessSettings": {}, - "workspaces-web:CreateNetworkSettings": {}, - "workspaces-web:CreatePortal": {}, - "workspaces-web:CreateTrustStore": {}, - "workspaces-web:CreateUserAccessLoggingSettings": {}, - "workspaces-web:CreateUserSettings": {}, - "workspaces-web:ListBrowserSettings": {}, - "workspaces-web:ListIpAccessSettings": {}, - "workspaces-web:ListNetworkSettings": {}, - "workspaces-web:ListPortals": {}, - "workspaces-web:ListTagsForResource": {}, - "workspaces-web:ListTrustStoreCertificates": {}, - "workspaces-web:ListTrustStores": {}, - "workspaces-web:ListUserAccessLoggingSettings": {}, - "workspaces-web:ListUserSettings": {}, - "workspaces:CreateConnectionAlias": {}, - "workspaces:CreateIpGroup": {}, - "workspaces:CreateTags": {}, - "workspaces:DeleteTags": {}, - "workspaces:DescribeAccount": {}, - "workspaces:DescribeAccountModifications": {}, - "workspaces:DescribeApplications": {}, - "workspaces:DescribeConnectionAliases": {}, - "workspaces:DescribeTags": {}, - "workspaces:DescribeWorkspaceBundles": {}, - "workspaces:DescribeWorkspaceDirectories": {}, - "workspaces:DescribeWorkspaceImages": {}, - "workspaces:DescribeWorkspaces": {}, - "workspaces:DescribeWorkspacesConnectionStatus": {}, - "workspaces:ImportWorkspaceImage": {}, - "workspaces:ListAvailableManagementCidrRanges": {}, - "workspaces:ModifyAccount": {}, - "xray:BatchGetTraceSummaryById": {}, - "xray:BatchGetTraces": {}, - "xray:DeleteResourcePolicy": {}, - "xray:GetDistinctTraceGraphs": {}, - "xray:GetEncryptionConfig": {}, - "xray:GetGroups": {}, - "xray:GetInsight": {}, - "xray:GetInsightEvents": {}, - "xray:GetInsightImpactGraph": {}, - "xray:GetInsightSummaries": {}, - "xray:GetSamplingRules": {}, - "xray:GetSamplingStatisticSummaries": {}, - "xray:GetSamplingTargets": {}, - "xray:GetServiceGraph": {}, - "xray:GetTimeSeriesServiceStatistics": {}, - "xray:GetTraceGraph": {}, - "xray:GetTraceSummaries": {}, - "xray:Link": {}, - "xray:ListResourcePolicies": {}, - "xray:PutEncryptionConfig": {}, - "xray:PutResourcePolicy": {}, - "xray:PutTelemetryRecords": {}, - "xray:PutTraceSegments": {}, -} \ No newline at end of file + "servicecatalog:CopyProduct": {}, + "servicecatalog:CreateProvisionedProductPlan": {}, + "servicecatalog:CreateServiceAction": {}, + "servicecatalog:CreateTagOption": {}, + "servicecatalog:DeleteConstraint": {}, + "servicecatalog:DeleteProvisionedProductPlan": {}, + "servicecatalog:DeleteServiceAction": {}, + "servicecatalog:DeleteTagOption": {}, + "servicecatalog:DescribeConstraint": {}, + "servicecatalog:DescribeCopyProductStatus": {}, + "servicecatalog:DescribePortfolioShareStatus": {}, + "servicecatalog:DescribeProductView": {}, + "servicecatalog:DescribeProvisionedProduct": {}, + "servicecatalog:DescribeProvisionedProductPlan": {}, + "servicecatalog:DescribeRecord": {}, + "servicecatalog:DescribeServiceAction": {}, + "servicecatalog:DescribeServiceActionExecutionParameters": {}, + "servicecatalog:DescribeTagOption": {}, + "servicecatalog:DisableAWSOrganizationsAccess": {}, + "servicecatalog:DisassociateBudgetFromResource": {}, + "servicecatalog:DisassociateProductFromPortfolio": {}, + "servicecatalog:EnableAWSOrganizationsAccess": {}, + "servicecatalog:ExecuteProvisionedProductPlan": {}, + "servicecatalog:ExecuteProvisionedProductServiceAction": {}, + "servicecatalog:GetAWSOrganizationsAccessStatus": {}, + "servicecatalog:GetConfiguration": {}, + "servicecatalog:GetProvisionedProductOutputs": {}, + "servicecatalog:ListAcceptedPortfolioShares": {}, + "servicecatalog:ListApplications": {}, + "servicecatalog:ListAttributeGroups": {}, + "servicecatalog:ListBudgetsForResource": {}, + "servicecatalog:ListConstraintsForPortfolio": {}, + "servicecatalog:ListOrganizationPortfolioAccess": {}, + "servicecatalog:ListPortfolios": {}, + "servicecatalog:ListProvisionedProductPlans": {}, + "servicecatalog:ListProvisioningArtifactsForServiceAction": {}, + "servicecatalog:ListRecordHistory": {}, + "servicecatalog:ListResourcesForTagOption": {}, + "servicecatalog:ListServiceActions": {}, + "servicecatalog:ListStackInstancesForProvisionedProduct": {}, + "servicecatalog:ListTagOptions": {}, + "servicecatalog:NotifyProvisionProductEngineWorkflowResult": {}, + "servicecatalog:NotifyTerminateProvisionedProductEngineWorkflowResult": {}, + "servicecatalog:NotifyUpdateProvisionedProductEngineWorkflowResult": {}, + "servicecatalog:PutConfiguration": {}, + "servicecatalog:ScanProvisionedProducts": {}, + "servicecatalog:SearchProducts": {}, + "servicecatalog:SearchProductsAsAdmin": {}, + "servicecatalog:SearchProvisionedProducts": {}, + "servicecatalog:SyncResource": {}, + "servicecatalog:TerminateProvisionedProduct": {}, + "servicecatalog:UpdateConstraint": {}, + "servicecatalog:UpdateProvisionedProduct": {}, + "servicecatalog:UpdateProvisionedProductProperties": {}, + "servicecatalog:UpdateServiceAction": {}, + "servicecatalog:UpdateTagOption": {}, + "servicediscovery:CreateHttpNamespace": {}, + "servicediscovery:CreatePrivateDnsNamespace": {}, + "servicediscovery:CreatePublicDnsNamespace": {}, + "servicediscovery:DiscoverInstances": {}, + "servicediscovery:DiscoverInstancesRevision": {}, + "servicediscovery:GetInstance": {}, + "servicediscovery:GetInstancesHealthStatus": {}, + "servicediscovery:GetOperation": {}, + "servicediscovery:ListInstances": {}, + "servicediscovery:ListNamespaces": {}, + "servicediscovery:ListOperations": {}, + "servicediscovery:ListServices": {}, + "servicediscovery:ListTagsForResource": {}, + "servicediscovery:TagResource": {}, + "servicediscovery:UntagResource": {}, + "servicediscovery:UpdateInstanceCustomHealthStatus": {}, + "serviceextract:GetConfig": {}, + "servicequotas:AssociateServiceQuotaTemplate": {}, + "servicequotas:DeleteServiceQuotaIncreaseRequestFromTemplate": {}, + "servicequotas:DisassociateServiceQuotaTemplate": {}, + "servicequotas:GetAWSDefaultServiceQuota": {}, + "servicequotas:GetAssociationForServiceQuotaTemplate": {}, + "servicequotas:GetRequestedServiceQuotaChange": {}, + "servicequotas:GetServiceQuota": {}, + "servicequotas:GetServiceQuotaIncreaseRequestFromTemplate": {}, + "servicequotas:ListAWSDefaultServiceQuotas": {}, + "servicequotas:ListRequestedServiceQuotaChangeHistory": {}, + "servicequotas:ListRequestedServiceQuotaChangeHistoryByQuota": {}, + "servicequotas:ListServiceQuotaIncreaseRequestsInTemplate": {}, + "servicequotas:ListServiceQuotas": {}, + "servicequotas:ListServices": {}, + "servicequotas:ListTagsForResource": {}, + "servicequotas:TagResource": {}, + "servicequotas:UntagResource": {}, + "ses:CloneReceiptRuleSet": {}, + "ses:CreateConfigurationSet": {}, + "ses:CreateConfigurationSetEventDestination": {}, + "ses:CreateConfigurationSetTrackingOptions": {}, + "ses:CreateCustomVerificationEmailTemplate": {}, + "ses:CreateDedicatedIpPool": {}, + "ses:CreateEmailIdentity": {}, + "ses:CreateExportJob": {}, + "ses:CreateImportJob": {}, + "ses:CreateReceiptFilter": {}, + "ses:CreateReceiptRule": {}, + "ses:CreateReceiptRuleSet": {}, + "ses:CreateTemplate": {}, + "ses:DeleteConfigurationSet": {}, + "ses:DeleteConfigurationSetEventDestination": {}, + "ses:DeleteConfigurationSetTrackingOptions": {}, + "ses:DeleteCustomVerificationEmailTemplate": {}, + "ses:DeleteIdentity": {}, + "ses:DeleteIdentityPolicy": {}, + "ses:DeleteReceiptFilter": {}, + "ses:DeleteReceiptRule": {}, + "ses:DeleteReceiptRuleSet": {}, + "ses:DeleteSuppressedDestination": {}, + "ses:DeleteTemplate": {}, + "ses:DeleteVerifiedEmailAddress": {}, + "ses:DescribeActiveReceiptRuleSet": {}, + "ses:DescribeConfigurationSet": {}, + "ses:DescribeReceiptRule": {}, + "ses:DescribeReceiptRuleSet": {}, + "ses:GetAccount": {}, + "ses:GetAccountSendingEnabled": {}, + "ses:GetBlacklistReports": {}, + "ses:GetCustomVerificationEmailTemplate": {}, + "ses:GetDedicatedIp": {}, + "ses:GetDeliverabilityDashboardOptions": {}, + "ses:GetDomainDeliverabilityCampaign": {}, + "ses:GetIdentityDkimAttributes": {}, + "ses:GetIdentityMailFromDomainAttributes": {}, + "ses:GetIdentityNotificationAttributes": {}, + "ses:GetIdentityPolicies": {}, + "ses:GetIdentityVerificationAttributes": {}, + "ses:GetMessageInsights": {}, + "ses:GetSendQuota": {}, + "ses:GetSendStatistics": {}, + "ses:GetSuppressedDestination": {}, + "ses:GetTemplate": {}, + "ses:ListConfigurationSets": {}, + "ses:ListContactLists": {}, + "ses:ListCustomVerificationEmailTemplates": {}, + "ses:ListDedicatedIpPools": {}, + "ses:ListDeliverabilityTestReports": {}, + "ses:ListDomainDeliverabilityCampaigns": {}, + "ses:ListEmailIdentities": {}, + "ses:ListEmailTemplates": {}, + "ses:ListExportJobs": {}, + "ses:ListIdentities": {}, + "ses:ListIdentityPolicies": {}, + "ses:ListImportJobs": {}, + "ses:ListReceiptFilters": {}, + "ses:ListReceiptRuleSets": {}, + "ses:ListSuppressedDestinations": {}, + "ses:ListTemplates": {}, + "ses:ListVerifiedEmailAddresses": {}, + "ses:PutAccountDedicatedIpWarmupAttributes": {}, + "ses:PutAccountDetails": {}, + "ses:PutAccountSendingAttributes": {}, + "ses:PutAccountSuppressionAttributes": {}, + "ses:PutAccountVdmAttributes": {}, + "ses:PutConfigurationSetDeliveryOptions": {}, + "ses:PutDedicatedIpWarmupAttributes": {}, + "ses:PutDeliverabilityDashboardOption": {}, + "ses:PutIdentityPolicy": {}, + "ses:PutSuppressedDestination": {}, + "ses:ReorderReceiptRuleSet": {}, + "ses:SetActiveReceiptRuleSet": {}, + "ses:SetIdentityDkimEnabled": {}, + "ses:SetIdentityFeedbackForwardingEnabled": {}, + "ses:SetIdentityHeadersInNotificationsEnabled": {}, + "ses:SetIdentityMailFromDomain": {}, + "ses:SetIdentityNotificationTopic": {}, + "ses:SetReceiptRulePosition": {}, + "ses:TestRenderTemplate": {}, + "ses:UpdateAccountSendingEnabled": {}, + "ses:UpdateConfigurationSetEventDestination": {}, + "ses:UpdateConfigurationSetReputationMetricsEnabled": {}, + "ses:UpdateConfigurationSetSendingEnabled": {}, + "ses:UpdateConfigurationSetTrackingOptions": {}, + "ses:UpdateCustomVerificationEmailTemplate": {}, + "ses:UpdateReceiptRule": {}, + "ses:UpdateTemplate": {}, + "ses:VerifyDomainDkim": {}, + "ses:VerifyDomainIdentity": {}, + "ses:VerifyEmailAddress": {}, + "ses:VerifyEmailIdentity": {}, + "shield:AssociateDRTLogBucket": {}, + "shield:AssociateDRTRole": {}, + "shield:AssociateProactiveEngagementDetails": {}, + "shield:CreateProtection": {}, + "shield:CreateProtectionGroup": {}, + "shield:CreateSubscription": {}, + "shield:DeleteSubscription": {}, + "shield:DescribeAttackStatistics": {}, + "shield:DescribeDRTAccess": {}, + "shield:DescribeEmergencyContactSettings": {}, + "shield:DescribeSubscription": {}, + "shield:DisableApplicationLayerAutomaticResponse": {}, + "shield:DisableProactiveEngagement": {}, + "shield:DisassociateDRTLogBucket": {}, + "shield:DisassociateDRTRole": {}, + "shield:EnableApplicationLayerAutomaticResponse": {}, + "shield:EnableProactiveEngagement": {}, + "shield:GetSubscriptionState": {}, + "shield:ListAttacks": {}, + "shield:ListProtectionGroups": {}, + "shield:ListProtections": {}, + "shield:UpdateApplicationLayerAutomaticResponse": {}, + "shield:UpdateEmergencyContactSettings": {}, + "shield:UpdateSubscription": {}, + "signer:GetSigningPlatform": {}, + "signer:ListSigningJobs": {}, + "signer:ListSigningPlatforms": {}, + "signer:ListSigningProfiles": {}, + "signer:PutSigningProfile": {}, + "simspaceweaver:ListSimulations": {}, + "simspaceweaver:ListTagsForResource": {}, + "simspaceweaver:StartSimulation": {}, + "sms-voice:CreateConfigurationSet": {}, + "sms-voice:CreateConfigurationSetEventDestination": {}, + "sms-voice:CreateOptOutList": {}, + "sms-voice:CreateRegistration": {}, + "sms-voice:CreateRegistrationAttachment": {}, + "sms-voice:CreateVerifiedDestinationNumber": {}, + "sms-voice:DeleteConfigurationSet": {}, + "sms-voice:DeleteConfigurationSetEventDestination": {}, + "sms-voice:DeleteTextMessageSpendLimitOverride": {}, + "sms-voice:DeleteVoiceMessageSpendLimitOverride": {}, + "sms-voice:DescribeAccountAttributes": {}, + "sms-voice:DescribeAccountLimits": {}, + "sms-voice:DescribeRegistrationFieldDefinitions": {}, + "sms-voice:DescribeRegistrationSectionDefinitions": {}, + "sms-voice:DescribeRegistrationTypeDefinitions": {}, + "sms-voice:DescribeSpendLimits": {}, + "sms-voice:GetConfigurationSetEventDestinations": {}, + "sms-voice:ListConfigurationSets": {}, + "sms-voice:RequestSenderId": {}, + "sms-voice:SendVoiceMessage": {}, + "sms-voice:SetTextMessageSpendLimitOverride": {}, + "sms-voice:SetVoiceMessageSpendLimitOverride": {}, + "sms-voice:UpdateConfigurationSetEventDestination": {}, + "sms:CreateApp": {}, + "sms:CreateReplicationJob": {}, + "sms:DeleteApp": {}, + "sms:DeleteAppLaunchConfiguration": {}, + "sms:DeleteAppReplicationConfiguration": {}, + "sms:DeleteAppValidationConfiguration": {}, + "sms:DeleteReplicationJob": {}, + "sms:DeleteServerCatalog": {}, + "sms:DisassociateConnector": {}, + "sms:GenerateChangeSet": {}, + "sms:GenerateTemplate": {}, + "sms:GetApp": {}, + "sms:GetAppLaunchConfiguration": {}, + "sms:GetAppReplicationConfiguration": {}, + "sms:GetAppValidationConfiguration": {}, + "sms:GetAppValidationOutput": {}, + "sms:GetConnectors": {}, + "sms:GetReplicationJobs": {}, + "sms:GetReplicationRuns": {}, + "sms:GetServers": {}, + "sms:ImportAppCatalog": {}, + "sms:ImportServerCatalog": {}, + "sms:LaunchApp": {}, + "sms:ListApps": {}, + "sms:NotifyAppValidationOutput": {}, + "sms:PutAppLaunchConfiguration": {}, + "sms:PutAppReplicationConfiguration": {}, + "sms:PutAppValidationConfiguration": {}, + "sms:StartAppReplication": {}, + "sms:StartOnDemandAppReplication": {}, + "sms:StartOnDemandReplicationRun": {}, + "sms:StopAppReplication": {}, + "sms:TerminateApp": {}, + "sms:UpdateApp": {}, + "sms:UpdateReplicationJob": {}, + "snow-device-management:CreateTask": {}, + "snow-device-management:DescribeExecution": {}, + "snow-device-management:ListDevices": {}, + "snow-device-management:ListExecutions": {}, + "snow-device-management:ListTagsForResource": {}, + "snow-device-management:ListTasks": {}, + "snowball:CancelCluster": {}, + "snowball:CancelJob": {}, + "snowball:CreateAddress": {}, + "snowball:CreateCluster": {}, + "snowball:CreateJob": {}, + "snowball:CreateLongTermPricing": {}, + "snowball:CreateReturnShippingLabel": {}, + "snowball:DescribeAddress": {}, + "snowball:DescribeAddresses": {}, + "snowball:DescribeCluster": {}, + "snowball:DescribeJob": {}, + "snowball:DescribeReturnShippingLabel": {}, + "snowball:GetJobManifest": {}, + "snowball:GetJobUnlockCode": {}, + "snowball:GetSnowballUsage": {}, + "snowball:GetSoftwareUpdates": {}, + "snowball:ListClusterJobs": {}, + "snowball:ListClusters": {}, + "snowball:ListCompatibleImages": {}, + "snowball:ListJobs": {}, + "snowball:ListLongTermPricing": {}, + "snowball:ListPickupLocations": {}, + "snowball:ListServiceVersions": {}, + "snowball:UpdateCluster": {}, + "snowball:UpdateJob": {}, + "snowball:UpdateJobShipmentState": {}, + "snowball:UpdateLongTermPricing": {}, + "sns:CheckIfPhoneNumberIsOptedOut": {}, + "sns:CreatePlatformApplication": {}, + "sns:CreatePlatformEndpoint": {}, + "sns:CreateSMSSandboxPhoneNumber": {}, + "sns:DeleteEndpoint": {}, + "sns:DeletePlatformApplication": {}, + "sns:DeleteSMSSandboxPhoneNumber": {}, + "sns:GetEndpointAttributes": {}, + "sns:GetPlatformApplicationAttributes": {}, + "sns:GetSMSAttributes": {}, + "sns:GetSMSSandboxAccountStatus": {}, + "sns:GetSubscriptionAttributes": {}, + "sns:ListEndpointsByPlatformApplication": {}, + "sns:ListOriginationNumbers": {}, + "sns:ListPhoneNumbersOptedOut": {}, + "sns:ListPlatformApplications": {}, + "sns:ListSMSSandboxPhoneNumbers": {}, + "sns:ListSubscriptions": {}, + "sns:ListTopics": {}, + "sns:OptInPhoneNumber": {}, + "sns:SetEndpointAttributes": {}, + "sns:SetPlatformApplicationAttributes": {}, + "sns:SetSMSAttributes": {}, + "sns:SetSubscriptionAttributes": {}, + "sns:Unsubscribe": {}, + "sns:VerifySMSSandboxPhoneNumber": {}, + "sqlworkbench:BatchDeleteFolder": {}, + "sqlworkbench:CreateAccount": {}, + "sqlworkbench:CreateFolder": {}, + "sqlworkbench:DeleteTab": {}, + "sqlworkbench:GenerateSession": {}, + "sqlworkbench:GetAccountInfo": {}, + "sqlworkbench:GetAccountSettings": {}, + "sqlworkbench:GetAutocompletionMetadata": {}, + "sqlworkbench:GetAutocompletionResource": {}, + "sqlworkbench:GetQSqlRecommendations": {}, + "sqlworkbench:GetQueryExecutionHistory": {}, + "sqlworkbench:GetSchemaInference": {}, + "sqlworkbench:GetUserInfo": {}, + "sqlworkbench:GetUserWorkspaceSettings": {}, + "sqlworkbench:ListConnections": {}, + "sqlworkbench:ListDatabases": {}, + "sqlworkbench:ListFiles": {}, + "sqlworkbench:ListNotebooks": {}, + "sqlworkbench:ListQueryExecutionHistory": {}, + "sqlworkbench:ListRedshiftClusters": {}, + "sqlworkbench:ListSampleDatabases": {}, + "sqlworkbench:ListTabs": {}, + "sqlworkbench:ListTaggedResources": {}, + "sqlworkbench:PutTab": {}, + "sqlworkbench:PutUserWorkspaceSettings": {}, + "sqlworkbench:UpdateAccountConnectionSettings": {}, + "sqlworkbench:UpdateAccountExportSettings": {}, + "sqlworkbench:UpdateAccountGeneralSettings": {}, + "sqlworkbench:UpdateAccountQSqlSettings": {}, + "sqlworkbench:UpdateFolder": {}, + "sqs:ListQueues": {}, + "ssm-contacts:ListContacts": {}, + "ssm-contacts:ListEngagements": {}, + "ssm-contacts:ListRotations": {}, + "ssm-guiconnect:CancelConnection": {}, + "ssm-guiconnect:GetConnection": {}, + "ssm-guiconnect:StartConnection": {}, + "ssm-incidents:CreateReplicationSet": {}, + "ssm-incidents:CreateResponsePlan": {}, + "ssm-incidents:ListIncidentRecords": {}, + "ssm-incidents:ListReplicationSets": {}, + "ssm-incidents:ListResponsePlans": {}, + "ssm-sap:BackupDatabase": {}, + "ssm-sap:DeleteResourcePermission": {}, + "ssm-sap:GetApplication": {}, + "ssm-sap:GetDatabase": {}, + "ssm-sap:GetOperation": {}, + "ssm-sap:GetResourcePermission": {}, + "ssm-sap:ListApplications": {}, + "ssm-sap:ListDatabases": {}, + "ssm-sap:ListOperations": {}, + "ssm-sap:ListTagsForResource": {}, + "ssm-sap:PutResourcePermission": {}, + "ssm-sap:RegisterApplication": {}, + "ssm-sap:RestoreDatabase": {}, + "ssm-sap:UpdateHANABackupSettings": {}, + "ssm:CancelCommand": {}, + "ssm:CreateActivation": {}, + "ssm:CreateMaintenanceWindow": {}, + "ssm:CreateOpsItem": {}, + "ssm:CreateOpsMetadata": {}, + "ssm:CreatePatchBaseline": {}, + "ssm:DeleteActivation": {}, + "ssm:DeleteInventory": {}, + "ssm:DescribeActivations": {}, + "ssm:DescribeAutomationExecutions": {}, + "ssm:DescribeAvailablePatches": {}, + "ssm:DescribeInstanceInformation": {}, + "ssm:DescribeInstancePatchStates": {}, + "ssm:DescribeInstancePatchStatesForPatchGroup": {}, + "ssm:DescribeInstancePatches": {}, + "ssm:DescribeInstanceProperties": {}, + "ssm:DescribeInventoryDeletions": {}, + "ssm:DescribeMaintenanceWindowExecutionTaskInvocations": {}, + "ssm:DescribeMaintenanceWindowSchedule": {}, + "ssm:DescribeMaintenanceWindows": {}, + "ssm:DescribeMaintenanceWindowsForTarget": {}, + "ssm:DescribeOpsItems": {}, + "ssm:DescribeParameters": {}, + "ssm:DescribePatchBaselines": {}, + "ssm:DescribePatchGroupState": {}, + "ssm:DescribePatchGroups": {}, + "ssm:DescribePatchProperties": {}, + "ssm:DescribeSessions": {}, + "ssm:GetCommandInvocation": {}, + "ssm:GetDeployablePatchSnapshotForInstance": {}, + "ssm:GetInventory": {}, + "ssm:GetInventorySchema": {}, + "ssm:GetMaintenanceWindowExecution": {}, + "ssm:GetMaintenanceWindowExecutionTask": {}, + "ssm:GetMaintenanceWindowExecutionTaskInvocation": {}, + "ssm:GetManifest": {}, + "ssm:ListAssociations": {}, + "ssm:ListCommandInvocations": {}, + "ssm:ListCommands": {}, + "ssm:ListComplianceItems": {}, + "ssm:ListComplianceSummaries": {}, + "ssm:ListDocuments": {}, + "ssm:ListInventoryEntries": {}, + "ssm:ListOpsItemEvents": {}, + "ssm:ListOpsItemRelatedItems": {}, + "ssm:ListOpsMetadata": {}, + "ssm:ListResourceComplianceSummaries": {}, + "ssm:ListResourceDataSync": {}, + "ssm:PutConfigurePackageResult": {}, + "ssm:PutInventory": {}, + "ssm:RegisterManagedInstance": {}, + "ssmmessages:CreateControlChannel": {}, + "ssmmessages:CreateDataChannel": {}, + "ssmmessages:OpenControlChannel": {}, + "ssmmessages:OpenDataChannel": {}, + "sso-directory:AddMemberToGroup": {}, + "sso-directory:CompleteVirtualMfaDeviceRegistration": {}, + "sso-directory:CompleteWebAuthnDeviceRegistration": {}, + "sso-directory:CreateAlias": {}, + "sso-directory:CreateBearerToken": {}, + "sso-directory:CreateExternalIdPConfigurationForDirectory": {}, + "sso-directory:CreateGroup": {}, + "sso-directory:CreateProvisioningTenant": {}, + "sso-directory:CreateUser": {}, + "sso-directory:DeleteBearerToken": {}, + "sso-directory:DeleteExternalIdPCertificate": {}, + "sso-directory:DeleteExternalIdPConfigurationForDirectory": {}, + "sso-directory:DeleteGroup": {}, + "sso-directory:DeleteMfaDeviceForUser": {}, + "sso-directory:DeleteProvisioningTenant": {}, + "sso-directory:DeleteUser": {}, + "sso-directory:DescribeDirectory": {}, + "sso-directory:DescribeGroup": {}, + "sso-directory:DescribeGroups": {}, + "sso-directory:DescribeProvisioningTenant": {}, + "sso-directory:DescribeUser": {}, + "sso-directory:DescribeUserByUniqueAttribute": {}, + "sso-directory:DescribeUsers": {}, + "sso-directory:DisableExternalIdPConfigurationForDirectory": {}, + "sso-directory:DisableUser": {}, + "sso-directory:EnableExternalIdPConfigurationForDirectory": {}, + "sso-directory:EnableUser": {}, + "sso-directory:GetAWSSPConfigurationForDirectory": {}, + "sso-directory:GetUserPoolInfo": {}, + "sso-directory:ImportExternalIdPCertificate": {}, + "sso-directory:IsMemberInGroup": {}, + "sso-directory:ListBearerTokens": {}, + "sso-directory:ListExternalIdPCertificates": {}, + "sso-directory:ListExternalIdPConfigurationsForDirectory": {}, + "sso-directory:ListGroupsForMember": {}, + "sso-directory:ListGroupsForUser": {}, + "sso-directory:ListMembersInGroup": {}, + "sso-directory:ListMfaDevicesForUser": {}, + "sso-directory:ListProvisioningTenants": {}, + "sso-directory:RemoveMemberFromGroup": {}, + "sso-directory:SearchGroups": {}, + "sso-directory:SearchUsers": {}, + "sso-directory:StartVirtualMfaDeviceRegistration": {}, + "sso-directory:StartWebAuthnDeviceRegistration": {}, + "sso-directory:UpdateExternalIdPConfigurationForDirectory": {}, + "sso-directory:UpdateGroup": {}, + "sso-directory:UpdateGroupDisplayName": {}, + "sso-directory:UpdateMfaDeviceForUser": {}, + "sso-directory:UpdatePassword": {}, + "sso-directory:UpdateUser": {}, + "sso-directory:UpdateUserName": {}, + "sso-directory:VerifyEmail": {}, + "sso:AssociateDirectory": {}, + "sso:AssociateProfile": {}, + "sso:CreateApplicationInstance": {}, + "sso:CreateApplicationInstanceCertificate": {}, + "sso:CreateManagedApplicationInstance": {}, + "sso:CreateProfile": {}, + "sso:CreateTrust": {}, + "sso:DeleteApplicationInstance": {}, + "sso:DeleteApplicationInstanceCertificate": {}, + "sso:DeleteManagedApplicationInstance": {}, + "sso:DeletePermissionsPolicy": {}, + "sso:DeleteProfile": {}, + "sso:DescribeDirectories": {}, + "sso:DescribePermissionsPolicies": {}, + "sso:DescribeRegisteredRegions": {}, + "sso:DescribeTrusts": {}, + "sso:DisassociateDirectory": {}, + "sso:DisassociateProfile": {}, + "sso:GetApplicationInstance": {}, + "sso:GetApplicationTemplate": {}, + "sso:GetManagedApplicationInstance": {}, + "sso:GetMfaDeviceManagementForDirectory": {}, + "sso:GetPermissionSet": {}, + "sso:GetPermissionsPolicy": {}, + "sso:GetProfile": {}, + "sso:GetSSOStatus": {}, + "sso:GetSharedSsoConfiguration": {}, + "sso:GetSsoConfiguration": {}, + "sso:GetTrust": {}, + "sso:ImportApplicationInstanceServiceProviderMetadata": {}, + "sso:ListApplicationInstanceCertificates": {}, + "sso:ListApplicationInstances": {}, + "sso:ListApplicationTemplates": {}, + "sso:ListApplications": {}, + "sso:ListDirectoryAssociations": {}, + "sso:ListInstances": {}, + "sso:ListProfileAssociations": {}, + "sso:ListProfiles": {}, + "sso:PutMfaDeviceManagementForDirectory": {}, + "sso:PutPermissionsPolicy": {}, + "sso:SearchGroups": {}, + "sso:SearchUsers": {}, + "sso:StartSSO": {}, + "sso:UpdateApplicationInstanceActiveCertificate": {}, + "sso:UpdateApplicationInstanceDisplayData": {}, + "sso:UpdateApplicationInstanceResponseConfiguration": {}, + "sso:UpdateApplicationInstanceResponseSchemaConfiguration": {}, + "sso:UpdateApplicationInstanceSecurityConfiguration": {}, + "sso:UpdateApplicationInstanceServiceProviderConfiguration": {}, + "sso:UpdateApplicationInstanceStatus": {}, + "sso:UpdateDirectoryAssociation": {}, + "sso:UpdateManagedApplicationInstanceStatus": {}, + "sso:UpdateProfile": {}, + "sso:UpdateSSOConfiguration": {}, + "sso:UpdateTrust": {}, + "states:InvokeHTTPEndpoint": {}, + "states:ListActivities": {}, + "states:ListStateMachines": {}, + "states:RevealSecrets": {}, + "states:SendTaskFailure": {}, + "states:SendTaskHeartbeat": {}, + "states:SendTaskSuccess": {}, + "states:TestState": {}, + "storagegateway:ActivateGateway": {}, + "storagegateway:CreateTapePool": {}, + "storagegateway:DeleteTapeArchive": {}, + "storagegateway:DescribeTapeArchives": {}, + "storagegateway:ListAutomaticTapeCreationPolicies": {}, + "storagegateway:ListFileShares": {}, + "storagegateway:ListFileSystemAssociations": {}, + "storagegateway:ListGateways": {}, + "storagegateway:ListTapePools": {}, + "storagegateway:ListTapes": {}, + "storagegateway:ListVolumes": {}, + "sts:DecodeAuthorizationMessage": {}, + "sts:GetAccessKeyInfo": {}, + "sts:GetCallerIdentity": {}, + "sts:GetServiceBearerToken": {}, + "sts:GetSessionToken": {}, + "support:AddAttachmentsToSet": {}, + "support:AddCommunicationToCase": {}, + "support:CreateCase": {}, + "support:DescribeAttachment": {}, + "support:DescribeCaseAttributes": {}, + "support:DescribeCases": {}, + "support:DescribeCommunication": {}, + "support:DescribeCommunications": {}, + "support:DescribeCreateCaseOptions": {}, + "support:DescribeIssueTypes": {}, + "support:DescribeServices": {}, + "support:DescribeSeverityLevels": {}, + "support:DescribeSupportLevel": {}, + "support:DescribeSupportedLanguages": {}, + "support:DescribeTrustedAdvisorCheckRefreshStatuses": {}, + "support:DescribeTrustedAdvisorCheckResult": {}, + "support:DescribeTrustedAdvisorCheckSummaries": {}, + "support:DescribeTrustedAdvisorChecks": {}, + "support:InitiateCallForCase": {}, + "support:InitiateChatForCase": {}, + "support:PutCaseAttributes": {}, + "support:RateCaseCommunication": {}, + "support:RefreshTrustedAdvisorCheck": {}, + "support:ResolveCase": {}, + "support:SearchForCases": {}, + "supportapp:CreateSlackChannelConfiguration": {}, + "supportapp:DeleteAccountAlias": {}, + "supportapp:DeleteSlackChannelConfiguration": {}, + "supportapp:DeleteSlackWorkspaceConfiguration": {}, + "supportapp:DescribeSlackChannels": {}, + "supportapp:GetAccountAlias": {}, + "supportapp:GetSlackOauthParameters": {}, + "supportapp:ListSlackChannelConfigurations": {}, + "supportapp:ListSlackWorkspaceConfigurations": {}, + "supportapp:PutAccountAlias": {}, + "supportapp:RedeemSlackOauthCode": {}, + "supportapp:RegisterSlackWorkspaceForOrganization": {}, + "supportapp:UpdateSlackChannelConfiguration": {}, + "supportplans:CreateSupportPlanSchedule": {}, + "supportplans:GetSupportPlan": {}, + "supportplans:GetSupportPlanUpdateStatus": {}, + "supportplans:StartSupportPlanUpdate": {}, + "sustainability:GetCarbonFootprintSummary": {}, + "swf:ListDomains": {}, + "swf:RegisterDomain": {}, + "synthetics:CreateCanary": {}, + "synthetics:CreateGroup": {}, + "synthetics:DescribeCanaries": {}, + "synthetics:DescribeCanariesLastRun": {}, + "synthetics:DescribeRuntimeVersions": {}, + "synthetics:ListGroups": {}, + "tag:DescribeReportCreation": {}, + "tag:GetComplianceSummary": {}, + "tag:GetResources": {}, + "tag:GetTagKeys": {}, + "tag:GetTagValues": {}, + "tag:StartReportCreation": {}, + "tag:TagResources": {}, + "tag:UntagResources": {}, + "tax:BatchPutTaxRegistration": {}, + "tax:DeleteTaxRegistration": {}, + "tax:GetExemptions": {}, + "tax:GetTaxInfoReportingDocument": {}, + "tax:GetTaxInheritance": {}, + "tax:GetTaxInterview": {}, + "tax:GetTaxRegistration": {}, + "tax:GetTaxRegistrationDocument": {}, + "tax:ListTaxRegistrations": {}, + "tax:PutTaxInheritance": {}, + "tax:PutTaxInterview": {}, + "tax:PutTaxRegistration": {}, + "tax:UpdateExemptions": {}, + "textract:AnalyzeDocument": {}, + "textract:AnalyzeExpense": {}, + "textract:AnalyzeID": {}, + "textract:CreateAdapter": {}, + "textract:DetectDocumentText": {}, + "textract:GetDocumentAnalysis": {}, + "textract:GetDocumentTextDetection": {}, + "textract:GetExpenseAnalysis": {}, + "textract:GetLendingAnalysis": {}, + "textract:GetLendingAnalysisSummary": {}, + "textract:ListAdapterVersions": {}, + "textract:ListAdapters": {}, + "textract:StartDocumentAnalysis": {}, + "textract:StartDocumentTextDetection": {}, + "textract:StartExpenseAnalysis": {}, + "textract:StartLendingAnalysis": {}, + "thinclient:CreateEnvironment": {}, + "thinclient:ListDeviceSessions": {}, + "thinclient:ListDevices": {}, + "thinclient:ListEnvironments": {}, + "thinclient:ListSoftwareSets": {}, + "thinclient:ListTagsForResource": {}, + "timestream:CancelQuery": {}, + "timestream:CreateScheduledQuery": {}, + "timestream:DescribeBatchLoadTask": {}, + "timestream:DescribeEndpoints": {}, + "timestream:GetAwsBackupStatus": {}, + "timestream:GetAwsRestoreStatus": {}, + "timestream:ListBatchLoadTasks": {}, + "timestream:ListDatabases": {}, + "timestream:ListScheduledQueries": {}, + "timestream:ResumeBatchLoadTask": {}, + "timestream:SelectValues": {}, + "tiros:CreateQuery": {}, + "tiros:ExtendQuery": {}, + "tiros:GetQueryAnswer": {}, + "tiros:GetQueryExplanation": {}, + "tiros:GetQueryExtensionAccounts": {}, + "tnb:ListTagsForResource": {}, + "transcribe:CreateCallAnalyticsCategory": {}, + "transcribe:CreateLanguageModel": {}, + "transcribe:CreateMedicalVocabulary": {}, + "transcribe:CreateVocabulary": {}, + "transcribe:CreateVocabularyFilter": {}, + "transcribe:DeleteCallAnalyticsCategory": {}, + "transcribe:DeleteCallAnalyticsJob": {}, + "transcribe:GetCallAnalyticsCategory": {}, + "transcribe:GetCallAnalyticsJob": {}, + "transcribe:ListCallAnalyticsCategories": {}, + "transcribe:ListCallAnalyticsJobs": {}, + "transcribe:ListLanguageModels": {}, + "transcribe:ListMedicalScribeJobs": {}, + "transcribe:ListMedicalTranscriptionJobs": {}, + "transcribe:ListMedicalVocabularies": {}, + "transcribe:ListTagsForResource": {}, + "transcribe:ListTranscriptionJobs": {}, + "transcribe:ListVocabularies": {}, + "transcribe:ListVocabularyFilters": {}, + "transcribe:StartCallAnalyticsJob": {}, + "transcribe:StartCallAnalyticsStreamTranscription": {}, + "transcribe:StartCallAnalyticsStreamTranscriptionWebSocket": {}, + "transcribe:StartMedicalScribeJob": {}, + "transcribe:StartMedicalStreamTranscription": {}, + "transcribe:StartMedicalStreamTranscriptionWebSocket": {}, + "transcribe:StartMedicalTranscriptionJob": {}, + "transcribe:StartStreamTranscription": {}, + "transcribe:StartStreamTranscriptionWebSocket": {}, + "transcribe:StartTranscriptionJob": {}, + "transcribe:TagResource": {}, + "transcribe:UntagResource": {}, + "transcribe:UpdateCallAnalyticsCategory": {}, + "transfer:CreateConnector": {}, + "transfer:CreateProfile": {}, + "transfer:CreateServer": {}, + "transfer:CreateWorkflow": {}, + "transfer:DescribeSecurityPolicy": {}, + "transfer:ImportCertificate": {}, + "transfer:ListCertificates": {}, + "transfer:ListConnectors": {}, + "transfer:ListProfiles": {}, + "transfer:ListSecurityPolicies": {}, + "transfer:ListServers": {}, + "transfer:ListWorkflows": {}, + "transfer:UpdateAccess": {}, + "translate:DescribeTextTranslationJob": {}, + "translate:ListLanguages": {}, + "translate:ListParallelData": {}, + "translate:ListTerminologies": {}, + "translate:ListTextTranslationJobs": {}, + "translate:StopTextTranslationJob": {}, + "trustedadvisor:CreateEngagement": {}, + "trustedadvisor:CreateEngagementAttachment": {}, + "trustedadvisor:CreateEngagementCommunication": {}, + "trustedadvisor:DeleteNotificationConfigurationForDelegatedAdmin": {}, + "trustedadvisor:DescribeAccount": {}, + "trustedadvisor:DescribeAccountAccess": {}, + "trustedadvisor:DescribeChecks": {}, + "trustedadvisor:DescribeNotificationConfigurations": {}, + "trustedadvisor:DescribeNotificationPreferences": {}, + "trustedadvisor:DescribeOrganization": {}, + "trustedadvisor:DescribeOrganizationAccounts": {}, + "trustedadvisor:DescribeReports": {}, + "trustedadvisor:DescribeRisk": {}, + "trustedadvisor:DescribeRiskResources": {}, + "trustedadvisor:DescribeRisks": {}, + "trustedadvisor:DescribeServiceMetadata": {}, + "trustedadvisor:DownloadRisk": {}, + "trustedadvisor:GenerateReport": {}, + "trustedadvisor:GetEngagement": {}, + "trustedadvisor:GetEngagementAttachment": {}, + "trustedadvisor:GetEngagementType": {}, + "trustedadvisor:GetOrganizationRecommendation": {}, + "trustedadvisor:GetRecommendation": {}, + "trustedadvisor:ListAccountsForParent": {}, + "trustedadvisor:ListChecks": {}, + "trustedadvisor:ListEngagementCommunications": {}, + "trustedadvisor:ListEngagementTypes": {}, + "trustedadvisor:ListEngagements": {}, + "trustedadvisor:ListOrganizationRecommendationAccounts": {}, + "trustedadvisor:ListOrganizationRecommendationResources": {}, + "trustedadvisor:ListOrganizationRecommendations": {}, + "trustedadvisor:ListOrganizationalUnitsForParent": {}, + "trustedadvisor:ListRecommendationResources": {}, + "trustedadvisor:ListRecommendations": {}, + "trustedadvisor:ListRoots": {}, + "trustedadvisor:SetAccountAccess": {}, + "trustedadvisor:SetOrganizationAccess": {}, + "trustedadvisor:UpdateEngagement": {}, + "trustedadvisor:UpdateEngagementStatus": {}, + "trustedadvisor:UpdateNotificationConfigurations": {}, + "trustedadvisor:UpdateNotificationPreferences": {}, + "trustedadvisor:UpdateOrganizationRecommendationLifecycle": {}, + "trustedadvisor:UpdateRecommendationLifecycle": {}, + "trustedadvisor:UpdateRiskStatus": {}, + "ts:ListExecutions": {}, + "ts:ListTools": {}, + "ts:StartExecution": {}, + "vendor-insights:CreateDataSource": {}, + "vendor-insights:CreateSecurityProfile": {}, + "vendor-insights:GetProfileAccessTerms": {}, + "vendor-insights:ListDataSources": {}, + "vendor-insights:ListEntitledSecurityProfiles": {}, + "vendor-insights:ListSecurityProfiles": {}, + "verified-access:AllowVerifiedAccess": {}, + "verifiedpermissions:CreatePolicyStore": {}, + "verifiedpermissions:ListPolicyStores": {}, + "voiceid:CreateDomain": {}, + "voiceid:DescribeComplianceConsent": {}, + "voiceid:ListDomains": {}, + "voiceid:RegisterComplianceConsent": {}, + "vpc-lattice:ListAccessLogSubscriptions": {}, + "vpc-lattice:ListListeners": {}, + "vpc-lattice:ListRules": {}, + "vpc-lattice:ListServiceNetworkServiceAssociations": {}, + "vpc-lattice:ListServiceNetworkVpcAssociations": {}, + "vpc-lattice:ListServiceNetworks": {}, + "vpc-lattice:ListServices": {}, + "vpc-lattice:ListTagsForResource": {}, + "vpc-lattice:ListTargetGroups": {}, + "waf-regional:GetChangeToken": {}, + "waf-regional:GetChangeTokenStatus": {}, + "waf-regional:ListActivatedRulesInRuleGroup": {}, + "waf-regional:ListByteMatchSets": {}, + "waf-regional:ListGeoMatchSets": {}, + "waf-regional:ListIPSets": {}, + "waf-regional:ListLoggingConfigurations": {}, + "waf-regional:ListRateBasedRules": {}, + "waf-regional:ListRegexMatchSets": {}, + "waf-regional:ListRegexPatternSets": {}, + "waf-regional:ListRuleGroups": {}, + "waf-regional:ListRules": {}, + "waf-regional:ListSizeConstraintSets": {}, + "waf-regional:ListSqlInjectionMatchSets": {}, + "waf-regional:ListSubscribedRuleGroups": {}, + "waf-regional:ListWebACLs": {}, + "waf-regional:ListXssMatchSets": {}, + "waf:GetChangeToken": {}, + "waf:GetChangeTokenStatus": {}, + "waf:ListActivatedRulesInRuleGroup": {}, + "waf:ListByteMatchSets": {}, + "waf:ListGeoMatchSets": {}, + "waf:ListIPSets": {}, + "waf:ListLoggingConfigurations": {}, + "waf:ListRateBasedRules": {}, + "waf:ListRegexMatchSets": {}, + "waf:ListRegexPatternSets": {}, + "waf:ListRuleGroups": {}, + "waf:ListRules": {}, + "waf:ListSizeConstraintSets": {}, + "waf:ListSqlInjectionMatchSets": {}, + "waf:ListSubscribedRuleGroups": {}, + "waf:ListWebACLs": {}, + "waf:ListXssMatchSets": {}, + "wafv2:CheckCapacity": {}, + "wafv2:CreateAPIKey": {}, + "wafv2:DescribeAllManagedProducts": {}, + "wafv2:DescribeManagedProductsByVendor": {}, + "wafv2:DescribeManagedRuleGroup": {}, + "wafv2:GenerateMobileSdkReleaseUrl": {}, + "wafv2:GetDecryptedAPIKey": {}, + "wafv2:GetMobileSdkRelease": {}, + "wafv2:ListAPIKeys": {}, + "wafv2:ListAvailableManagedRuleGroupVersions": {}, + "wafv2:ListAvailableManagedRuleGroups": {}, + "wafv2:ListIPSets": {}, + "wafv2:ListLoggingConfigurations": {}, + "wafv2:ListManagedRuleSets": {}, + "wafv2:ListMobileSdkReleases": {}, + "wafv2:ListRegexPatternSets": {}, + "wafv2:ListRuleGroups": {}, + "wafv2:ListWebACLs": {}, + "wam:AuthenticatePackager": {}, + "wellarchitected:CreateProfile": {}, + "wellarchitected:CreateReviewTemplate": {}, + "wellarchitected:CreateWorkload": {}, + "wellarchitected:GetConsolidatedReport": {}, + "wellarchitected:GetProfileTemplate": {}, + "wellarchitected:ImportLens": {}, + "wellarchitected:ListLenses": {}, + "wellarchitected:ListNotifications": {}, + "wellarchitected:ListProfileNotifications": {}, + "wellarchitected:ListProfiles": {}, + "wellarchitected:ListReviewTemplates": {}, + "wellarchitected:ListShareInvitations": {}, + "wellarchitected:ListWorkloads": {}, + "wellarchitected:UpdateGlobalSettings": {}, + "wellarchitected:UpdateShareInvitation": {}, + "wickr:CreateNetwork": {}, + "wickr:ListNetworks": {}, + "wickr:ListTagsForResource": {}, + "wisdom:CreateAssistant": {}, + "wisdom:CreateKnowledgeBase": {}, + "wisdom:ListAssistants": {}, + "wisdom:ListKnowledgeBases": {}, + "wisdom:ListTagsForResource": {}, + "workdocs:AbortDocumentVersionUpload": {}, + "workdocs:ActivateUser": {}, + "workdocs:AddNotificationPermissions": {}, + "workdocs:AddResourcePermissions": {}, + "workdocs:AddUserToGroup": {}, + "workdocs:CheckAlias": {}, + "workdocs:CreateComment": {}, + "workdocs:CreateCustomMetadata": {}, + "workdocs:CreateFolder": {}, + "workdocs:CreateInstance": {}, + "workdocs:CreateLabels": {}, + "workdocs:CreateNotificationSubscription": {}, + "workdocs:CreateUser": {}, + "workdocs:DeactivateUser": {}, + "workdocs:DeleteComment": {}, + "workdocs:DeleteCustomMetadata": {}, + "workdocs:DeleteDocument": {}, + "workdocs:DeleteDocumentVersion": {}, + "workdocs:DeleteFolder": {}, + "workdocs:DeleteFolderContents": {}, + "workdocs:DeleteInstance": {}, + "workdocs:DeleteLabels": {}, + "workdocs:DeleteNotificationPermissions": {}, + "workdocs:DeleteNotificationSubscription": {}, + "workdocs:DeleteUser": {}, + "workdocs:DeregisterDirectory": {}, + "workdocs:DescribeActivities": {}, + "workdocs:DescribeAvailableDirectories": {}, + "workdocs:DescribeComments": {}, + "workdocs:DescribeDocumentVersions": {}, + "workdocs:DescribeFolderContents": {}, + "workdocs:DescribeGroups": {}, + "workdocs:DescribeInstances": {}, + "workdocs:DescribeNotificationPermissions": {}, + "workdocs:DescribeNotificationSubscriptions": {}, + "workdocs:DescribeResourcePermissions": {}, + "workdocs:DescribeRootFolders": {}, + "workdocs:DescribeUsers": {}, + "workdocs:DownloadDocumentVersion": {}, + "workdocs:GetCurrentUser": {}, + "workdocs:GetDocument": {}, + "workdocs:GetDocumentPath": {}, + "workdocs:GetDocumentVersion": {}, + "workdocs:GetFolder": {}, + "workdocs:GetFolderPath": {}, + "workdocs:GetGroup": {}, + "workdocs:GetResources": {}, + "workdocs:InitiateDocumentVersionUpload": {}, + "workdocs:RegisterDirectory": {}, + "workdocs:RemoveAllResourcePermissions": {}, + "workdocs:RemoveResourcePermission": {}, + "workdocs:RestoreDocumentVersions": {}, + "workdocs:SearchResources": {}, + "workdocs:UpdateDocument": {}, + "workdocs:UpdateDocumentVersion": {}, + "workdocs:UpdateFolder": {}, + "workdocs:UpdateInstanceAlias": {}, + "workdocs:UpdateUser": {}, + "workdocs:UpdateUserAdministrativeSettings": {}, + "worklink:CreateFleet": {}, + "worklink:ListFleets": {}, + "workmail:CreateOrganization": {}, + "workmail:DescribeDirectories": {}, + "workmail:DescribeKmsKeys": {}, + "workmail:DescribeOrganizations": {}, + "workmail:ListOrganizations": {}, + "workspaces-web:CreateBrowserSettings": {}, + "workspaces-web:CreateIpAccessSettings": {}, + "workspaces-web:CreateNetworkSettings": {}, + "workspaces-web:CreatePortal": {}, + "workspaces-web:CreateTrustStore": {}, + "workspaces-web:CreateUserAccessLoggingSettings": {}, + "workspaces-web:CreateUserSettings": {}, + "workspaces-web:ListBrowserSettings": {}, + "workspaces-web:ListIpAccessSettings": {}, + "workspaces-web:ListNetworkSettings": {}, + "workspaces-web:ListPortals": {}, + "workspaces-web:ListTagsForResource": {}, + "workspaces-web:ListTrustStoreCertificates": {}, + "workspaces-web:ListTrustStores": {}, + "workspaces-web:ListUserAccessLoggingSettings": {}, + "workspaces-web:ListUserSettings": {}, + "workspaces:CreateConnectionAlias": {}, + "workspaces:CreateIpGroup": {}, + "workspaces:CreateTags": {}, + "workspaces:DeleteTags": {}, + "workspaces:DescribeAccount": {}, + "workspaces:DescribeAccountModifications": {}, + "workspaces:DescribeApplications": {}, + "workspaces:DescribeConnectionAliases": {}, + "workspaces:DescribeTags": {}, + "workspaces:DescribeWorkspaceBundles": {}, + "workspaces:DescribeWorkspaceDirectories": {}, + "workspaces:DescribeWorkspaceImages": {}, + "workspaces:DescribeWorkspaces": {}, + "workspaces:DescribeWorkspacesConnectionStatus": {}, + "workspaces:ImportWorkspaceImage": {}, + "workspaces:ListAvailableManagementCidrRanges": {}, + "workspaces:ModifyAccount": {}, + "xray:BatchGetTraceSummaryById": {}, + "xray:BatchGetTraces": {}, + "xray:DeleteResourcePolicy": {}, + "xray:GetDistinctTraceGraphs": {}, + "xray:GetEncryptionConfig": {}, + "xray:GetGroups": {}, + "xray:GetInsight": {}, + "xray:GetInsightEvents": {}, + "xray:GetInsightImpactGraph": {}, + "xray:GetInsightSummaries": {}, + "xray:GetSamplingRules": {}, + "xray:GetSamplingStatisticSummaries": {}, + "xray:GetSamplingTargets": {}, + "xray:GetServiceGraph": {}, + "xray:GetTimeSeriesServiceStatistics": {}, + "xray:GetTraceGraph": {}, + "xray:GetTraceSummaries": {}, + "xray:Link": {}, + "xray:ListResourcePolicies": {}, + "xray:PutEncryptionConfig": {}, + "xray:PutResourcePolicy": {}, + "xray:PutTelemetryRecords": {}, + "xray:PutTraceSegments": {}, +} diff --git a/pkg/iac/providers/aws/iam/iam.go b/pkg/iac/providers/aws/iam/iam.go index f12a5eae845f..7589c7cbce48 100644 --- a/pkg/iac/providers/aws/iam/iam.go +++ b/pkg/iac/providers/aws/iam/iam.go @@ -34,10 +34,10 @@ type Document struct { HasRefs bool } -func (d Document) ToRego() interface{} { +func (d Document) ToRego() any { m := d.Metadata doc, _ := d.Parsed.MarshalJSON() - input := map[string]interface{}{ + input := map[string]any{ "filepath": m.Range().GetFilename(), "startline": m.Range().GetStartLine(), "endline": m.Range().GetEndLine(), diff --git a/pkg/iac/providers/dockerfile/dockerfile.go b/pkg/iac/providers/dockerfile/dockerfile.go index aed723989632..84b10c42fb7e 100644 --- a/pkg/iac/providers/dockerfile/dockerfile.go +++ b/pkg/iac/providers/dockerfile/dockerfile.go @@ -18,14 +18,14 @@ type Stage struct { Commands []Command } -func (d Dockerfile) ToRego() interface{} { - return map[string]interface{}{ +func (d Dockerfile) ToRego() any { + return map[string]any{ "Stages": convert.SliceToRego(reflect.ValueOf(d.Stages)), } } -func (s Stage) ToRego() interface{} { - return map[string]interface{}{ +func (s Stage) ToRego() any { + return map[string]any{ "Name": s.Name, "Commands": convert.SliceToRego(reflect.ValueOf(s.Commands)), } @@ -45,8 +45,8 @@ type Command struct { EndLine int } -func (c Command) ToRego() interface{} { - return map[string]interface{}{ +func (c Command) ToRego() any { + return map[string]any{ "Cmd": c.Cmd, "SubCmd": c.SubCmd, "Flags": c.Flags, diff --git a/pkg/iac/rego/build.go b/pkg/iac/rego/build.go index c593f6247ea0..a56ee042fb52 100644 --- a/pkg/iac/rego/build.go +++ b/pkg/iac/rego/build.go @@ -15,7 +15,7 @@ import ( func BuildSchemaSetFromPolicies(policies map[string]*ast.Module, paths []string, fsys fs.FS) (*ast.SchemaSet, bool, error) { schemaSet := ast.NewSchemaSet() - schemaSet.Put(ast.MustParseRef("schema.input"), make(map[string]interface{})) // for backwards compat only + schemaSet.Put(ast.MustParseRef("schema.input"), make(map[string]any)) // for backwards compat only var customFound bool for _, policy := range policies { diff --git a/pkg/iac/rego/convert/anonymous.go b/pkg/iac/rego/convert/anonymous.go index 3563b0fccfc8..069ea0d06d88 100644 --- a/pkg/iac/rego/convert/anonymous.go +++ b/pkg/iac/rego/convert/anonymous.go @@ -6,7 +6,7 @@ import ( var converterInterface = reflect.TypeOf((*Converter)(nil)).Elem() -func anonymousToRego(inputValue reflect.Value) interface{} { +func anonymousToRego(inputValue reflect.Value) any { if inputValue.IsZero() { return nil diff --git a/pkg/iac/rego/convert/converter.go b/pkg/iac/rego/convert/converter.go index e132d6875aa2..880050c0eb6a 100644 --- a/pkg/iac/rego/convert/converter.go +++ b/pkg/iac/rego/convert/converter.go @@ -1,5 +1,5 @@ package convert type Converter interface { - ToRego() interface{} + ToRego() any } diff --git a/pkg/iac/rego/convert/slice.go b/pkg/iac/rego/convert/slice.go index 8bb68a7fb551..02c7dcff92a0 100644 --- a/pkg/iac/rego/convert/slice.go +++ b/pkg/iac/rego/convert/slice.go @@ -4,7 +4,7 @@ import ( "reflect" ) -func SliceToRego(inputValue reflect.Value) []interface{} { +func SliceToRego(inputValue reflect.Value) []any { // make sure we have a struct literal for inputValue.Type().Kind() == reflect.Ptr { @@ -17,7 +17,7 @@ func SliceToRego(inputValue reflect.Value) []interface{} { panic("not a slice") } - output := make([]interface{}, inputValue.Len()) + output := make([]any, inputValue.Len()) for i := 0; i < inputValue.Len(); i++ { val := inputValue.Index(i) diff --git a/pkg/iac/rego/convert/slice_test.go b/pkg/iac/rego/convert/slice_test.go index 7d8125f96cd2..e30c200142b2 100644 --- a/pkg/iac/rego/convert/slice_test.go +++ b/pkg/iac/rego/convert/slice_test.go @@ -21,7 +21,7 @@ func Test_SliceConversion(t *testing.T) { } input[0].Z.A = 123 converted := SliceToRego(reflect.ValueOf(input)) - assert.Equal(t, []interface{}{map[string]interface{}{"z": make(map[string]interface{})}}, converted) + assert.Equal(t, []any{map[string]any{"z": make(map[string]any)}}, converted) } func Test_SliceTypesConversion(t *testing.T) { @@ -30,8 +30,8 @@ func Test_SliceTypesConversion(t *testing.T) { types.String("test2", types.NewTestMetadata()), } converted := SliceToRego(reflect.ValueOf(input)) - assert.Equal(t, []interface{}{ - map[string]interface{}{ + assert.Equal(t, []any{ + map[string]any{ "value": "test1", "filepath": "test.test", "startline": 123, @@ -42,7 +42,7 @@ func Test_SliceTypesConversion(t *testing.T) { "fskey": "", "resource": "", }, - map[string]interface{}{ + map[string]any{ "value": "test2", "filepath": "test.test", "startline": 123, diff --git a/pkg/iac/rego/convert/struct.go b/pkg/iac/rego/convert/struct.go index c4819b96815e..0ca5cffae97b 100644 --- a/pkg/iac/rego/convert/struct.go +++ b/pkg/iac/rego/convert/struct.go @@ -13,7 +13,7 @@ type metadataProvider interface { var metadataInterface = reflect.TypeOf((*metadataProvider)(nil)).Elem() -func StructToRego(inputValue reflect.Value) map[string]interface{} { +func StructToRego(inputValue reflect.Value) map[string]any { // make sure we have a struct literal for inputValue.Type().Kind() == reflect.Ptr || inputValue.Type().Kind() == reflect.Interface { @@ -26,7 +26,7 @@ func StructToRego(inputValue reflect.Value) map[string]interface{} { panic("not a struct") } - output := make(map[string]interface{}, inputValue.NumField()) + output := make(map[string]any, inputValue.NumField()) for i := 0; i < inputValue.NumField(); i++ { field := inputValue.Field(i) diff --git a/pkg/iac/rego/convert/struct_test.go b/pkg/iac/rego/convert/struct_test.go index 1d0774d48247..2147110b4a26 100644 --- a/pkg/iac/rego/convert/struct_test.go +++ b/pkg/iac/rego/convert/struct_test.go @@ -17,5 +17,5 @@ func Test_StructConversion(t *testing.T) { }{} input.Z.A = 123 converted := StructToRego(reflect.ValueOf(input)) - assert.Equal(t, map[string]interface{}{"z": make(map[string]interface{})}, converted) + assert.Equal(t, map[string]any{"z": make(map[string]any)}, converted) } diff --git a/pkg/iac/rego/metadata.go b/pkg/iac/rego/metadata.go index 5699276ad054..dd2c1f104fcf 100644 --- a/pkg/iac/rego/metadata.go +++ b/pkg/iac/rego/metadata.go @@ -128,7 +128,7 @@ func (sm *StaticMetadata) Update(meta map[string]any) error { func (sm *StaticMetadata) updateAliases(meta map[string]any) { if raw, ok := meta["aliases"]; ok { - if aliases, ok := raw.([]interface{}); ok { + if aliases, ok := raw.([]any); ok { for _, a := range aliases { sm.Aliases = append(sm.Aliases, fmt.Sprintf("%s", a)) } @@ -156,10 +156,10 @@ func (sm *StaticMetadata) FromAnnotations(annotations *ast.Annotations) error { return nil } -func NewEngineMetadata(schema string, meta map[string]interface{}) (*scan.EngineMetadata, error) { - var sMap map[string]interface{} +func NewEngineMetadata(schema string, meta map[string]any) (*scan.EngineMetadata, error) { + var sMap map[string]any if raw, ok := meta[schema]; ok { - sMap, ok = raw.(map[string]interface{}) + sMap, ok = raw.(map[string]any) if !ok { return nil, fmt.Errorf("failed to parse %s metadata: not an object", schema) } @@ -297,7 +297,7 @@ func (m *MetadataRetriever) RetrieveMetadata(ctx context.Context, module *ast.Mo return nil, fmt.Errorf("failed to parse metadata: unexpected expression length") } expression := set[0].Expressions[0] - meta, ok := expression.Value.(map[string]interface{}) + meta, ok := expression.Value.(map[string]any) if !ok { return nil, fmt.Errorf("failed to parse metadata: not an object") } @@ -317,12 +317,12 @@ func (m *MetadataRetriever) queryInputOptions(ctx context.Context, module *ast.M Selectors: nil, } - var metadata map[string]interface{} + var metadata map[string]any // read metadata from official rego annotations if possible if annotation := m.findPackageAnnotations(module); annotation != nil && annotation.Custom != nil { if input, ok := annotation.Custom["input"]; ok { - if mapped, ok := input.(map[string]interface{}); ok { + if mapped, ok := input.(map[string]any); ok { metadata = mapped } } @@ -349,7 +349,7 @@ func (m *MetadataRetriever) queryInputOptions(ctx context.Context, module *ast.M return options } expression := set[0].Expressions[0] - meta, ok := expression.Value.(map[string]interface{}) + meta, ok := expression.Value.(map[string]any) if !ok { return options } @@ -363,10 +363,10 @@ func (m *MetadataRetriever) queryInputOptions(ctx context.Context, module *ast.M } if raw, ok := metadata["selector"]; ok { - if each, ok := raw.([]interface{}); ok { + if each, ok := raw.([]any); ok { for _, rawSelector := range each { var selector Selector - if selectorMap, ok := rawSelector.(map[string]interface{}); ok { + if selectorMap, ok := rawSelector.(map[string]any); ok { if rawType, ok := selectorMap["type"]; ok { selector.Type = fmt.Sprintf("%s", rawType) // handle backward compatibility for "defsec" source type which is now "cloud" @@ -374,9 +374,9 @@ func (m *MetadataRetriever) queryInputOptions(ctx context.Context, module *ast.M selector.Type = string(iacTypes.SourceCloud) } } - if subType, ok := selectorMap["subtypes"].([]interface{}); ok { + if subType, ok := selectorMap["subtypes"].([]any); ok { for _, subT := range subType { - if st, ok := subT.(map[string]interface{}); ok { + if st, ok := subT.(map[string]any); ok { s := SubType{} _ = mapstructure.Decode(st, &s) selector.Subtypes = append(selector.Subtypes, s) diff --git a/pkg/iac/rego/metadata_test.go b/pkg/iac/rego/metadata_test.go index c2b38ba79740..4ef5a7173062 100644 --- a/pkg/iac/rego/metadata_test.go +++ b/pkg/iac/rego/metadata_test.go @@ -138,8 +138,8 @@ func Test_UpdateStaticMetadata(t *testing.T) { } func Test_getEngineMetadata(t *testing.T) { - inputSchema := map[string]interface{}{ - "terraform": map[string]interface{}{ + inputSchema := map[string]any{ + "terraform": map[string]any{ "good_examples": `resource "aws_cloudtrail" "good_example" { is_multi_region_trail = true @@ -154,7 +154,7 @@ func Test_getEngineMetadata(t *testing.T) { } }`, }, - "cloud_formation": map[string]interface{}{"good_examples": `--- + "cloud_formation": map[string]any{"good_examples": `--- Resources: GoodExample: Type: AWS::CloudTrail::Trail diff --git a/pkg/iac/rego/result.go b/pkg/iac/rego/result.go index 9217c15f5479..c4045705249c 100644 --- a/pkg/iac/rego/result.go +++ b/pkg/iac/rego/result.go @@ -43,19 +43,19 @@ func (r regoResult) GetMetadata() iacTypes.Metadata { return m } -func (r regoResult) GetRawValue() interface{} { +func (r regoResult) GetRawValue() any { return nil } -func parseResult(raw interface{}) *regoResult { +func parseResult(raw any) *regoResult { var result regoResult result.Managed = true switch val := raw.(type) { - case []interface{}: + case []any: var msg string for _, item := range val { switch raw := item.(type) { - case map[string]interface{}: + case map[string]any: result = parseCause(raw) case string: msg = raw @@ -64,7 +64,7 @@ func parseResult(raw interface{}) *regoResult { result.Message = msg case string: result.Message = val - case map[string]interface{}: + case map[string]any: result = parseCause(val) default: result.Message = "Rego check resulted in DENY" @@ -72,7 +72,7 @@ func parseResult(raw interface{}) *regoResult { return &result } -func parseCause(cause map[string]interface{}) regoResult { +func parseCause(cause map[string]any) regoResult { var result regoResult result.Managed = true if msg, ok := cause["msg"]; ok { @@ -107,7 +107,7 @@ func parseCause(cause map[string]interface{}) regoResult { } } if parent, ok := cause["parent"]; ok { - if m, ok := parent.(map[string]interface{}); ok { + if m, ok := parent.(map[string]any); ok { parentResult := parseCause(m) result.Parent = &parentResult } @@ -115,7 +115,7 @@ func parseCause(cause map[string]interface{}) regoResult { return result } -func parseLineNumber(raw interface{}) int { +func parseLineNumber(raw any) int { str := fmt.Sprintf("%s", raw) n, _ := strconv.Atoi(str) return n @@ -126,9 +126,9 @@ func (s *Scanner) convertResults(set rego.ResultSet, input Input, namespace, rul offset := 0 if input.Contents != nil { - if xx, ok := input.Contents.(map[string]interface{}); ok { + if xx, ok := input.Contents.(map[string]any); ok { if md, ok := xx["__defsec_metadata"]; ok { - if md2, ok := md.(map[string]interface{}); ok { + if md2, ok := md.(map[string]any); ok { if sl, ok := md2["offset"]; ok { offset, _ = sl.(int) } @@ -138,9 +138,9 @@ func (s *Scanner) convertResults(set rego.ResultSet, input Input, namespace, rul } for _, result := range set { for _, expression := range result.Expressions { - values, ok := expression.Value.([]interface{}) + values, ok := expression.Value.([]any) if !ok { - values = []interface{}{expression.Value} + values = []any{expression.Value} } for _, value := range values { diff --git a/pkg/iac/rego/result_test.go b/pkg/iac/rego/result_test.go index a3ee4e57c900..fe1b1df846aa 100644 --- a/pkg/iac/rego/result_test.go +++ b/pkg/iac/rego/result_test.go @@ -9,7 +9,7 @@ import ( func Test_parseResult(t *testing.T) { var testCases = []struct { name string - input interface{} + input any want regoResult }{ { @@ -30,7 +30,7 @@ func Test_parseResult(t *testing.T) { }, { name: "strings", - input: []interface{}{"message"}, + input: []any{"message"}, want: regoResult{ Managed: true, Message: "message", @@ -38,9 +38,9 @@ func Test_parseResult(t *testing.T) { }, { name: "maps", - input: []interface{}{ + input: []any{ "message", - map[string]interface{}{ + map[string]any{ "filepath": "a.out", }, }, @@ -52,7 +52,7 @@ func Test_parseResult(t *testing.T) { }, { name: "map", - input: map[string]interface{}{ + input: map[string]any{ "msg": "message", "filepath": "a.out", "fskey": "abcd", @@ -77,9 +77,9 @@ func Test_parseResult(t *testing.T) { }, { name: "parent", - input: map[string]interface{}{ + input: map[string]any{ "msg": "child", - "parent": map[string]interface{}{ + "parent": map[string]any{ "msg": "parent", }, }, diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go index ceed9bd7ae6f..723f4c02181b 100644 --- a/pkg/iac/rego/scanner.go +++ b/pkg/iac/rego/scanner.go @@ -41,7 +41,7 @@ type Scanner struct { dataFS fs.FS frameworks []framework.Framework spec string - inputSchema interface{} // unmarshalled into this from a json schema document + inputSchema any // unmarshalled into this from a json schema document sourceType types.Source includeDeprecatedChecks bool @@ -71,7 +71,7 @@ func (s *Scanner) SetUseEmbeddedPolicies(b bool) { // handled externally } -func (s *Scanner) trace(heading string, input interface{}) { +func (s *Scanner) trace(heading string, input any) { if s.traceWriter == nil { return } @@ -212,9 +212,9 @@ func (s *Scanner) runQuery(ctx context.Context, query string, input ast.Value, d } type Input struct { - Path string `json:"path"` - FS fs.FS `json:"-"` - Contents interface{} `json:"contents"` + Path string `json:"path"` + FS fs.FS `json:"-"` + Contents any `json:"contents"` } func GetInputsContents(inputs []Input) []any { @@ -303,14 +303,14 @@ func isPolicyWithSubtype(sourceType types.Source) bool { return false } -func checkSubtype(ii map[string]interface{}, provider string, subTypes []SubType) bool { +func checkSubtype(ii map[string]any, provider string, subTypes []SubType) bool { if len(subTypes) == 0 { return true } for _, st := range subTypes { switch services := ii[provider].(type) { - case map[string]interface{}: // cloud + case map[string]any: for service := range services { if (service == st.Service) && (st.Provider == provider) { return true @@ -329,7 +329,7 @@ func checkSubtype(ii map[string]interface{}, provider string, subTypes []SubType func isPolicyApplicable(staticMetadata *StaticMetadata, inputs ...Input) bool { for _, input := range inputs { - if ii, ok := input.Contents.(map[string]interface{}); ok { + if ii, ok := input.Contents.(map[string]any); ok { for provider := range ii { // TODO(simar): Add other providers if !strings.Contains(strings.Join([]string{"kind", "aws", "azure"}, ","), provider) { diff --git a/pkg/iac/rego/scanner_test.go b/pkg/iac/rego/scanner_test.go index d820fe805969..67a80f51c2fd 100644 --- a/pkg/iac/rego/scanner_test.go +++ b/pkg/iac/rego/scanner_test.go @@ -51,7 +51,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, FS: srcFS, @@ -86,7 +86,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, FS: srcFS, @@ -121,7 +121,7 @@ warn { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -153,7 +153,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": false, }, }) @@ -196,7 +196,7 @@ exception[ns] { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -244,7 +244,7 @@ exception[ns] { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -281,7 +281,7 @@ exception[rules] { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -317,7 +317,7 @@ exception[rules] { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -351,7 +351,7 @@ deny_evil { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -382,7 +382,7 @@ deny[msg] { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -420,7 +420,7 @@ deny[res] { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -462,7 +462,7 @@ deny[res] { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -516,7 +516,7 @@ deny[res] { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -565,7 +565,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -599,7 +599,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -630,7 +630,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -665,7 +665,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -699,7 +699,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "evil": true, }, }) @@ -737,7 +737,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "text": "dynamic", }, }) @@ -770,7 +770,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "text": "test", }, }) @@ -817,7 +817,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "text": "test", }, }) @@ -1077,7 +1077,7 @@ deny { results, err := scanner.ScanInput(context.TODO(), Input{ Path: "/evil.lol", - Contents: map[string]interface{}{ + Contents: map[string]any{ "text": "test", }, }) diff --git a/pkg/iac/rego/schemas/builder.go b/pkg/iac/rego/schemas/builder.go index 76edec4163a8..5ae395423624 100644 --- a/pkg/iac/rego/schemas/builder.go +++ b/pkg/iac/rego/schemas/builder.go @@ -232,10 +232,10 @@ func (b *builder) readSlice(name string, parent, inputType reflect.Type, indent return prop, nil } -func (b *builder) readRego(def *Property, name string, parent, typ reflect.Type, raw interface{}, indent int) error { +func (b *builder) readRego(def *Property, name string, parent, typ reflect.Type, raw any, indent int) error { switch cast := raw.(type) { - case map[string]interface{}: + case map[string]any: def.Type = "object" for k, v := range cast { child := &Property{ diff --git a/pkg/iac/scan/result.go b/pkg/iac/scan/result.go index d9924c6aaeef..3109ae00b641 100644 --- a/pkg/iac/scan/result.go +++ b/pkg/iac/scan/result.go @@ -149,7 +149,7 @@ type Results []Result type MetadataProvider interface { GetMetadata() iacTypes.Metadata - GetRawValue() interface{} + GetRawValue() any } func (r *Results) GetPassed() Results { @@ -177,7 +177,7 @@ func (r *Results) filterStatus(status Status) Results { return filtered } -func (r *Results) Add(description string, source interface{}) { +func (r *Results) Add(description string, source any) { result := Result{ description: description, } @@ -207,7 +207,7 @@ func (r *Results) AddRego(description, namespace, rule string, traces []string, *r = append(*r, result) } -func (r *Results) AddPassed(source interface{}, descriptions ...string) { +func (r *Results) AddPassed(source any, descriptions ...string) { res := Result{ description: strings.Join(descriptions, " "), status: StatusPassed, @@ -218,7 +218,7 @@ func (r *Results) AddPassed(source interface{}, descriptions ...string) { *r = append(*r, res) } -func getMetadataFromSource(source interface{}) iacTypes.Metadata { +func getMetadataFromSource(source any) iacTypes.Metadata { if provider, ok := source.(MetadataProvider); ok { return provider.GetMetadata() } @@ -231,14 +231,14 @@ func getMetadataFromSource(source interface{}) iacTypes.Metadata { return metaVal.Interface().(iacTypes.Metadata) } -func getAnnotation(source interface{}) string { +func getAnnotation(source any) string { if provider, ok := source.(MetadataProvider); ok { return rawToString(provider.GetRawValue()) } return "" } -func (r *Results) AddPassedRego(namespace, rule string, traces []string, source interface{}) { +func (r *Results) AddPassedRego(namespace, rule string, traces []string, source any) { res := Result{ status: StatusPassed, regoNamespace: namespace, @@ -251,7 +251,7 @@ func (r *Results) AddPassedRego(namespace, rule string, traces []string, source *r = append(*r, res) } -func (r *Results) AddIgnored(source interface{}, descriptions ...string) { +func (r *Results) AddIgnored(source any, descriptions ...string) { res := Result{ description: strings.Join(descriptions, " "), status: StatusIgnored, @@ -311,7 +311,7 @@ func (r *Results) SetSourceAndFilesystem(source string, f fs.FS, logicalSource b } } -func rawToString(raw interface{}) string { +func rawToString(raw any) string { if raw == nil { return "" } diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/bench_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/bench_test.go index 4d08c8e3b9c6..1437a9b83bb3 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/bench_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/bench_test.go @@ -10,7 +10,7 @@ import ( ) func BenchmarkUnmarshal_JFather(b *testing.B) { - target := make(map[string]interface{}) + target := make(map[string]any) input := []byte(`{ "glossary": { "title": "example glossary", @@ -41,7 +41,7 @@ func BenchmarkUnmarshal_JFather(b *testing.B) { } func BenchmarkUnmarshal_Traditional(b *testing.B) { - target := make(map[string]interface{}) + target := make(map[string]any) input := []byte(`{ "glossary": { "title": "example glossary", diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/decode.go b/pkg/iac/scanners/azure/arm/parser/armjson/decode.go index 1f228065b9c5..c0c476db7681 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/decode.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/decode.go @@ -7,7 +7,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/types" ) -func (n *node) Decode(target interface{}) error { +func (n *node) Decode(target any) error { v := reflect.ValueOf(target) return n.decodeToValue(v) } diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/decode_array.go b/pkg/iac/scanners/azure/arm/parser/armjson/decode_array.go index 75faada57252..483880814383 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/decode_array.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/decode_array.go @@ -20,7 +20,7 @@ func (n *node) decodeArray(v reflect.Value) error { v.Set(reflect.MakeSlice(v.Type(), length, length)) case reflect.Interface: original = v - slice := reflect.ValueOf(make([]interface{}, length)) + slice := reflect.ValueOf(make([]any, length)) v = reflect.New(slice.Type()).Elem() v.Set(slice) default: diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/decode_object.go b/pkg/iac/scanners/azure/arm/parser/armjson/decode_object.go index 57b611065242..fdc58f6c8e34 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/decode_object.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/decode_object.go @@ -13,7 +13,7 @@ func (n *node) decodeObject(v reflect.Value) error { case reflect.Map: return n.decodeObjectToMap(v) case reflect.Interface: - target := reflect.New(reflect.TypeOf(make(map[string]interface{}, len(n.Content())))).Elem() + target := reflect.New(reflect.TypeOf(make(map[string]any, len(n.Content())))).Elem() if err := n.decodeObjectToMap(target); err != nil { return err } diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/node.go b/pkg/iac/scanners/azure/arm/parser/armjson/node.go index af8f9188ebd8..28322a0c264d 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/node.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/node.go @@ -5,7 +5,7 @@ import "github.com/aquasecurity/trivy/pkg/iac/types" type Node interface { Comments() []Node Range() Range - Decode(target interface{}) error + Decode(target any) error Kind() Kind Content() []Node Metadata() types.Metadata @@ -22,7 +22,7 @@ type Position struct { } type node struct { - raw interface{} + raw any start Position end Position kind Kind diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse.go index 8248ec1a1723..6eaea9c57390 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse.go @@ -89,7 +89,7 @@ func (p *parser) undo() error { return nil } -func (p *parser) makeError(format string, args ...interface{}) error { +func (p *parser) makeError(format string, args ...any) error { return fmt.Errorf( "error at line %d, column %d: %s", p.position.Line, diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go index 593d784bd084..0d373c6da241 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_array_test.go @@ -38,7 +38,7 @@ func Test_Array_ToArray(t *testing.T) { func Test_Array_ToInterface(t *testing.T) { example := []byte(`{ "List": [1, 2, 3] }`) target := struct { - List interface{} + List any }{} metadata := types.NewTestMetadata() require.NoError(t, Unmarshal(example, &target, &metadata)) diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean_test.go index 6abb9da200b7..2e9fec7fcae6 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_boolean_test.go @@ -46,7 +46,7 @@ func Test_Bool_ToUninitialisedPointer(t *testing.T) { func Test_Bool_ToInterface(t *testing.T) { example := []byte(`true`) - var output interface{} + var output any metadata := types.NewTestMetadata() err := Unmarshal(example, &output, &metadata) require.NoError(t, err) diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_complex_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_complex_test.go index 550f94a55794..3cc16f94046c 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_complex_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_complex_test.go @@ -9,7 +9,7 @@ import ( ) func Test_Complex(t *testing.T) { - target := make(map[string]interface{}) + target := make(map[string]any) input := `{ "glossary": { "title": "example glossary", @@ -56,8 +56,8 @@ type Parameter struct { } type parameterInner struct { - Type string `json:"Type" yaml:"Type"` - Default interface{} `yaml:"Default"` + Type string `json:"Type" yaml:"Type"` + Default any `yaml:"Default"` } func (p *Parameter) UnmarshalJSONWithMetadata(node Node) error { @@ -73,7 +73,7 @@ type CFType string type propertyInner struct { Type CFType - Value interface{} `json:"Value" yaml:"Value"` + Value any `json:"Value" yaml:"Value"` } func (p *Property) UnmarshalJSONWithMetadata(node Node) error { diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go index 1477f56f9231..2971ae73de21 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go @@ -68,7 +68,7 @@ func Test_Object_ToMapStringInterface(t *testing.T) { "Name": "testing" }`) - parent := make(map[string]interface{}) + parent := make(map[string]any) metadata := types.NewTestMetadata() require.NoError(t, Unmarshal(example, &parent, &metadata)) assert.Equal(t, "testing", parent["Name"]) @@ -93,7 +93,7 @@ func Test_Object_ToNestedMapStringInterfaceFromIAM(t *testing.T) { ] }`) - parent := make(map[string]interface{}) + parent := make(map[string]any) metadata := types.NewTestMetadata() require.NoError(t, Unmarshal(example, &parent, &metadata)) } @@ -106,10 +106,10 @@ func Test_Object_ToNestedMapStringInterface(t *testing.T) { "Name": "testing" }`) - parent := make(map[string]interface{}) + parent := make(map[string]any) metadata := types.NewTestMetadata() require.NoError(t, Unmarshal(example, &parent, &metadata)) assert.Equal(t, "testing", parent["Name"]) - child := parent["Child"].(map[string]interface{}) + child := parent["Child"].(map[string]any) assert.Equal(t, "password", child["secret"]) } diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_string_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_string_test.go index ab630222b6c5..699c1c88fffe 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_string_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_string_test.go @@ -29,7 +29,7 @@ func Test_StringToUninitialisedPointer(t *testing.T) { func Test_String_ToInterface(t *testing.T) { example := []byte(`"hello"`) - var output interface{} + var output any metadata := types.NewTestMetadata() err := Unmarshal(example, &output, &metadata) require.NoError(t, err) diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/unmarshal.go b/pkg/iac/scanners/azure/arm/parser/armjson/unmarshal.go index a75942808eba..b198bb7b7073 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/unmarshal.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/unmarshal.go @@ -15,7 +15,7 @@ type MetadataReceiver interface { SetMetadata(m *types.Metadata) } -func Unmarshal(data []byte, target interface{}, metadata *types.Metadata) error { +func Unmarshal(data []byte, target any, metadata *types.Metadata) error { node, err := newParser(NewPeekReader(bytes.NewReader(data)), Position{1, 1}).parse(metadata) if err != nil { return err @@ -27,7 +27,7 @@ func Unmarshal(data []byte, target interface{}, metadata *types.Metadata) error return nil } -func UnmarshalFromReader(r io.ReadSeeker, target interface{}, metadata *types.Metadata) error { +func UnmarshalFromReader(r io.ReadSeeker, target any, metadata *types.Metadata) error { node, err := newParser(NewPeekReader(r), Position{1, 1}).parse(metadata) if err != nil { return err diff --git a/pkg/iac/scanners/azure/deployment.go b/pkg/iac/scanners/azure/deployment.go index f2f050f7cf95..9932f1538db3 100644 --- a/pkg/iac/scanners/azure/deployment.go +++ b/pkg/iac/scanners/azure/deployment.go @@ -77,7 +77,7 @@ func (r *Resource) GetResourcesByType(t string) []Resource { return resources } -func (d *Deployment) GetParameter(parameterName string) interface{} { +func (d *Deployment) GetParameter(parameterName string) any { for _, parameter := range d.Parameters { if parameter.Name == parameterName { @@ -87,7 +87,7 @@ func (d *Deployment) GetParameter(parameterName string) interface{} { return nil } -func (d *Deployment) GetVariable(variableName string) interface{} { +func (d *Deployment) GetVariable(variableName string) any { for _, variable := range d.Variables { if variable.Name == variableName { @@ -97,7 +97,7 @@ func (d *Deployment) GetVariable(variableName string) interface{} { return nil } -func (d *Deployment) GetEnvVariable(envVariableName string) interface{} { +func (d *Deployment) GetEnvVariable(envVariableName string) any { if envVariable, exists := os.LookupEnv(envVariableName); exists { return envVariable @@ -105,7 +105,7 @@ func (d *Deployment) GetEnvVariable(envVariableName string) interface{} { return nil } -func (d *Deployment) GetOutput(outputName string) interface{} { +func (d *Deployment) GetOutput(outputName string) any { for _, output := range d.Outputs { if output.Name == outputName { @@ -115,15 +115,15 @@ func (d *Deployment) GetOutput(outputName string) interface{} { return nil } -func (d *Deployment) GetDeployment() interface{} { +func (d *Deployment) GetDeployment() any { type template struct { - Schema string `json:"$schema"` - ContentVersion string `json:"contentVersion"` - Parameters map[string]interface{} `json:"parameters"` - Variables map[string]interface{} `json:"variables"` - Resources []interface{} `json:"resources"` - Outputs map[string]interface{} `json:"outputs"` + Schema string `json:"$schema"` + ContentVersion string `json:"contentVersion"` + Parameters map[string]any `json:"parameters"` + Variables map[string]any `json:"variables"` + Resources []any `json:"resources"` + Outputs map[string]any `json:"outputs"` } type templateLink struct { @@ -131,12 +131,12 @@ func (d *Deployment) GetDeployment() interface{} { } type properties struct { - TemplateLink templateLink `json:"templateLink"` - Template template `json:"template"` - TemplateHash string `json:"templateHash"` - Parameters map[string]interface{} `json:"parameters"` - Mode string `json:"mode"` - ProvisioningState string `json:"provisioningState"` + TemplateLink templateLink `json:"templateLink"` + Template template `json:"template"` + TemplateHash string `json:"templateHash"` + Parameters map[string]any `json:"parameters"` + Mode string `json:"mode"` + ProvisioningState string `json:"provisioningState"` } deploymentShell := struct { @@ -151,10 +151,10 @@ func (d *Deployment) GetDeployment() interface{} { Template: template{ Schema: "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", ContentVersion: "", - Parameters: make(map[string]interface{}), - Variables: make(map[string]interface{}), - Resources: make([]interface{}, 0), - Outputs: make(map[string]interface{}), + Parameters: make(map[string]any), + Variables: make(map[string]any), + Resources: make([]any, 0), + Outputs: make(map[string]any), }, }, } diff --git a/pkg/iac/scanners/azure/expressions/lex.go b/pkg/iac/scanners/azure/expressions/lex.go index 09eb7b819eff..f5cfb2a34705 100644 --- a/pkg/iac/scanners/azure/expressions/lex.go +++ b/pkg/iac/scanners/azure/expressions/lex.go @@ -23,7 +23,7 @@ const ( type Token struct { Type TokenType - Data interface{} + Data any } type lexer struct { diff --git a/pkg/iac/scanners/azure/expressions/node.go b/pkg/iac/scanners/azure/expressions/node.go index a126a9e40c3f..8c274b500a6c 100644 --- a/pkg/iac/scanners/azure/expressions/node.go +++ b/pkg/iac/scanners/azure/expressions/node.go @@ -5,14 +5,14 @@ import ( ) type Node interface { - Evaluate(deploymentProvider functions2.DeploymentData) interface{} + Evaluate(deploymentProvider functions2.DeploymentData) any } type expressionValue struct { - val interface{} + val any } -func (e expressionValue) Evaluate(deploymentProvider functions2.DeploymentData) interface{} { +func (e expressionValue) Evaluate(deploymentProvider functions2.DeploymentData) any { if f, ok := e.val.(expression); ok { return f.Evaluate(deploymentProvider) } @@ -24,8 +24,8 @@ type expression struct { args []Node } -func (f expression) Evaluate(deploymentProvider functions2.DeploymentData) interface{} { - args := make([]interface{}, len(f.args)) +func (f expression) Evaluate(deploymentProvider functions2.DeploymentData) any { + args := make([]any, len(f.args)) for i, arg := range f.args { args[i] = arg.Evaluate(deploymentProvider) } diff --git a/pkg/iac/scanners/azure/functions/add.go b/pkg/iac/scanners/azure/functions/add.go index 9eb699e2eb9b..21b1281574ad 100644 --- a/pkg/iac/scanners/azure/functions/add.go +++ b/pkg/iac/scanners/azure/functions/add.go @@ -1,6 +1,6 @@ package functions -func Add(args ...interface{}) interface{} { +func Add(args ...any) any { if len(args) != 2 { return nil diff --git a/pkg/iac/scanners/azure/functions/add_test.go b/pkg/iac/scanners/azure/functions/add_test.go index b88e9b8ee1cc..51ae2bab044a 100644 --- a/pkg/iac/scanners/azure/functions/add_test.go +++ b/pkg/iac/scanners/azure/functions/add_test.go @@ -9,22 +9,22 @@ import ( func Test_Add(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected int }{ { name: "Add with 1 and 2", - args: []interface{}{1, 2}, + args: []any{1, 2}, expected: 3, }, { name: "Add with 2 and 3", - args: []interface{}{2, 3}, + args: []any{2, 3}, expected: 5, }, { name: "Add with 3 and -4", - args: []interface{}{3, -4}, + args: []any{3, -4}, expected: -1, }, } diff --git a/pkg/iac/scanners/azure/functions/and.go b/pkg/iac/scanners/azure/functions/and.go index 67070b5c2cb0..4b8b10b7cb31 100644 --- a/pkg/iac/scanners/azure/functions/and.go +++ b/pkg/iac/scanners/azure/functions/and.go @@ -1,6 +1,6 @@ package functions -func And(args ...interface{}) interface{} { +func And(args ...any) any { if len(args) <= 1 { return false diff --git a/pkg/iac/scanners/azure/functions/and_test.go b/pkg/iac/scanners/azure/functions/and_test.go index 6814e9288ca0..8124ee262b1f 100644 --- a/pkg/iac/scanners/azure/functions/and_test.go +++ b/pkg/iac/scanners/azure/functions/and_test.go @@ -10,22 +10,22 @@ func Test_And(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected bool }{ { name: "And with same 2 bools", - args: []interface{}{true, true}, + args: []any{true, true}, expected: true, }, { name: "And with same 3 bools", - args: []interface{}{true, true, true}, + args: []any{true, true, true}, expected: true, }, { name: "And with different 4 bools", - args: []interface{}{true, true, false, true}, + args: []any{true, true, false, true}, expected: false, }, } diff --git a/pkg/iac/scanners/azure/functions/array.go b/pkg/iac/scanners/azure/functions/array.go index a1da05ef4fdc..813a133b30a0 100644 --- a/pkg/iac/scanners/azure/functions/array.go +++ b/pkg/iac/scanners/azure/functions/array.go @@ -1,6 +1,6 @@ package functions -func Array(args ...interface{}) interface{} { +func Array(args ...any) any { if len(args) != 1 { return "" @@ -11,19 +11,19 @@ func Array(args ...interface{}) interface{} { return []int{ctype} case string: return []string{ctype} - case map[string]interface{}: - var result []interface{} + case map[string]any: + var result []any for k, v := range ctype { result = append(result, k, v) } return result - case interface{}: + case any: switch ctype := ctype.(type) { case []string: return ctype - case []interface{}: + case []any: return ctype } } - return []interface{}{} + return []any{} } diff --git a/pkg/iac/scanners/azure/functions/array_test.go b/pkg/iac/scanners/azure/functions/array_test.go index c4a376ea6080..f4396fc4093a 100644 --- a/pkg/iac/scanners/azure/functions/array_test.go +++ b/pkg/iac/scanners/azure/functions/array_test.go @@ -9,27 +9,27 @@ import ( func Test_Array(t *testing.T) { test := []struct { name string - input []interface{} - expected interface{} + input []any + expected any }{ { name: "array from an int", - input: []interface{}{1}, + input: []any{1}, expected: []int{1}, }, { name: "array from a string", - input: []interface{}{"hello"}, + input: []any{"hello"}, expected: []string{"hello"}, }, { name: "array from a map", - input: []interface{}{map[string]interface{}{"hello": "world"}}, - expected: []interface{}{"hello", "world"}, + input: []any{map[string]any{"hello": "world"}}, + expected: []any{"hello", "world"}, }, { name: "array from an slice", - input: []interface{}{ + input: []any{ []string{"hello", "world"}, }, expected: []string{"hello", "world"}, diff --git a/pkg/iac/scanners/azure/functions/base64.go b/pkg/iac/scanners/azure/functions/base64.go index c3222e7675ec..46b784bb0f96 100644 --- a/pkg/iac/scanners/azure/functions/base64.go +++ b/pkg/iac/scanners/azure/functions/base64.go @@ -5,7 +5,7 @@ import ( "encoding/json" ) -func Base64(args ...interface{}) interface{} { +func Base64(args ...any) any { if len(args) == 0 { return nil @@ -16,7 +16,7 @@ func Base64(args ...interface{}) interface{} { return base64.StdEncoding.EncodeToString([]byte(input)) } -func Base64ToString(args ...interface{}) interface{} { +func Base64ToString(args ...any) any { if len(args) == 0 { return nil } @@ -30,7 +30,7 @@ func Base64ToString(args ...interface{}) interface{} { return string(result) } -func Base64ToJson(args ...interface{}) interface{} { +func Base64ToJson(args ...any) any { if len(args) == 0 { return nil @@ -43,7 +43,7 @@ func Base64ToJson(args ...interface{}) interface{} { return nil } - var result map[string]interface{} + var result map[string]any if err := json.Unmarshal(decoded, &result); err != nil { return nil diff --git a/pkg/iac/scanners/azure/functions/base64_test.go b/pkg/iac/scanners/azure/functions/base64_test.go index f557b277930c..648617ee40eb 100644 --- a/pkg/iac/scanners/azure/functions/base64_test.go +++ b/pkg/iac/scanners/azure/functions/base64_test.go @@ -11,12 +11,12 @@ import ( func Test_Base64Call(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "simple base64 call", - args: []interface{}{ + args: []any{ "hello, world", }, expected: "aGVsbG8sIHdvcmxk", @@ -35,12 +35,12 @@ func Test_Base64Call(t *testing.T) { func Test_Base64ToStringCall(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "simple base64ToString call", - args: []interface{}{ + args: []any{ "aGVsbG8sIHdvcmxk", }, expected: "hello, world", @@ -60,12 +60,12 @@ func Test_Base64ToJsonCall(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "simple base64ToJson call", - args: []interface{}{ + args: []any{ "eyJoZWxsbyI6ICJ3b3JsZCJ9", }, expected: `{"hello":"world"}`, diff --git a/pkg/iac/scanners/azure/functions/bool.go b/pkg/iac/scanners/azure/functions/bool.go index 0221a5a4b8ee..cb3031dba4cb 100644 --- a/pkg/iac/scanners/azure/functions/bool.go +++ b/pkg/iac/scanners/azure/functions/bool.go @@ -2,7 +2,7 @@ package functions import "strings" -func Bool(args ...interface{}) interface{} { +func Bool(args ...any) any { if len(args) != 1 { return false } diff --git a/pkg/iac/scanners/azure/functions/bool_test.go b/pkg/iac/scanners/azure/functions/bool_test.go index 6c520a9380f8..054dd52776ad 100644 --- a/pkg/iac/scanners/azure/functions/bool_test.go +++ b/pkg/iac/scanners/azure/functions/bool_test.go @@ -9,47 +9,47 @@ import ( func Test_Bool(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected bool }{ { name: "Bool with true", - args: []interface{}{true}, + args: []any{true}, expected: true, }, { name: "Bool with false", - args: []interface{}{false}, + args: []any{false}, expected: false, }, { name: "Bool with 1", - args: []interface{}{1}, + args: []any{1}, expected: true, }, { name: "Bool with 0", - args: []interface{}{0}, + args: []any{0}, expected: false, }, { name: "Bool with true string", - args: []interface{}{"true"}, + args: []any{"true"}, expected: true, }, { name: "Bool with false string", - args: []interface{}{"false"}, + args: []any{"false"}, expected: false, }, { name: "Bool with 1 string", - args: []interface{}{"1"}, + args: []any{"1"}, expected: true, }, { name: "Bool with 0 string", - args: []interface{}{"0"}, + args: []any{"0"}, expected: false, }, } diff --git a/pkg/iac/scanners/azure/functions/casing.go b/pkg/iac/scanners/azure/functions/casing.go index 56a93bbd7a4b..2df433a5ade2 100644 --- a/pkg/iac/scanners/azure/functions/casing.go +++ b/pkg/iac/scanners/azure/functions/casing.go @@ -2,7 +2,7 @@ package functions import "strings" -func ToLower(args ...interface{}) interface{} { +func ToLower(args ...any) any { if len(args) != 1 { return "" } @@ -15,7 +15,7 @@ func ToLower(args ...interface{}) interface{} { return strings.ToLower(input) } -func ToUpper(args ...interface{}) interface{} { +func ToUpper(args ...any) any { if len(args) != 1 { return "" } diff --git a/pkg/iac/scanners/azure/functions/casing_test.go b/pkg/iac/scanners/azure/functions/casing_test.go index 51c970e1765e..d94fca7ab050 100644 --- a/pkg/iac/scanners/azure/functions/casing_test.go +++ b/pkg/iac/scanners/azure/functions/casing_test.go @@ -10,19 +10,19 @@ func Test_ToLower(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "lowercase a string", - args: []interface{}{ + args: []any{ "HELLO", }, expected: "hello", }, { name: "lowercase a string with a non-string input", - args: []interface{}{ + args: []any{ 10, }, expected: "", @@ -42,19 +42,19 @@ func Test_ToUpper(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "uppercase a string", - args: []interface{}{ + args: []any{ "hello", }, expected: "HELLO", }, { name: "uppercase a string with a non-string input", - args: []interface{}{ + args: []any{ 10, }, expected: "", diff --git a/pkg/iac/scanners/azure/functions/coalesce.go b/pkg/iac/scanners/azure/functions/coalesce.go index b7ec261450f7..cec7220a83ba 100644 --- a/pkg/iac/scanners/azure/functions/coalesce.go +++ b/pkg/iac/scanners/azure/functions/coalesce.go @@ -1,6 +1,6 @@ package functions -func Coalesce(args ...interface{}) interface{} { +func Coalesce(args ...any) any { for _, arg := range args { if arg != nil { return arg diff --git a/pkg/iac/scanners/azure/functions/coalesce_test.go b/pkg/iac/scanners/azure/functions/coalesce_test.go index 361914df64cd..b0d0459bde54 100644 --- a/pkg/iac/scanners/azure/functions/coalesce_test.go +++ b/pkg/iac/scanners/azure/functions/coalesce_test.go @@ -9,19 +9,19 @@ import ( func Test_Coalesce(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "coalesce with nil", - args: []interface{}{ + args: []any{ nil, }, expected: nil, }, { name: "coalesce with nil and string", - args: []interface{}{ + args: []any{ nil, "test", }, @@ -29,7 +29,7 @@ func Test_Coalesce(t *testing.T) { }, { name: "coalesce with nil and string and int", - args: []interface{}{ + args: []any{ nil, "test", 1, @@ -38,12 +38,12 @@ func Test_Coalesce(t *testing.T) { }, { name: "coalesce with nil and nil and array", - args: []interface{}{ + args: []any{ nil, nil, - []interface{}{"a", "b", "c"}, + []any{"a", "b", "c"}, }, - expected: []interface{}{"a", "b", "c"}, + expected: []any{"a", "b", "c"}, }, } diff --git a/pkg/iac/scanners/azure/functions/concat.go b/pkg/iac/scanners/azure/functions/concat.go index 800db04be77d..ac15c1e0097d 100644 --- a/pkg/iac/scanners/azure/functions/concat.go +++ b/pkg/iac/scanners/azure/functions/concat.go @@ -4,7 +4,7 @@ import ( "fmt" ) -func Concat(args ...interface{}) interface{} { +func Concat(args ...any) any { switch args[0].(type) { case string: @@ -13,10 +13,10 @@ func Concat(args ...interface{}) interface{} { result += fmt.Sprintf("%v", arg) } return result - case interface{}: - var result []interface{} + case any: + var result []any for _, arg := range args { - argArr, ok := arg.([]interface{}) + argArr, ok := arg.([]any) if !ok { continue } diff --git a/pkg/iac/scanners/azure/functions/concat_test.go b/pkg/iac/scanners/azure/functions/concat_test.go index 7b0c461c960d..0418058c483c 100644 --- a/pkg/iac/scanners/azure/functions/concat_test.go +++ b/pkg/iac/scanners/azure/functions/concat_test.go @@ -9,12 +9,12 @@ import ( func Test_StringConcatenation(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "simple string concatenation", - args: []interface{}{ + args: []any{ "hello", ", ", "world", @@ -24,7 +24,7 @@ func Test_StringConcatenation(t *testing.T) { }, { name: "string concatenation with non strings", - args: []interface{}{ + args: []any{ "pi to 3 decimal places is ", 3.142, }, @@ -32,7 +32,7 @@ func Test_StringConcatenation(t *testing.T) { }, { name: "string concatenation with multiple primitives", - args: []interface{}{ + args: []any{ "to say that ", 3, " is greater than ", @@ -55,33 +55,33 @@ func Test_StringConcatenation(t *testing.T) { func Test_ArrayConcatenation(t *testing.T) { tests := []struct { name string - args []interface{} - expected []interface{} + args []any + expected []any }{ { name: "simple array concatenation", - args: []interface{}{ - []interface{}{1, 2, 3}, - []interface{}{4, 5, 6}, + args: []any{ + []any{1, 2, 3}, + []any{4, 5, 6}, }, - expected: []interface{}{1, 2, 3, 4, 5, 6}, + expected: []any{1, 2, 3, 4, 5, 6}, }, { name: "array concatenation with non arrays", - args: []interface{}{ - []interface{}{1, 2, 3}, + args: []any{ + []any{1, 2, 3}, 4, }, - expected: []interface{}{1, 2, 3}, + expected: []any{1, 2, 3}, }, { name: "array concatenation with multiple primitives", - args: []interface{}{ - []interface{}{1, 2, 3}, + args: []any{ + []any{1, 2, 3}, 4, - []interface{}{5, 6, 7}, + []any{5, 6, 7}, }, - expected: []interface{}{1, 2, 3, 5, 6, 7}, + expected: []any{1, 2, 3, 5, 6, 7}, }, } diff --git a/pkg/iac/scanners/azure/functions/contains.go b/pkg/iac/scanners/azure/functions/contains.go index a067d63dfa85..3d735c95c210 100644 --- a/pkg/iac/scanners/azure/functions/contains.go +++ b/pkg/iac/scanners/azure/functions/contains.go @@ -5,7 +5,7 @@ import ( "strings" ) -func Contains(args ...interface{}) interface{} { +func Contains(args ...any) any { if len(args) != 2 { return false @@ -22,13 +22,13 @@ func Contains(args ...interface{}) interface{} { case int, int32, int64, uint, uint32, uint64: return strings.Contains(strings.ToLower(cType), fmt.Sprintf("%d", iType)) } - case []interface{}: + case []any: for _, item := range cType { if item == itemToFind { return true } } - case map[string]interface{}: + case map[string]any: for key := range cType { if key == itemToFind { return true diff --git a/pkg/iac/scanners/azure/functions/contains_test.go b/pkg/iac/scanners/azure/functions/contains_test.go index e92f08fd5462..37fd793cdeff 100644 --- a/pkg/iac/scanners/azure/functions/contains_test.go +++ b/pkg/iac/scanners/azure/functions/contains_test.go @@ -9,12 +9,12 @@ import ( func Test_Contains(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected bool }{ { name: "simple true string contains", - args: []interface{}{ + args: []any{ "hello, world", "hell", }, @@ -22,7 +22,7 @@ func Test_Contains(t *testing.T) { }, { name: "simple false string contains", - args: []interface{}{ + args: []any{ "hello, world", "help", }, @@ -30,7 +30,7 @@ func Test_Contains(t *testing.T) { }, { name: "simple true string contains with case sensitivity", - args: []interface{}{ + args: []any{ "hello, world", "HELL", }, @@ -38,7 +38,7 @@ func Test_Contains(t *testing.T) { }, { name: "simple true string contains with number", - args: []interface{}{ + args: []any{ "You're my number 1", 1, }, @@ -46,8 +46,8 @@ func Test_Contains(t *testing.T) { }, { name: "true object contains key", - args: []interface{}{ - map[string]interface{}{ + args: []any{ + map[string]any{ "hello": "world", }, "hello", @@ -56,8 +56,8 @@ func Test_Contains(t *testing.T) { }, { name: "false object contains key", - args: []interface{}{ - map[string]interface{}{ + args: []any{ + map[string]any{ "hello": "world", }, "world", @@ -66,8 +66,8 @@ func Test_Contains(t *testing.T) { }, { name: "true array contains value", - args: []interface{}{ - []interface{}{ + args: []any{ + []any{ "hello", "world", }, "hello", @@ -76,8 +76,8 @@ func Test_Contains(t *testing.T) { }, { name: "false array contains value", - args: []interface{}{ - []interface{}{ + args: []any{ + []any{ "hello", "world", }, "help", diff --git a/pkg/iac/scanners/azure/functions/copy_index.go b/pkg/iac/scanners/azure/functions/copy_index.go index d1289cc0a20d..777d9a6237f6 100644 --- a/pkg/iac/scanners/azure/functions/copy_index.go +++ b/pkg/iac/scanners/azure/functions/copy_index.go @@ -2,7 +2,7 @@ package functions var loopCounter = make(map[string]int) -func CopyIndex(args ...interface{}) interface{} { +func CopyIndex(args ...any) any { loopName := "default" offset := 1 if len(args) > 0 { diff --git a/pkg/iac/scanners/azure/functions/copy_index_test.go b/pkg/iac/scanners/azure/functions/copy_index_test.go index 041b258ca8cf..c161d2596223 100644 --- a/pkg/iac/scanners/azure/functions/copy_index_test.go +++ b/pkg/iac/scanners/azure/functions/copy_index_test.go @@ -5,38 +5,38 @@ import "testing" func Test_CopyIndex(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected int }{ { name: "CopyIndex with 1", - args: []interface{}{}, + args: []any{}, expected: 1, }, { name: "CopyIndex with 2", - args: []interface{}{}, + args: []any{}, expected: 2, }, { name: "CopyIndex with 3", - args: []interface{}{}, + args: []any{}, expected: 3, }, { name: "CopyIndex with loopName", - args: []interface{}{"loop1"}, + args: []any{"loop1"}, expected: 1, }, { name: "CopyIndex with same lo" + "opName", - args: []interface{}{"loop1"}, + args: []any{"loop1"}, expected: 2, }, { name: "CopyIndex with loopName", - args: []interface{}{"loop2", 10}, + args: []any{"loop2", 10}, expected: 10, }, } diff --git a/pkg/iac/scanners/azure/functions/create_array.go b/pkg/iac/scanners/azure/functions/create_array.go index 99f3558847a1..ad92670e10be 100644 --- a/pkg/iac/scanners/azure/functions/create_array.go +++ b/pkg/iac/scanners/azure/functions/create_array.go @@ -1,7 +1,7 @@ package functions -func CreateArray(args ...interface{}) interface{} { - var result []interface{} +func CreateArray(args ...any) any { + var result []any if len(args) == 0 { return result } diff --git a/pkg/iac/scanners/azure/functions/create_array_test.go b/pkg/iac/scanners/azure/functions/create_array_test.go index 5e63074888cb..d2d6062b9bd5 100644 --- a/pkg/iac/scanners/azure/functions/create_array_test.go +++ b/pkg/iac/scanners/azure/functions/create_array_test.go @@ -10,48 +10,48 @@ func Test_CreateArray(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "create array with strings", - args: []interface{}{ + args: []any{ "Hello", "World", }, - expected: []interface{}{"Hello", "World"}, + expected: []any{"Hello", "World"}, }, { name: "create array with ints", - args: []interface{}{ + args: []any{ 1, 2, 3, }, - expected: []interface{}{1, 2, 3}, + expected: []any{1, 2, 3}, }, { name: "create array with arrays", - args: []interface{}{ - []interface{}{1, 2, 3}, - []interface{}{4, 5, 6}, + args: []any{ + []any{1, 2, 3}, + []any{4, 5, 6}, }, - expected: []interface{}{[]interface{}{1, 2, 3}, []interface{}{4, 5, 6}}, + expected: []any{[]any{1, 2, 3}, []any{4, 5, 6}}, }, { name: "create arrau with maps", - args: []interface{}{ - map[string]interface{}{ + args: []any{ + map[string]any{ "one": "a", }, - map[string]interface{}{ + map[string]any{ "two": "b", }, }, - expected: []interface{}{ - map[string]interface{}{ + expected: []any{ + map[string]any{ "one": "a", }, - map[string]interface{}{ + map[string]any{ "two": "b", }, }, diff --git a/pkg/iac/scanners/azure/functions/create_object.go b/pkg/iac/scanners/azure/functions/create_object.go index 30dc239847f8..48a0ebafb673 100644 --- a/pkg/iac/scanners/azure/functions/create_object.go +++ b/pkg/iac/scanners/azure/functions/create_object.go @@ -1,7 +1,7 @@ package functions -func CreateObject(args ...interface{}) interface{} { - obj := make(map[string]interface{}) +func CreateObject(args ...any) any { + obj := make(map[string]any) if len(args) == 0 { return obj } diff --git a/pkg/iac/scanners/azure/functions/create_object_test.go b/pkg/iac/scanners/azure/functions/create_object_test.go index 1e72c4626a48..10cc72d52ede 100644 --- a/pkg/iac/scanners/azure/functions/create_object_test.go +++ b/pkg/iac/scanners/azure/functions/create_object_test.go @@ -10,38 +10,38 @@ func Test_CreateObject(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "CreateObject with no args", - args: []interface{}{}, - expected: make(map[string]interface{}), + args: []any{}, + expected: make(map[string]any), }, { name: "CreateObject with one arg", - args: []interface{}{"foo", "bar"}, - expected: map[string]interface{}{"foo": "bar"}, + args: []any{"foo", "bar"}, + expected: map[string]any{"foo": "bar"}, }, { name: "CreateObject with two args", - args: []interface{}{"foo", "bar", "baz", "qux"}, - expected: map[string]interface{}{"foo": "bar", "baz": "qux"}, + args: []any{"foo", "bar", "baz", "qux"}, + expected: map[string]any{"foo": "bar", "baz": "qux"}, }, { name: "CreateObject with three args", - args: []interface{}{"foo", "bar", "baz", 1, "quux", true}, - expected: map[string]interface{}{"foo": "bar", "baz": 1, "quux": true}, + args: []any{"foo", "bar", "baz", 1, "quux", true}, + expected: map[string]any{"foo": "bar", "baz": 1, "quux": true}, }, { name: "CreateObject with odd number of args", - args: []interface{}{"foo", "bar", "baz"}, - expected: make(map[string]interface{}), + args: []any{"foo", "bar", "baz"}, + expected: make(map[string]any), }, { name: "CreateObject with odd number of args", - args: []interface{}{"foo", "bar", "baz", []string{"Hello", "World"}}, - expected: map[string]interface{}{ + args: []any{"foo", "bar", "baz", []string{"Hello", "World"}}, + expected: map[string]any{ "foo": "bar", "baz": []string{ "Hello", "World", diff --git a/pkg/iac/scanners/azure/functions/data_uri.go b/pkg/iac/scanners/azure/functions/data_uri.go index 50f0835ee6ad..3b147b568f09 100644 --- a/pkg/iac/scanners/azure/functions/data_uri.go +++ b/pkg/iac/scanners/azure/functions/data_uri.go @@ -5,7 +5,7 @@ import ( "strings" ) -func DataUri(args ...interface{}) interface{} { +func DataUri(args ...any) any { if len(args) == 0 { return "" } @@ -18,7 +18,7 @@ func DataUri(args ...interface{}) interface{} { return fmt.Sprintf("data:text/plain;charset=utf8;base64,%s", Base64(input)) } -func DataUriToString(args ...interface{}) interface{} { +func DataUriToString(args ...any) any { if len(args) == 0 { return "" } diff --git a/pkg/iac/scanners/azure/functions/data_uri_test.go b/pkg/iac/scanners/azure/functions/data_uri_test.go index 04f92249e093..87f8a16de609 100644 --- a/pkg/iac/scanners/azure/functions/data_uri_test.go +++ b/pkg/iac/scanners/azure/functions/data_uri_test.go @@ -9,12 +9,12 @@ import ( func Test_data_uri_from_string(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "data uri from string", - args: []interface{}{ + args: []any{ "Hello", }, expected: "data:text/plain;charset=utf8;base64,SGVsbG8=", @@ -32,12 +32,12 @@ func Test_data_uri_from_string(t *testing.T) { func Test_string_from_data_uri(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "data uri to string", - args: []interface{}{ + args: []any{ "data:;base64,SGVsbG8sIFdvcmxkIQ==", }, expected: "Hello, World!", diff --git a/pkg/iac/scanners/azure/functions/date_time_add.go b/pkg/iac/scanners/azure/functions/date_time_add.go index c3b902b08965..bbd03b47bdfa 100644 --- a/pkg/iac/scanners/azure/functions/date_time_add.go +++ b/pkg/iac/scanners/azure/functions/date_time_add.go @@ -9,7 +9,7 @@ import ( var pattern = regexp.MustCompile(`^P((?P\d+)Y)?((?P\d+)M)?((?P\d+)W)?((?P\d+)D)?(T((?P\d+)H)?((?P\d+)M)?((?P\d+)S)?)?$`) -func DateTimeAdd(args ...interface{}) interface{} { +func DateTimeAdd(args ...any) any { if len(args) < 2 { return nil } diff --git a/pkg/iac/scanners/azure/functions/date_time_epoch.go b/pkg/iac/scanners/azure/functions/date_time_epoch.go index 9b1802573269..5c42a4e25baa 100644 --- a/pkg/iac/scanners/azure/functions/date_time_epoch.go +++ b/pkg/iac/scanners/azure/functions/date_time_epoch.go @@ -6,7 +6,7 @@ import ( smithyTime "github.com/aws/smithy-go/time" ) -func DateTimeFromEpoch(args ...interface{}) interface{} { +func DateTimeFromEpoch(args ...any) any { if len(args) != 1 { return nil } @@ -19,7 +19,7 @@ func DateTimeFromEpoch(args ...interface{}) interface{} { return smithyTime.ParseEpochSeconds(float64(epoch)).Format(time.RFC3339) } -func DateTimeToEpoch(args ...interface{}) interface{} { +func DateTimeToEpoch(args ...any) any { if len(args) != 1 { return nil } diff --git a/pkg/iac/scanners/azure/functions/date_time_epoch_test.go b/pkg/iac/scanners/azure/functions/date_time_epoch_test.go index 6cdf7a0442bd..1df45badd7cc 100644 --- a/pkg/iac/scanners/azure/functions/date_time_epoch_test.go +++ b/pkg/iac/scanners/azure/functions/date_time_epoch_test.go @@ -9,12 +9,12 @@ import ( func Test_DateTimeFromEpoch(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "datetime from epoch", - args: []interface{}{ + args: []any{ 1683040573, }, expected: "2023-05-02T15:16:13Z", @@ -31,12 +31,12 @@ func Test_DateTimeFromEpoch(t *testing.T) { func Test_DateTimeToEpoch(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "datetime to epoch", - args: []interface{}{ + args: []any{ "2023-05-02T15:16:13Z", }, expected: 1683040573, diff --git a/pkg/iac/scanners/azure/functions/datetime_add_test.go b/pkg/iac/scanners/azure/functions/datetime_add_test.go index b5c09d04a742..3334323bf09c 100644 --- a/pkg/iac/scanners/azure/functions/datetime_add_test.go +++ b/pkg/iac/scanners/azure/functions/datetime_add_test.go @@ -10,13 +10,13 @@ import ( func Test_DateTimeAdd(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "datetime add 1 years", - args: []interface{}{ + args: []any{ "2010-01-01T00:00:00Z", "P1Y", }, @@ -24,7 +24,7 @@ func Test_DateTimeAdd(t *testing.T) { }, { name: "datetime add 3 months", - args: []interface{}{ + args: []any{ "2010-01-01T00:00:00Z", "P3M", }, diff --git a/pkg/iac/scanners/azure/functions/deployment.go b/pkg/iac/scanners/azure/functions/deployment.go index afafb2b3587c..bde37b4915ff 100644 --- a/pkg/iac/scanners/azure/functions/deployment.go +++ b/pkg/iac/scanners/azure/functions/deployment.go @@ -1,12 +1,12 @@ package functions type DeploymentData interface { - GetParameter(name string) interface{} - GetVariable(variableName string) interface{} - GetEnvVariable(envVariableName string) interface{} + GetParameter(name string) any + GetVariable(variableName string) any + GetEnvVariable(envVariableName string) any } -func Deployment(deploymentProvider DeploymentData, args ...interface{}) interface{} { +func Deployment(deploymentProvider DeploymentData, args ...any) any { /* @@ -36,7 +36,7 @@ func Deployment(deploymentProvider DeploymentData, args ...interface{}) interfac return nil } -func Environment(envProvider DeploymentData, args ...interface{}) interface{} { +func Environment(envProvider DeploymentData, args ...any) any { if len(args) == 0 { return nil } @@ -48,7 +48,7 @@ func Environment(envProvider DeploymentData, args ...interface{}) interface{} { return envProvider.GetEnvVariable(envVarName) } -func Variables(varProvider DeploymentData, args ...interface{}) interface{} { +func Variables(varProvider DeploymentData, args ...any) any { if len(args) == 0 { return nil } @@ -60,7 +60,7 @@ func Variables(varProvider DeploymentData, args ...interface{}) interface{} { return varProvider.GetVariable(varName) } -func Parameters(paramProvider DeploymentData, args ...interface{}) interface{} { +func Parameters(paramProvider DeploymentData, args ...any) any { if len(args) == 0 { return nil } diff --git a/pkg/iac/scanners/azure/functions/div.go b/pkg/iac/scanners/azure/functions/div.go index 9de0dfb05f73..2890e3f8c89c 100644 --- a/pkg/iac/scanners/azure/functions/div.go +++ b/pkg/iac/scanners/azure/functions/div.go @@ -1,6 +1,6 @@ package functions -func Div(args ...interface{}) interface{} { +func Div(args ...any) any { if len(args) != 2 { return nil diff --git a/pkg/iac/scanners/azure/functions/div_test.go b/pkg/iac/scanners/azure/functions/div_test.go index 49166190fb5d..55a429908bcb 100644 --- a/pkg/iac/scanners/azure/functions/div_test.go +++ b/pkg/iac/scanners/azure/functions/div_test.go @@ -9,22 +9,22 @@ import ( func Test_Div(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected int }{ { name: "Div 2 by 1", - args: []interface{}{2, 1}, + args: []any{2, 1}, expected: 2, }, { name: "Div 4 by 2", - args: []interface{}{4, 2}, + args: []any{4, 2}, expected: 2, }, { name: "Div 6 by 2", - args: []interface{}{6, 2}, + args: []any{6, 2}, expected: 3, }, } diff --git a/pkg/iac/scanners/azure/functions/empty.go b/pkg/iac/scanners/azure/functions/empty.go index 1dbe8396f7c3..96848b9dcd5a 100644 --- a/pkg/iac/scanners/azure/functions/empty.go +++ b/pkg/iac/scanners/azure/functions/empty.go @@ -1,6 +1,6 @@ package functions -func Empty(args ...interface{}) interface{} { +func Empty(args ...any) any { if len(args) != 1 { return false @@ -11,9 +11,9 @@ func Empty(args ...interface{}) interface{} { switch cType := container.(type) { case string: return cType == "" - case map[string]interface{}: + case map[string]any: return len(cType) == 0 - case interface{}: + case any: switch iType := cType.(type) { case []string: return len(iType) == 0 @@ -23,7 +23,7 @@ func Empty(args ...interface{}) interface{} { return len(iType) == 0 case []float64: return len(iType) == 0 - case map[string]interface{}: + case map[string]any: return len(iType) == 0 } diff --git a/pkg/iac/scanners/azure/functions/empty_test.go b/pkg/iac/scanners/azure/functions/empty_test.go index 9f1ead58e684..e325f3c80447 100644 --- a/pkg/iac/scanners/azure/functions/empty_test.go +++ b/pkg/iac/scanners/azure/functions/empty_test.go @@ -9,48 +9,48 @@ import ( func Test_Empty(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected bool }{ { name: "string is empty", - args: []interface{}{ + args: []any{ "", }, expected: true, }, { name: "string is not empty", - args: []interface{}{ + args: []any{ "hello, world", }, expected: false, }, { name: "array is empty", - args: []interface{}{ + args: []any{ []string{}, }, expected: true, }, { name: "array is not empty", - args: []interface{}{ + args: []any{ []string{"Hello", "World"}, }, expected: false, }, { name: "map is empty", - args: []interface{}{ - make(map[string]interface{}), + args: []any{ + make(map[string]any), }, expected: true, }, { name: "map is not empty", - args: []interface{}{ - map[string]interface{}{ + args: []any{ + map[string]any{ "hello": "world", }, "world", diff --git a/pkg/iac/scanners/azure/functions/ends_with.go b/pkg/iac/scanners/azure/functions/ends_with.go index 2bcd66217ecb..1ca6b099657f 100644 --- a/pkg/iac/scanners/azure/functions/ends_with.go +++ b/pkg/iac/scanners/azure/functions/ends_with.go @@ -2,7 +2,7 @@ package functions import "strings" -func EndsWith(args ...interface{}) interface{} { +func EndsWith(args ...any) any { if len(args) != 2 { return false diff --git a/pkg/iac/scanners/azure/functions/ends_with_test.go b/pkg/iac/scanners/azure/functions/ends_with_test.go index b1d1900ba0d2..d8703d6c6493 100644 --- a/pkg/iac/scanners/azure/functions/ends_with_test.go +++ b/pkg/iac/scanners/azure/functions/ends_with_test.go @@ -10,12 +10,12 @@ func Test_EndsWith(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected bool }{ { name: "string ends with", - args: []interface{}{ + args: []any{ "Hello world!", "world!", }, @@ -23,7 +23,7 @@ func Test_EndsWith(t *testing.T) { }, { name: "string does not end with", - args: []interface{}{ + args: []any{ "Hello world!", "world", }, diff --git a/pkg/iac/scanners/azure/functions/equals.go b/pkg/iac/scanners/azure/functions/equals.go index ca5174144cb8..2034e18bc0c2 100644 --- a/pkg/iac/scanners/azure/functions/equals.go +++ b/pkg/iac/scanners/azure/functions/equals.go @@ -1,13 +1,13 @@ package functions -func Equals(args ...interface{}) interface{} { +func Equals(args ...any) any { if len(args) != 2 { return false } - slice1, ok := args[0].([]interface{}) + slice1, ok := args[0].([]any) if ok { - slice2, ok := args[1].([]interface{}) + slice2, ok := args[1].([]any) if ok { if len(slice1) != len(slice2) { return false diff --git a/pkg/iac/scanners/azure/functions/equals_test.go b/pkg/iac/scanners/azure/functions/equals_test.go index e9ad7f03f7c7..afee2c3d92a1 100644 --- a/pkg/iac/scanners/azure/functions/equals_test.go +++ b/pkg/iac/scanners/azure/functions/equals_test.go @@ -9,19 +9,19 @@ import ( func Test_Equals(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "equals with nil", - args: []interface{}{ + args: []any{ nil, }, expected: false, }, { name: "equals with nil and string", - args: []interface{}{ + args: []any{ nil, "test", }, @@ -29,7 +29,7 @@ func Test_Equals(t *testing.T) { }, { name: "equals with nil and string and int", - args: []interface{}{ + args: []any{ nil, "test", 1, @@ -38,16 +38,16 @@ func Test_Equals(t *testing.T) { }, { name: "equals with nil and nil and array", - args: []interface{}{ + args: []any{ nil, nil, - []interface{}{"a", "b", "c"}, + []any{"a", "b", "c"}, }, expected: false, }, { name: "equals with nil and nil", - args: []interface{}{ + args: []any{ nil, nil, }, @@ -55,7 +55,7 @@ func Test_Equals(t *testing.T) { }, { name: "equals with string and string", - args: []interface{}{ + args: []any{ "test", "test", }, @@ -63,7 +63,7 @@ func Test_Equals(t *testing.T) { }, { name: "equals with string and string", - args: []interface{}{ + args: []any{ "test", "test1", }, @@ -71,7 +71,7 @@ func Test_Equals(t *testing.T) { }, { name: "equals with int and int", - args: []interface{}{ + args: []any{ 1, 1, }, @@ -79,7 +79,7 @@ func Test_Equals(t *testing.T) { }, { name: "equals with int and int", - args: []interface{}{ + args: []any{ 1, 2, }, @@ -87,17 +87,17 @@ func Test_Equals(t *testing.T) { }, { name: "equals with array and array", - args: []interface{}{ - []interface{}{"a", "b", "c"}, - []interface{}{"a", "b", "c"}, + args: []any{ + []any{"a", "b", "c"}, + []any{"a", "b", "c"}, }, expected: true, }, { name: "equals with array and array", - args: []interface{}{ - []interface{}{"a", "b", "c"}, - []interface{}{"a", "b", "d"}, + args: []any{ + []any{"a", "b", "c"}, + []any{"a", "b", "d"}, }, expected: false, }, diff --git a/pkg/iac/scanners/azure/functions/false.go b/pkg/iac/scanners/azure/functions/false.go index 26309e333812..267cb936846e 100644 --- a/pkg/iac/scanners/azure/functions/false.go +++ b/pkg/iac/scanners/azure/functions/false.go @@ -1,5 +1,5 @@ package functions -func False(args ...interface{}) interface{} { +func False(args ...any) any { return false } diff --git a/pkg/iac/scanners/azure/functions/first.go b/pkg/iac/scanners/azure/functions/first.go index 91e2aece8e26..83bd61eba20c 100644 --- a/pkg/iac/scanners/azure/functions/first.go +++ b/pkg/iac/scanners/azure/functions/first.go @@ -1,6 +1,6 @@ package functions -func First(args ...interface{}) interface{} { +func First(args ...any) any { if len(args) != 1 { return "" } @@ -12,7 +12,7 @@ func First(args ...interface{}) interface{} { if cType != "" { return string(cType[0]) } - case interface{}: + case any: switch iType := cType.(type) { case []string: if len(iType) > 0 { diff --git a/pkg/iac/scanners/azure/functions/first_test.go b/pkg/iac/scanners/azure/functions/first_test.go index 5ce059750184..690cd3d1f77d 100644 --- a/pkg/iac/scanners/azure/functions/first_test.go +++ b/pkg/iac/scanners/azure/functions/first_test.go @@ -9,33 +9,33 @@ import ( func Test_First(t *testing.T) { test := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "first in empty string", - args: []interface{}{ + args: []any{ "", }, expected: "", }, { name: "first in string", - args: []interface{}{ + args: []any{ "Hello", }, expected: "H", }, { name: "first in empty slice", - args: []interface{}{ + args: []any{ []string{}, }, expected: "", }, { name: "first in slice", - args: []interface{}{ + args: []any{ []string{"Hello", "World"}, }, expected: "Hello", diff --git a/pkg/iac/scanners/azure/functions/float.go b/pkg/iac/scanners/azure/functions/float.go index 512b471b9421..d7a1acfcdfac 100644 --- a/pkg/iac/scanners/azure/functions/float.go +++ b/pkg/iac/scanners/azure/functions/float.go @@ -2,7 +2,7 @@ package functions import "strconv" -func Float(args ...interface{}) interface{} { +func Float(args ...any) any { if len(args) != 1 { return 0.0 } diff --git a/pkg/iac/scanners/azure/functions/float_test.go b/pkg/iac/scanners/azure/functions/float_test.go index a7f5f84a8c20..63610183bcdd 100644 --- a/pkg/iac/scanners/azure/functions/float_test.go +++ b/pkg/iac/scanners/azure/functions/float_test.go @@ -5,22 +5,22 @@ import "testing" func Test_Float(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected float64 }{ { name: "Float with 1", - args: []interface{}{1}, + args: []any{1}, expected: 1.0, }, { name: "Float with 2", - args: []interface{}{"2"}, + args: []any{"2"}, expected: 2.0, }, { name: "Float with 3", - args: []interface{}{"2.3"}, + args: []any{"2.3"}, expected: 2.3, }, } diff --git a/pkg/iac/scanners/azure/functions/format.go b/pkg/iac/scanners/azure/functions/format.go index 207b9ebfdda7..48395f374aa6 100644 --- a/pkg/iac/scanners/azure/functions/format.go +++ b/pkg/iac/scanners/azure/functions/format.go @@ -5,13 +5,13 @@ import ( "strings" ) -func Format(args ...interface{}) interface{} { +func Format(args ...any) any { formatter := generateFormatterString(args...) return fmt.Sprintf(formatter, args[1:]...) } -func generateFormatterString(args ...interface{}) string { +func generateFormatterString(args ...any) string { formatter, ok := args[0].(string) if !ok { diff --git a/pkg/iac/scanners/azure/functions/format_test.go b/pkg/iac/scanners/azure/functions/format_test.go index 8d5e840c61a6..34a0138d1f0a 100644 --- a/pkg/iac/scanners/azure/functions/format_test.go +++ b/pkg/iac/scanners/azure/functions/format_test.go @@ -10,12 +10,12 @@ func Test_FormatCall(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "simple format call", - args: []interface{}{ + args: []any{ "{0}/{1}", "myPostgreSQLServer", "log_checkpoints", @@ -24,7 +24,7 @@ func Test_FormatCall(t *testing.T) { }, { name: "complex format call", - args: []interface{}{ + args: []any{ "{0} + {1} = {2}", 1, 2, 3, }, diff --git a/pkg/iac/scanners/azure/functions/functions.go b/pkg/iac/scanners/azure/functions/functions.go index f4ed7815f485..f24112d80953 100644 --- a/pkg/iac/scanners/azure/functions/functions.go +++ b/pkg/iac/scanners/azure/functions/functions.go @@ -1,12 +1,12 @@ package functions -var deploymentFuncs = map[string]func(dp DeploymentData, args ...interface{}) interface{}{ +var deploymentFuncs = map[string]func(dp DeploymentData, args ...any) any{ "parameters": Parameters, "deployment": Deployment, "environment": Environment, "variables": Variables, } -var generalFuncs = map[string]func(...interface{}) interface{}{ +var generalFuncs = map[string]func(...any) any{ "add": Add, "and": And, @@ -85,7 +85,7 @@ var generalFuncs = map[string]func(...interface{}) interface{}{ "utcNow": UTCNow, } -func Evaluate(deploymentProvider DeploymentData, name string, args ...interface{}) interface{} { +func Evaluate(deploymentProvider DeploymentData, name string, args ...any) any { if f, ok := deploymentFuncs[name]; ok { return f(deploymentProvider, args...) diff --git a/pkg/iac/scanners/azure/functions/greater.go b/pkg/iac/scanners/azure/functions/greater.go index 24bf79834641..458985862010 100644 --- a/pkg/iac/scanners/azure/functions/greater.go +++ b/pkg/iac/scanners/azure/functions/greater.go @@ -1,6 +1,6 @@ package functions -func Greater(args ...interface{}) interface{} { +func Greater(args ...any) any { if len(args) != 2 { return false @@ -22,7 +22,7 @@ func Greater(args ...interface{}) interface{} { return false } -func GreaterOrEquals(args ...interface{}) interface{} { +func GreaterOrEquals(args ...any) any { if len(args) != 2 { return false diff --git a/pkg/iac/scanners/azure/functions/greater_test.go b/pkg/iac/scanners/azure/functions/greater_test.go index 8d3e1b21b25e..ae5fd1c72a7f 100644 --- a/pkg/iac/scanners/azure/functions/greater_test.go +++ b/pkg/iac/scanners/azure/functions/greater_test.go @@ -9,13 +9,13 @@ import ( func Test_Greater(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "greater with nil and string", - args: []interface{}{ + args: []any{ nil, "test", }, @@ -23,7 +23,7 @@ func Test_Greater(t *testing.T) { }, { name: "greater with nil and nil", - args: []interface{}{ + args: []any{ nil, nil, }, @@ -31,7 +31,7 @@ func Test_Greater(t *testing.T) { }, { name: "greater with string and string", - args: []interface{}{ + args: []any{ "test", "test", }, @@ -39,7 +39,7 @@ func Test_Greater(t *testing.T) { }, { name: "greater with string and int", - args: []interface{}{ + args: []any{ "test", 1, }, @@ -47,7 +47,7 @@ func Test_Greater(t *testing.T) { }, { name: "greater with int and int", - args: []interface{}{ + args: []any{ 1, 1, }, @@ -65,13 +65,13 @@ func Test_Greater(t *testing.T) { func Test_GreaterThanOrEqual(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "greater with nil and string", - args: []interface{}{ + args: []any{ nil, "test", }, @@ -79,7 +79,7 @@ func Test_GreaterThanOrEqual(t *testing.T) { }, { name: "greater with nil and nil", - args: []interface{}{ + args: []any{ nil, nil, }, @@ -87,7 +87,7 @@ func Test_GreaterThanOrEqual(t *testing.T) { }, { name: "greater with string and string", - args: []interface{}{ + args: []any{ "test", "test", }, @@ -95,7 +95,7 @@ func Test_GreaterThanOrEqual(t *testing.T) { }, { name: "greater with string and int", - args: []interface{}{ + args: []any{ "test", 1, }, @@ -103,7 +103,7 @@ func Test_GreaterThanOrEqual(t *testing.T) { }, { name: "greater with int and int", - args: []interface{}{ + args: []any{ 1, 1, }, diff --git a/pkg/iac/scanners/azure/functions/guid.go b/pkg/iac/scanners/azure/functions/guid.go index d54bbacb1beb..203accc93292 100644 --- a/pkg/iac/scanners/azure/functions/guid.go +++ b/pkg/iac/scanners/azure/functions/guid.go @@ -7,7 +7,7 @@ import ( "github.com/google/uuid" ) -func Guid(args ...interface{}) interface{} { +func Guid(args ...any) any { if len(args) == 0 { return "" @@ -39,6 +39,6 @@ func generateSeededGUID(seedParts ...string) (uuid.UUID, error) { return id, nil } -func NewGuid(args ...interface{}) interface{} { +func NewGuid(args ...any) any { return uuid.NewString() } diff --git a/pkg/iac/scanners/azure/functions/guid_test.go b/pkg/iac/scanners/azure/functions/guid_test.go index 0e47e5383a54..d29eed0ebd33 100644 --- a/pkg/iac/scanners/azure/functions/guid_test.go +++ b/pkg/iac/scanners/azure/functions/guid_test.go @@ -9,19 +9,19 @@ import ( func Test_Guid(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "guid from a string", - args: []interface{}{ + args: []any{ "hello", }, expected: "2cf24dba-5fb0-430e-a6e8-3b2ac5b9e29e", }, { name: "guid from an string", - args: []interface{}{}, + args: []any{}, expected: "", }, } diff --git a/pkg/iac/scanners/azure/functions/if.go b/pkg/iac/scanners/azure/functions/if.go index 03fd35e360ff..03cfa752c71f 100644 --- a/pkg/iac/scanners/azure/functions/if.go +++ b/pkg/iac/scanners/azure/functions/if.go @@ -1,6 +1,6 @@ package functions -func If(args ...interface{}) interface{} { +func If(args ...any) any { if len(args) != 3 { return nil diff --git a/pkg/iac/scanners/azure/functions/if_test.go b/pkg/iac/scanners/azure/functions/if_test.go index 52c645fb30aa..b13c040efaf1 100644 --- a/pkg/iac/scanners/azure/functions/if_test.go +++ b/pkg/iac/scanners/azure/functions/if_test.go @@ -10,27 +10,27 @@ func Test_If(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "If with true", - args: []interface{}{true, "true", "false"}, + args: []any{true, "true", "false"}, expected: "true", }, { name: "If with false", - args: []interface{}{false, "true", "false"}, + args: []any{false, "true", "false"}, expected: "false", }, { name: "If with true and slice returned", - args: []interface{}{ + args: []any{ true, - []interface{}{"Hello", "World"}, - []interface{}{"Goodbye", "World"}, + []any{"Hello", "World"}, + []any{"Goodbye", "World"}, }, - expected: []interface{}{"Hello", "World"}, + expected: []any{"Hello", "World"}, }, } diff --git a/pkg/iac/scanners/azure/functions/index_of.go b/pkg/iac/scanners/azure/functions/index_of.go index 93896e21e897..25242f90fd7b 100644 --- a/pkg/iac/scanners/azure/functions/index_of.go +++ b/pkg/iac/scanners/azure/functions/index_of.go @@ -2,7 +2,7 @@ package functions import "strings" -func IndexOf(args ...interface{}) interface{} { +func IndexOf(args ...any) any { if len(args) != 2 { return -1 diff --git a/pkg/iac/scanners/azure/functions/index_of_test.go b/pkg/iac/scanners/azure/functions/index_of_test.go index c35d59279942..0e5214aaa8e8 100644 --- a/pkg/iac/scanners/azure/functions/index_of_test.go +++ b/pkg/iac/scanners/azure/functions/index_of_test.go @@ -10,12 +10,12 @@ func Test_IndexOf(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected int }{ { name: "get index of string that is there", - args: []interface{}{ + args: []any{ "Hello world!", "Hell", }, @@ -23,7 +23,7 @@ func Test_IndexOf(t *testing.T) { }, { name: "get index of string that is there as well", - args: []interface{}{ + args: []any{ "Hello world!", "world", }, @@ -31,7 +31,7 @@ func Test_IndexOf(t *testing.T) { }, { name: "get index of string that isn't there", - args: []interface{}{ + args: []any{ "Hello world!", "planet!", }, diff --git a/pkg/iac/scanners/azure/functions/int.go b/pkg/iac/scanners/azure/functions/int.go index f873a29fb0bf..9d82077e5ab5 100644 --- a/pkg/iac/scanners/azure/functions/int.go +++ b/pkg/iac/scanners/azure/functions/int.go @@ -2,7 +2,7 @@ package functions import "strconv" -func Int(args ...interface{}) interface{} { +func Int(args ...any) any { if len(args) != 1 { return 0 } diff --git a/pkg/iac/scanners/azure/functions/int_test.go b/pkg/iac/scanners/azure/functions/int_test.go index 0834ecdd6fc2..aa3e8cd07a13 100644 --- a/pkg/iac/scanners/azure/functions/int_test.go +++ b/pkg/iac/scanners/azure/functions/int_test.go @@ -5,22 +5,22 @@ import "testing" func Test_Int(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected int }{ { name: "Int with 1", - args: []interface{}{1}, + args: []any{1}, expected: 1, }, { name: "Int with 2", - args: []interface{}{"2"}, + args: []any{"2"}, expected: 2, }, { name: "Int with 2.3", - args: []interface{}{"2.3"}, + args: []any{"2.3"}, expected: 0, }, } diff --git a/pkg/iac/scanners/azure/functions/intersection.go b/pkg/iac/scanners/azure/functions/intersection.go index d137a7c2aec8..b813fa77544e 100644 --- a/pkg/iac/scanners/azure/functions/intersection.go +++ b/pkg/iac/scanners/azure/functions/intersection.go @@ -2,33 +2,33 @@ package functions import "sort" -func Intersection(args ...interface{}) interface{} { +func Intersection(args ...any) any { if args == nil || len(args) < 2 { - return []interface{}{} + return []any{} } switch args[0].(type) { - case map[string]interface{}: + case map[string]any: return intersectionMap(args...) - case interface{}: + case any: return intersectionArray(args...) } - return []interface{}{} + return []any{} } -func intersectionArray(args ...interface{}) interface{} { - var result []interface{} - hash := make(map[interface{}]bool) +func intersectionArray(args ...any) any { + var result []any + hash := make(map[any]bool) - for _, arg := range args[0].([]interface{}) { + for _, arg := range args[0].([]any) { hash[arg] = true } for i := 1; i < len(args); i++ { - workingHash := make(map[interface{}]bool) - argArr, ok := args[i].([]interface{}) + workingHash := make(map[any]bool) + argArr, ok := args[i].([]any) if !ok { continue } @@ -51,16 +51,16 @@ func intersectionArray(args ...interface{}) interface{} { return result } -func intersectionMap(args ...interface{}) interface{} { - hash := make(map[string]interface{}) +func intersectionMap(args ...any) any { + hash := make(map[string]any) - for k, v := range args[0].(map[string]interface{}) { + for k, v := range args[0].(map[string]any) { hash[k] = v } for i := 1; i < len(args); i++ { - workingHash := make(map[string]interface{}) - argArr, ok := args[i].(map[string]interface{}) + workingHash := make(map[string]any) + argArr, ok := args[i].(map[string]any) if !ok { continue } diff --git a/pkg/iac/scanners/azure/functions/intersection_test.go b/pkg/iac/scanners/azure/functions/intersection_test.go index 42d23fee4bf7..85b6b0564e30 100644 --- a/pkg/iac/scanners/azure/functions/intersection_test.go +++ b/pkg/iac/scanners/azure/functions/intersection_test.go @@ -10,88 +10,88 @@ func Test_Intersect(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "intersect two arrays", - args: []interface{}{ - []interface{}{"a", "b", "c"}, - []interface{}{"b", "c", "d"}, + args: []any{ + []any{"a", "b", "c"}, + []any{"b", "c", "d"}, }, - expected: []interface{}{"b", "c"}, + expected: []any{"b", "c"}, }, { name: "intersect three arrays", - args: []interface{}{ - []interface{}{"a", "b", "c", "d"}, - []interface{}{"b", "c", "d"}, - []interface{}{"b", "c"}, + args: []any{ + []any{"a", "b", "c", "d"}, + []any{"b", "c", "d"}, + []any{"b", "c"}, }, - expected: []interface{}{"b", "c"}, + expected: []any{"b", "c"}, }, { name: "intersect two arrays with one empty", - args: []interface{}{ - []interface{}{"a", "b", "c"}, - []interface{}{}, + args: []any{ + []any{"a", "b", "c"}, + []any{}, }, - expected: []interface{}(nil), + expected: []any(nil), }, { name: "intersect two arrays with both empty", - args: []interface{}{ - []interface{}{}, - []interface{}{}, + args: []any{ + []any{}, + []any{}, }, - expected: []interface{}(nil), + expected: []any(nil), }, { name: "intersect two arrays with both nil", - args: []interface{}{ + args: []any{ nil, nil, }, - expected: []interface{}{}, + expected: []any{}, }, { name: "intersect two maps", - args: []interface{}{ - map[string]interface{}{ + args: []any{ + map[string]any{ "a": "a", "b": "b", "c": "c", }, - map[string]interface{}{ + map[string]any{ "b": "b", "c": "c", "d": "d", }, }, - expected: map[string]interface{}{ + expected: map[string]any{ "b": "b", "c": "c", }, }, { name: "intersect three maps", - args: []interface{}{ - map[string]interface{}{ + args: []any{ + map[string]any{ "a": "a", "b": "b", "c": "c", }, - map[string]interface{}{ + map[string]any{ "b": "b", "c": "c", "d": "d", }, - map[string]interface{}{ + map[string]any{ "b": "b", "d": "d", }, }, - expected: map[string]interface{}{ + expected: map[string]any{ "b": "b", }, }, diff --git a/pkg/iac/scanners/azure/functions/items.go b/pkg/iac/scanners/azure/functions/items.go index 2b40a369ea46..3f68d8a15ba7 100644 --- a/pkg/iac/scanners/azure/functions/items.go +++ b/pkg/iac/scanners/azure/functions/items.go @@ -1,6 +1,6 @@ package functions -func Items(args ...interface{}) interface{} { +func Items(args ...any) any { return nil } diff --git a/pkg/iac/scanners/azure/functions/join.go b/pkg/iac/scanners/azure/functions/join.go index cdefa43fdad0..10fd76ee9f43 100644 --- a/pkg/iac/scanners/azure/functions/join.go +++ b/pkg/iac/scanners/azure/functions/join.go @@ -2,7 +2,7 @@ package functions import "strings" -func Join(args ...interface{}) interface{} { +func Join(args ...any) any { if len(args) != 2 { return "" diff --git a/pkg/iac/scanners/azure/functions/join_test.go b/pkg/iac/scanners/azure/functions/join_test.go index fab50a4e1e90..614bdfa94ee5 100644 --- a/pkg/iac/scanners/azure/functions/join_test.go +++ b/pkg/iac/scanners/azure/functions/join_test.go @@ -9,12 +9,12 @@ import ( func Test_Join(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "join strings with no items", - args: []interface{}{ + args: []any{ []string{}, " ", }, @@ -22,7 +22,7 @@ func Test_Join(t *testing.T) { }, { name: "join strings", - args: []interface{}{ + args: []any{ []string{"Hello", "World"}, " ", }, diff --git a/pkg/iac/scanners/azure/functions/json.go b/pkg/iac/scanners/azure/functions/json.go index 7694b358737b..07f7c1828ae0 100644 --- a/pkg/iac/scanners/azure/functions/json.go +++ b/pkg/iac/scanners/azure/functions/json.go @@ -2,7 +2,7 @@ package functions import "encoding/json" -func JSON(args ...interface{}) interface{} { +func JSON(args ...any) any { if len(args) != 1 { return "" } @@ -12,7 +12,7 @@ func JSON(args ...interface{}) interface{} { return "" } - var jsonType map[string]interface{} + var jsonType map[string]any if err := json.Unmarshal([]byte(value), &jsonType); err != nil { return "" } diff --git a/pkg/iac/scanners/azure/functions/json_test.go b/pkg/iac/scanners/azure/functions/json_test.go index 1f04cd65026f..46f2285618cd 100644 --- a/pkg/iac/scanners/azure/functions/json_test.go +++ b/pkg/iac/scanners/azure/functions/json_test.go @@ -10,25 +10,25 @@ func Test_JSON(t *testing.T) { tests := []struct { name string - args []interface{} - expected map[string]interface{} + args []any + expected map[string]any }{ { name: "simple json string to json type", - args: []interface{}{ + args: []any{ `{"hello": "world"}`, }, - expected: map[string]interface{}{ + expected: map[string]any{ "hello": "world", }, }, { name: "more complex json string to json type", - args: []interface{}{ + args: []any{ `{"hello": ["world", "world2"]}`, }, - expected: map[string]interface{}{ - "hello": []interface{}{"world", "world2"}, + expected: map[string]any{ + "hello": []any{"world", "world2"}, }, }, } diff --git a/pkg/iac/scanners/azure/functions/last.go b/pkg/iac/scanners/azure/functions/last.go index 84f54fb335fa..bbf307515d82 100644 --- a/pkg/iac/scanners/azure/functions/last.go +++ b/pkg/iac/scanners/azure/functions/last.go @@ -1,6 +1,6 @@ package functions -func Last(args ...interface{}) interface{} { +func Last(args ...any) any { if len(args) != 1 { return "" } @@ -12,7 +12,7 @@ func Last(args ...interface{}) interface{} { if cType != "" { return string(cType[len(cType)-1]) } - case interface{}: + case any: switch iType := cType.(type) { case []string: if len(iType) > 0 { diff --git a/pkg/iac/scanners/azure/functions/last_index_of.go b/pkg/iac/scanners/azure/functions/last_index_of.go index 7dce6320d8fb..1fa46d132c79 100644 --- a/pkg/iac/scanners/azure/functions/last_index_of.go +++ b/pkg/iac/scanners/azure/functions/last_index_of.go @@ -2,7 +2,7 @@ package functions import "strings" -func LastIndexOf(args ...interface{}) interface{} { +func LastIndexOf(args ...any) any { if len(args) != 2 { return -1 diff --git a/pkg/iac/scanners/azure/functions/last_index_of_test.go b/pkg/iac/scanners/azure/functions/last_index_of_test.go index 96b78d72dc5f..3b83a68fe744 100644 --- a/pkg/iac/scanners/azure/functions/last_index_of_test.go +++ b/pkg/iac/scanners/azure/functions/last_index_of_test.go @@ -10,12 +10,12 @@ func Test_LastIndexOf(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected int }{ { name: "get last index of string that is there", - args: []interface{}{ + args: []any{ "Hello world!", "l", }, @@ -23,7 +23,7 @@ func Test_LastIndexOf(t *testing.T) { }, { name: "get last index of string that is there as well", - args: []interface{}{ + args: []any{ "Hello world!", "world", }, @@ -31,7 +31,7 @@ func Test_LastIndexOf(t *testing.T) { }, { name: "get last index of string that isn't there", - args: []interface{}{ + args: []any{ "Hello world!", "planet!", }, diff --git a/pkg/iac/scanners/azure/functions/last_test.go b/pkg/iac/scanners/azure/functions/last_test.go index 2ceafbf8a69a..4073392b131d 100644 --- a/pkg/iac/scanners/azure/functions/last_test.go +++ b/pkg/iac/scanners/azure/functions/last_test.go @@ -9,33 +9,33 @@ import ( func Test_Last(t *testing.T) { test := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "last in empty string", - args: []interface{}{ + args: []any{ "", }, expected: "", }, { name: "last in string", - args: []interface{}{ + args: []any{ "Hello", }, expected: "o", }, { name: "last in empty slice", - args: []interface{}{ + args: []any{ []string{}, }, expected: "", }, { name: "last in slice", - args: []interface{}{ + args: []any{ []string{"Hello", "World"}, }, expected: "World", diff --git a/pkg/iac/scanners/azure/functions/length.go b/pkg/iac/scanners/azure/functions/length.go index d74bfb2553bf..c63a00329709 100644 --- a/pkg/iac/scanners/azure/functions/length.go +++ b/pkg/iac/scanners/azure/functions/length.go @@ -1,6 +1,6 @@ package functions -func Length(args ...interface{}) interface{} { +func Length(args ...any) any { if len(args) != 1 { return 0 @@ -9,9 +9,9 @@ func Length(args ...interface{}) interface{} { switch ctype := args[0].(type) { case string: return len(ctype) - case map[string]interface{}: + case map[string]any: return len(ctype) - case interface{}: + case any: switch iType := ctype.(type) { case []string: return len(iType) @@ -21,7 +21,7 @@ func Length(args ...interface{}) interface{} { return len(iType) case []float64: return len(iType) - case []interface{}: + case []any: return len(iType) } } diff --git a/pkg/iac/scanners/azure/functions/length_test.go b/pkg/iac/scanners/azure/functions/length_test.go index 2d15ba4968cf..09a9f68a4a67 100644 --- a/pkg/iac/scanners/azure/functions/length_test.go +++ b/pkg/iac/scanners/azure/functions/length_test.go @@ -10,33 +10,33 @@ func Test_Length(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected int }{ { name: "length of a string", - args: []interface{}{ + args: []any{ "hello", }, expected: 5, }, { name: "length of an empty string", - args: []interface{}{ + args: []any{ "", }, expected: 0, }, { name: "length of an empty slice", - args: []interface{}{ + args: []any{ []string{}, }, expected: 0, }, { name: "length of an slice with items", - args: []interface{}{ + args: []any{ []string{ "hello", "world", }, diff --git a/pkg/iac/scanners/azure/functions/less.go b/pkg/iac/scanners/azure/functions/less.go index e25b3662c5c9..5cdd7cb84c1d 100644 --- a/pkg/iac/scanners/azure/functions/less.go +++ b/pkg/iac/scanners/azure/functions/less.go @@ -1,6 +1,6 @@ package functions -func Less(args ...interface{}) interface{} { +func Less(args ...any) any { if len(args) != 2 { return false @@ -22,7 +22,7 @@ func Less(args ...interface{}) interface{} { return false } -func LessOrEquals(args ...interface{}) interface{} { +func LessOrEquals(args ...any) any { if len(args) != 2 { return false diff --git a/pkg/iac/scanners/azure/functions/less_test.go b/pkg/iac/scanners/azure/functions/less_test.go index 706ee89db33f..43c7b01e63d6 100644 --- a/pkg/iac/scanners/azure/functions/less_test.go +++ b/pkg/iac/scanners/azure/functions/less_test.go @@ -9,13 +9,13 @@ import ( func Test_Less(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "less with nil and string", - args: []interface{}{ + args: []any{ nil, "test", }, @@ -23,7 +23,7 @@ func Test_Less(t *testing.T) { }, { name: "less with nil and nil", - args: []interface{}{ + args: []any{ nil, nil, }, @@ -31,7 +31,7 @@ func Test_Less(t *testing.T) { }, { name: "less with string and string", - args: []interface{}{ + args: []any{ "test", "test", }, @@ -39,7 +39,7 @@ func Test_Less(t *testing.T) { }, { name: "less with string and int", - args: []interface{}{ + args: []any{ "test", 1, }, @@ -47,7 +47,7 @@ func Test_Less(t *testing.T) { }, { name: "less with int and int", - args: []interface{}{ + args: []any{ 1, 1, }, @@ -65,13 +65,13 @@ func Test_Less(t *testing.T) { func Test_LessThanOrEqual(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "less with nil and string", - args: []interface{}{ + args: []any{ nil, "test", }, @@ -79,7 +79,7 @@ func Test_LessThanOrEqual(t *testing.T) { }, { name: "less with nil and nil", - args: []interface{}{ + args: []any{ nil, nil, }, @@ -87,7 +87,7 @@ func Test_LessThanOrEqual(t *testing.T) { }, { name: "less with string and string", - args: []interface{}{ + args: []any{ "test", "test", }, @@ -95,7 +95,7 @@ func Test_LessThanOrEqual(t *testing.T) { }, { name: "less with string and int", - args: []interface{}{ + args: []any{ "test", 1, }, @@ -103,7 +103,7 @@ func Test_LessThanOrEqual(t *testing.T) { }, { name: "less with int and int", - args: []interface{}{ + args: []any{ 1, 1, }, diff --git a/pkg/iac/scanners/azure/functions/max.go b/pkg/iac/scanners/azure/functions/max.go index b28ddb436ced..e0519bc96e2f 100644 --- a/pkg/iac/scanners/azure/functions/max.go +++ b/pkg/iac/scanners/azure/functions/max.go @@ -1,6 +1,6 @@ package functions -func Max(args ...interface{}) interface{} { +func Max(args ...any) any { switch args[0].(type) { case int: var ints []int @@ -8,7 +8,7 @@ func Max(args ...interface{}) interface{} { ints = append(ints, arg.(int)) } return maxInt(ints) - case interface{}: + case any: if iType, ok := args[0].([]int); ok { return maxInt(iType) } diff --git a/pkg/iac/scanners/azure/functions/max_test.go b/pkg/iac/scanners/azure/functions/max_test.go index 942fad7e9e59..a64afa1e0277 100644 --- a/pkg/iac/scanners/azure/functions/max_test.go +++ b/pkg/iac/scanners/azure/functions/max_test.go @@ -9,40 +9,40 @@ import ( func Test_Max(t *testing.T) { test := []struct { name string - args []interface{} + args []any expected int }{ { name: "max of empty slice", - args: []interface{}{ + args: []any{ []int{}, }, expected: 0, }, { name: "max of slice", - args: []interface{}{ + args: []any{ []int{1, 2, 3}, }, expected: 3, }, { name: "max of slice with negative numbers", - args: []interface{}{ + args: []any{ []int{-1, -2, -3}, }, expected: -1, }, { name: "max of slice with negative and positive numbers", - args: []interface{}{ + args: []any{ []int{-1, 2, -3}, }, expected: 2, }, { name: "max of comma separated numbers", - args: []interface{}{ + args: []any{ 1, 2, 3, 4, 5, }, expected: 5, diff --git a/pkg/iac/scanners/azure/functions/min.go b/pkg/iac/scanners/azure/functions/min.go index 5c0d63332275..cc857aef2b16 100644 --- a/pkg/iac/scanners/azure/functions/min.go +++ b/pkg/iac/scanners/azure/functions/min.go @@ -1,6 +1,6 @@ package functions -func Min(args ...interface{}) interface{} { +func Min(args ...any) any { switch args[0].(type) { case int: var ints []int @@ -8,7 +8,7 @@ func Min(args ...interface{}) interface{} { ints = append(ints, arg.(int)) } return minInt(ints) - case interface{}: + case any: if iType, ok := args[0].([]int); ok { return minInt(iType) } diff --git a/pkg/iac/scanners/azure/functions/min_test.go b/pkg/iac/scanners/azure/functions/min_test.go index 28e12ef69de8..c6a0da68a226 100644 --- a/pkg/iac/scanners/azure/functions/min_test.go +++ b/pkg/iac/scanners/azure/functions/min_test.go @@ -9,40 +9,40 @@ import ( func Test_Min(t *testing.T) { test := []struct { name string - args []interface{} + args []any expected int }{ { name: "min of empty slice", - args: []interface{}{ + args: []any{ []int{}, }, expected: 0, }, { name: "min of slice", - args: []interface{}{ + args: []any{ []int{1, 2, 3}, }, expected: 1, }, { name: "min of slice with negative numbers", - args: []interface{}{ + args: []any{ []int{-1, -2, -3}, }, expected: -3, }, { name: "min of slice with negative and positive numbers", - args: []interface{}{ + args: []any{ []int{-1, 2, -3}, }, expected: -3, }, { name: "min of comma separated numbers", - args: []interface{}{ + args: []any{ 1, 2, 3, 4, 5, }, expected: 1, diff --git a/pkg/iac/scanners/azure/functions/mod.go b/pkg/iac/scanners/azure/functions/mod.go index 34fb12b7a356..8c03d4241df2 100644 --- a/pkg/iac/scanners/azure/functions/mod.go +++ b/pkg/iac/scanners/azure/functions/mod.go @@ -1,6 +1,6 @@ package functions -func Mod(args ...interface{}) interface{} { +func Mod(args ...any) any { if len(args) != 2 { return 0 } diff --git a/pkg/iac/scanners/azure/functions/mod_test.go b/pkg/iac/scanners/azure/functions/mod_test.go index 656e77e9aae3..60a055ae1544 100644 --- a/pkg/iac/scanners/azure/functions/mod_test.go +++ b/pkg/iac/scanners/azure/functions/mod_test.go @@ -5,27 +5,27 @@ import "testing" func Test_Mod(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected int }{ { name: "Mod with 1 and 2", - args: []interface{}{1, 2}, + args: []any{1, 2}, expected: 1, }, { name: "Mod with 2 and 3", - args: []interface{}{2, 3}, + args: []any{2, 3}, expected: 2, }, { name: "Mod with 3 and -4", - args: []interface{}{3, -4}, + args: []any{3, -4}, expected: 3, }, { name: "Mod with 7 and 3", - args: []interface{}{7, 3}, + args: []any{7, 3}, expected: 1, }, } diff --git a/pkg/iac/scanners/azure/functions/mul.go b/pkg/iac/scanners/azure/functions/mul.go index 9d079728107f..0cf8a8242c56 100644 --- a/pkg/iac/scanners/azure/functions/mul.go +++ b/pkg/iac/scanners/azure/functions/mul.go @@ -1,6 +1,6 @@ package functions -func Mul(args ...interface{}) interface{} { +func Mul(args ...any) any { if len(args) != 2 { return nil diff --git a/pkg/iac/scanners/azure/functions/mul_test.go b/pkg/iac/scanners/azure/functions/mul_test.go index cf4ff57607b2..77a74a4e2cf1 100644 --- a/pkg/iac/scanners/azure/functions/mul_test.go +++ b/pkg/iac/scanners/azure/functions/mul_test.go @@ -9,22 +9,22 @@ import ( func Test_Mul(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected int }{ { name: "multiply -2 by 1", - args: []interface{}{-2, 1}, + args: []any{-2, 1}, expected: -2, }, { name: "multiply 4 by 2", - args: []interface{}{4, 2}, + args: []any{4, 2}, expected: 8, }, { name: "multiply 6 by 3", - args: []interface{}{6, 3}, + args: []any{6, 3}, expected: 18, }, } diff --git a/pkg/iac/scanners/azure/functions/not.go b/pkg/iac/scanners/azure/functions/not.go index 5de10af5dffa..7b12d169fc18 100644 --- a/pkg/iac/scanners/azure/functions/not.go +++ b/pkg/iac/scanners/azure/functions/not.go @@ -1,6 +1,6 @@ package functions -func Not(args ...interface{}) interface{} { +func Not(args ...any) any { if len(args) != 1 { return false diff --git a/pkg/iac/scanners/azure/functions/not_test.go b/pkg/iac/scanners/azure/functions/not_test.go index b1a209768f36..808e3b5643dc 100644 --- a/pkg/iac/scanners/azure/functions/not_test.go +++ b/pkg/iac/scanners/azure/functions/not_test.go @@ -9,17 +9,17 @@ import ( func Test_Not(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected bool }{ { name: "Not with true", - args: []interface{}{true}, + args: []any{true}, expected: false, }, { name: "Not with false", - args: []interface{}{false}, + args: []any{false}, expected: true, }, } diff --git a/pkg/iac/scanners/azure/functions/null.go b/pkg/iac/scanners/azure/functions/null.go index 597c5485e9f5..21ccb9714b7c 100644 --- a/pkg/iac/scanners/azure/functions/null.go +++ b/pkg/iac/scanners/azure/functions/null.go @@ -1,5 +1,5 @@ package functions -func Null(args ...interface{}) interface{} { +func Null(args ...any) any { return nil } diff --git a/pkg/iac/scanners/azure/functions/or.go b/pkg/iac/scanners/azure/functions/or.go index 87e6f8627ed4..b94b706e50ae 100644 --- a/pkg/iac/scanners/azure/functions/or.go +++ b/pkg/iac/scanners/azure/functions/or.go @@ -1,6 +1,6 @@ package functions -func Or(args ...interface{}) interface{} { +func Or(args ...any) any { if len(args) <= 1 { return false diff --git a/pkg/iac/scanners/azure/functions/or_test.go b/pkg/iac/scanners/azure/functions/or_test.go index 2361c858a82a..83c7d29cf35d 100644 --- a/pkg/iac/scanners/azure/functions/or_test.go +++ b/pkg/iac/scanners/azure/functions/or_test.go @@ -10,27 +10,27 @@ func Test_Or(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected bool }{ { name: "And with same 2 bools", - args: []interface{}{true, true}, + args: []any{true, true}, expected: true, }, { name: "And with same 3 bools", - args: []interface{}{true, true, true}, + args: []any{true, true, true}, expected: true, }, { name: "And with different 4 bools", - args: []interface{}{true, true, false, true}, + args: []any{true, true, false, true}, expected: true, }, { name: "And with same false 4 bools", - args: []interface{}{false, false, false, false}, + args: []any{false, false, false, false}, expected: false, }, } diff --git a/pkg/iac/scanners/azure/functions/pad.go b/pkg/iac/scanners/azure/functions/pad.go index 9d668210b11c..a95a2e626303 100644 --- a/pkg/iac/scanners/azure/functions/pad.go +++ b/pkg/iac/scanners/azure/functions/pad.go @@ -2,7 +2,7 @@ package functions import "strings" -func PadLeft(args ...interface{}) interface{} { +func PadLeft(args ...any) any { if len(args) != 3 { return "" } diff --git a/pkg/iac/scanners/azure/functions/pad_test.go b/pkg/iac/scanners/azure/functions/pad_test.go index e7d274504298..aefc367f12af 100644 --- a/pkg/iac/scanners/azure/functions/pad_test.go +++ b/pkg/iac/scanners/azure/functions/pad_test.go @@ -10,12 +10,12 @@ func Test_PadLeft(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "pad left with a input smaller than length", - args: []interface{}{ + args: []any{ "1234", 8, "0", @@ -24,7 +24,7 @@ func Test_PadLeft(t *testing.T) { }, { name: "pad left with a input larger than length", - args: []interface{}{ + args: []any{ "1234", 2, "0", @@ -33,7 +33,7 @@ func Test_PadLeft(t *testing.T) { }, { name: "pad left with a input same as than length", - args: []interface{}{ + args: []any{ "1234", 4, "0", @@ -42,7 +42,7 @@ func Test_PadLeft(t *testing.T) { }, { name: "pad left with larger padding character", - args: []interface{}{ + args: []any{ "1234", 8, "00", diff --git a/pkg/iac/scanners/azure/functions/pick_zones.go b/pkg/iac/scanners/azure/functions/pick_zones.go index 982936633dbe..696cfe5a1835 100644 --- a/pkg/iac/scanners/azure/functions/pick_zones.go +++ b/pkg/iac/scanners/azure/functions/pick_zones.go @@ -1,6 +1,6 @@ package functions -func PickZones(args ...interface{}) interface{} { +func PickZones(args ...any) any { if len(args) < 3 { return nil } diff --git a/pkg/iac/scanners/azure/functions/range.go b/pkg/iac/scanners/azure/functions/range.go index 12a3526957d8..33090e276505 100644 --- a/pkg/iac/scanners/azure/functions/range.go +++ b/pkg/iac/scanners/azure/functions/range.go @@ -1,9 +1,9 @@ package functions -func Range(args ...interface{}) interface{} { +func Range(args ...any) any { if len(args) != 2 { - return []interface{}{} + return []any{} } start, ok := args[0].(int) diff --git a/pkg/iac/scanners/azure/functions/range_test.go b/pkg/iac/scanners/azure/functions/range_test.go index 9c0c6a084b6b..eb41cb9b83ec 100644 --- a/pkg/iac/scanners/azure/functions/range_test.go +++ b/pkg/iac/scanners/azure/functions/range_test.go @@ -9,12 +9,12 @@ import ( func Test_Range(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "range for 3 from 1", - args: []interface{}{ + args: []any{ 1, 3, }, @@ -22,7 +22,7 @@ func Test_Range(t *testing.T) { }, { name: "range with for 10 from 3", - args: []interface{}{ + args: []any{ 3, 10, }, @@ -30,7 +30,7 @@ func Test_Range(t *testing.T) { }, { name: "range with for 10 from -10", - args: []interface{}{ + args: []any{ -10, 10, }, diff --git a/pkg/iac/scanners/azure/functions/reference.go b/pkg/iac/scanners/azure/functions/reference.go index 2f7b38ccf741..cb768e102e0d 100644 --- a/pkg/iac/scanners/azure/functions/reference.go +++ b/pkg/iac/scanners/azure/functions/reference.go @@ -4,7 +4,7 @@ import "fmt" // Reference function can't work as per Azure because it requires Azure ARM logic // best effort is to return the resourcename with a suffix to try and make it unique -func Reference(args ...interface{}) interface{} { +func Reference(args ...any) any { if len(args) < 1 { return nil } diff --git a/pkg/iac/scanners/azure/functions/replace.go b/pkg/iac/scanners/azure/functions/replace.go index 09f829db2c50..bbe6ec8f4d9d 100644 --- a/pkg/iac/scanners/azure/functions/replace.go +++ b/pkg/iac/scanners/azure/functions/replace.go @@ -2,7 +2,7 @@ package functions import "strings" -func Replace(args ...interface{}) interface{} { +func Replace(args ...any) any { if len(args) != 3 { return "" } diff --git a/pkg/iac/scanners/azure/functions/replace_test.go b/pkg/iac/scanners/azure/functions/replace_test.go index fe8fb40994cd..ed827d2a7756 100644 --- a/pkg/iac/scanners/azure/functions/replace_test.go +++ b/pkg/iac/scanners/azure/functions/replace_test.go @@ -9,12 +9,12 @@ import ( func Test_Replace(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "replace a string", - args: []interface{}{ + args: []any{ "hello", "l", "p", @@ -23,7 +23,7 @@ func Test_Replace(t *testing.T) { }, { name: "replace a string with invalid replacement", - args: []interface{}{ + args: []any{ "hello", "q", "p", diff --git a/pkg/iac/scanners/azure/functions/resource.go b/pkg/iac/scanners/azure/functions/resource.go index 7eacfaeccff1..164fc6e42819 100644 --- a/pkg/iac/scanners/azure/functions/resource.go +++ b/pkg/iac/scanners/azure/functions/resource.go @@ -4,7 +4,7 @@ import ( "fmt" ) -func ResourceID(args ...interface{}) interface{} { +func ResourceID(args ...any) any { if len(args) < 2 { return nil } @@ -18,7 +18,7 @@ func ResourceID(args ...interface{}) interface{} { return resourceID } -func ExtensionResourceID(args ...interface{}) interface{} { +func ExtensionResourceID(args ...any) any { if len(args) < 3 { return nil } @@ -32,7 +32,7 @@ func ExtensionResourceID(args ...interface{}) interface{} { return resourceID } -func ResourceGroup(args ...interface{}) interface{} { +func ResourceGroup(args ...any) any { return fmt.Sprintf(`{ "id": "/subscriptions/%s/resourceGroups/PlaceHolderResourceGroup", "name": "Placeholder Resource Group", diff --git a/pkg/iac/scanners/azure/functions/scope.go b/pkg/iac/scanners/azure/functions/scope.go index dcd1676b1945..5801cba7c4b0 100644 --- a/pkg/iac/scanners/azure/functions/scope.go +++ b/pkg/iac/scanners/azure/functions/scope.go @@ -14,7 +14,7 @@ var ( managingResourceID = uuid.NewString() ) -func ManagementGroup(_ ...interface{}) interface{} { +func ManagementGroup(_ ...any) any { return fmt.Sprintf(`{ "id": "/providers/Microsoft.Management/managementGroups/mgPlaceholder", @@ -38,7 +38,7 @@ func ManagementGroup(_ ...interface{}) interface{} { `, groupID, updaterID, tenantID) } -func ManagementGroupResourceID(args ...interface{}) interface{} { +func ManagementGroupResourceID(args ...any) any { if len(args) < 2 { return "" } @@ -54,7 +54,7 @@ func ManagementGroupResourceID(args ...interface{}) interface{} { } -func Subscription(_ ...interface{}) interface{} { +func Subscription(_ ...any) any { return fmt.Sprintf(`{ "id": "/subscriptions/%[1]s", "subscriptionId": "%[1]s", @@ -63,7 +63,7 @@ func Subscription(_ ...interface{}) interface{} { }`, subscriptionID, tenantID) } -func SubscriptionResourceID(args ...interface{}) interface{} { +func SubscriptionResourceID(args ...any) any { if len(args) < 2 { return nil } @@ -81,7 +81,7 @@ func SubscriptionResourceID(args ...interface{}) interface{} { } } -func Tenant(_ ...interface{}) interface{} { +func Tenant(_ ...any) any { return fmt.Sprintf(`{ "countryCode": "US", "displayName": "Placeholder Tenant Name", @@ -90,7 +90,7 @@ func Tenant(_ ...interface{}) interface{} { }`, tenantID) } -func TenantResourceID(args ...interface{}) interface{} { +func TenantResourceID(args ...any) any { if len(args) < 2 { return nil } diff --git a/pkg/iac/scanners/azure/functions/scope_test.go b/pkg/iac/scanners/azure/functions/scope_test.go index af84119e350e..10fe7a5f6d02 100644 --- a/pkg/iac/scanners/azure/functions/scope_test.go +++ b/pkg/iac/scanners/azure/functions/scope_test.go @@ -10,12 +10,12 @@ func Test_SubscriptionResourceID(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "scope resource id with subscription ID", - args: []interface{}{ + args: []any{ "4ec875a5-41a2-4837-88cf-4266466e65ed", "Microsoft.Authorization/roleDefinitions", "8e3af657-a8ff-443c-a75c-2fe8c4bcb635", diff --git a/pkg/iac/scanners/azure/functions/skip.go b/pkg/iac/scanners/azure/functions/skip.go index b68296fff66d..dd99604fb680 100644 --- a/pkg/iac/scanners/azure/functions/skip.go +++ b/pkg/iac/scanners/azure/functions/skip.go @@ -1,6 +1,6 @@ package functions -func Skip(args ...interface{}) interface{} { +func Skip(args ...any) any { if len(args) != 2 { return "" } @@ -15,7 +15,7 @@ func Skip(args ...interface{}) interface{} { return "" } return input[count:] - case interface{}: + case any: switch iType := input.(type) { case []int: return iType[count:] @@ -25,7 +25,7 @@ func Skip(args ...interface{}) interface{} { return iType[count:] case []float64: return iType[count:] - case []interface{}: + case []any: return iType[count:] } } diff --git a/pkg/iac/scanners/azure/functions/skip_test.go b/pkg/iac/scanners/azure/functions/skip_test.go index 692e6508f7f1..3a5645cbf6ee 100644 --- a/pkg/iac/scanners/azure/functions/skip_test.go +++ b/pkg/iac/scanners/azure/functions/skip_test.go @@ -10,12 +10,12 @@ func Test_Skip(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "skip a string", - args: []interface{}{ + args: []any{ "hello", 1, }, @@ -23,7 +23,7 @@ func Test_Skip(t *testing.T) { }, { name: "skip beyond the length a string", - args: []interface{}{ + args: []any{ "hello", 6, }, @@ -31,7 +31,7 @@ func Test_Skip(t *testing.T) { }, { name: "skip with a zero count on a string", - args: []interface{}{ + args: []any{ "hello", 0, }, @@ -39,7 +39,7 @@ func Test_Skip(t *testing.T) { }, { name: "skip with slice of ints", - args: []interface{}{ + args: []any{ []int{1, 2, 3, 4, 5}, 2, }, @@ -47,7 +47,7 @@ func Test_Skip(t *testing.T) { }, { name: "skip with slice of strings", - args: []interface{}{ + args: []any{ []string{"hello", "world"}, 1, }, diff --git a/pkg/iac/scanners/azure/functions/split.go b/pkg/iac/scanners/azure/functions/split.go index 47e62e96034a..598f1995211d 100644 --- a/pkg/iac/scanners/azure/functions/split.go +++ b/pkg/iac/scanners/azure/functions/split.go @@ -2,7 +2,7 @@ package functions import "strings" -func Split(args ...interface{}) interface{} { +func Split(args ...any) any { if len(args) != 2 { return "" } @@ -15,7 +15,7 @@ func Split(args ...interface{}) interface{} { switch separator := args[1].(type) { case string: return strings.Split(input, separator) - case interface{}: + case any: if separator, ok := separator.([]string); ok { m := make(map[rune]int) for _, r := range separator { diff --git a/pkg/iac/scanners/azure/functions/split_test.go b/pkg/iac/scanners/azure/functions/split_test.go index e40df07526aa..e396dd2ef937 100644 --- a/pkg/iac/scanners/azure/functions/split_test.go +++ b/pkg/iac/scanners/azure/functions/split_test.go @@ -9,12 +9,12 @@ import ( func Test_Split(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected []string }{ { name: "split a string", - args: []interface{}{ + args: []any{ "hello, world", ",", }, @@ -22,7 +22,7 @@ func Test_Split(t *testing.T) { }, { name: "split a string with multiple separators", - args: []interface{}{ + args: []any{ "one;two,three", []string{",", ";"}, }, diff --git a/pkg/iac/scanners/azure/functions/starts_with.go b/pkg/iac/scanners/azure/functions/starts_with.go index a4eb398cea3d..3ae79bfff34b 100644 --- a/pkg/iac/scanners/azure/functions/starts_with.go +++ b/pkg/iac/scanners/azure/functions/starts_with.go @@ -2,7 +2,7 @@ package functions import "strings" -func StartsWith(args ...interface{}) interface{} { +func StartsWith(args ...any) any { if len(args) != 2 { return false diff --git a/pkg/iac/scanners/azure/functions/starts_with_test.go b/pkg/iac/scanners/azure/functions/starts_with_test.go index 4a745478ee51..400b36c1b894 100644 --- a/pkg/iac/scanners/azure/functions/starts_with_test.go +++ b/pkg/iac/scanners/azure/functions/starts_with_test.go @@ -10,12 +10,12 @@ func Test_StartsWith(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected bool }{ { name: "string ends with", - args: []interface{}{ + args: []any{ "Hello, world!", "Hello,", }, @@ -23,7 +23,7 @@ func Test_StartsWith(t *testing.T) { }, { name: "string does not end with", - args: []interface{}{ + args: []any{ "Hello world!", "Hello,", }, diff --git a/pkg/iac/scanners/azure/functions/string.go b/pkg/iac/scanners/azure/functions/string.go index cba9997d9e9c..278f09629f9d 100644 --- a/pkg/iac/scanners/azure/functions/string.go +++ b/pkg/iac/scanners/azure/functions/string.go @@ -2,7 +2,7 @@ package functions import "fmt" -func String(args ...interface{}) interface{} { +func String(args ...any) any { if len(args) != 1 { return "" } diff --git a/pkg/iac/scanners/azure/functions/string_test.go b/pkg/iac/scanners/azure/functions/string_test.go index ecab50ea8b65..2e8e35bbde88 100644 --- a/pkg/iac/scanners/azure/functions/string_test.go +++ b/pkg/iac/scanners/azure/functions/string_test.go @@ -9,26 +9,26 @@ import ( func Test_String(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "string from a string", - args: []interface{}{ + args: []any{ "hello", }, expected: "hello", }, { name: "string from a bool", - args: []interface{}{ + args: []any{ false, }, expected: "false", }, { name: "string from an int", - args: []interface{}{ + args: []any{ 10, }, expected: "10", diff --git a/pkg/iac/scanners/azure/functions/sub.go b/pkg/iac/scanners/azure/functions/sub.go index 6013a8c0d509..08751ff94656 100644 --- a/pkg/iac/scanners/azure/functions/sub.go +++ b/pkg/iac/scanners/azure/functions/sub.go @@ -1,6 +1,6 @@ package functions -func Sub(args ...interface{}) interface{} { +func Sub(args ...any) any { if len(args) != 2 { return nil diff --git a/pkg/iac/scanners/azure/functions/sub_test.go b/pkg/iac/scanners/azure/functions/sub_test.go index a3f9308a2710..66bd0c403758 100644 --- a/pkg/iac/scanners/azure/functions/sub_test.go +++ b/pkg/iac/scanners/azure/functions/sub_test.go @@ -9,27 +9,27 @@ import ( func Test_Sub(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected int }{ { name: "subtract 2 from 5", - args: []interface{}{5, 2}, + args: []any{5, 2}, expected: 3, }, { name: "subtract 2 from 1", - args: []interface{}{1, 2}, + args: []any{1, 2}, expected: -1, }, { name: "subtract 3 from 2", - args: []interface{}{2, 3}, + args: []any{2, 3}, expected: -1, }, { name: "subtract -4 from 3", - args: []interface{}{3, -4}, + args: []any{3, -4}, expected: 7, }, } diff --git a/pkg/iac/scanners/azure/functions/substring.go b/pkg/iac/scanners/azure/functions/substring.go index fed22f0d14a6..502ba6ac5df9 100644 --- a/pkg/iac/scanners/azure/functions/substring.go +++ b/pkg/iac/scanners/azure/functions/substring.go @@ -1,6 +1,6 @@ package functions -func SubString(args ...interface{}) interface{} { +func SubString(args ...any) any { if len(args) < 2 { return "" } diff --git a/pkg/iac/scanners/azure/functions/substring_test.go b/pkg/iac/scanners/azure/functions/substring_test.go index 56e2ea107c73..633e21b66996 100644 --- a/pkg/iac/scanners/azure/functions/substring_test.go +++ b/pkg/iac/scanners/azure/functions/substring_test.go @@ -10,12 +10,12 @@ func Test_SubString(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "substring a string", - args: []interface{}{ + args: []any{ "hello", 1, 3, @@ -24,7 +24,7 @@ func Test_SubString(t *testing.T) { }, { name: "substring a string with no upper bound", - args: []interface{}{ + args: []any{ "hello", 1, }, @@ -32,7 +32,7 @@ func Test_SubString(t *testing.T) { }, { name: "substring a string with start higher than the length", - args: []interface{}{ + args: []any{ "hello", 10, }, diff --git a/pkg/iac/scanners/azure/functions/take.go b/pkg/iac/scanners/azure/functions/take.go index 738c9d7d8064..9e43ed2ecb9d 100644 --- a/pkg/iac/scanners/azure/functions/take.go +++ b/pkg/iac/scanners/azure/functions/take.go @@ -1,6 +1,6 @@ package functions -func Take(args ...interface{}) interface{} { +func Take(args ...any) any { if len(args) != 2 { return "" } @@ -15,7 +15,7 @@ func Take(args ...interface{}) interface{} { return input } return input[:count] - case interface{}: + case any: switch iType := input.(type) { case []int: if count > len(iType) { @@ -37,7 +37,7 @@ func Take(args ...interface{}) interface{} { return iType } return iType[:count] - case []interface{}: + case []any: if count > len(iType) { return iType } diff --git a/pkg/iac/scanners/azure/functions/take_test.go b/pkg/iac/scanners/azure/functions/take_test.go index 68c19070a6e9..8334a1b7e121 100644 --- a/pkg/iac/scanners/azure/functions/take_test.go +++ b/pkg/iac/scanners/azure/functions/take_test.go @@ -9,12 +9,12 @@ import ( func Test_Take(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "take a string", - args: []interface{}{ + args: []any{ "hello", 2, }, @@ -22,7 +22,7 @@ func Test_Take(t *testing.T) { }, { name: "take a string with invalid count", - args: []interface{}{ + args: []any{ "hello", 10, }, @@ -30,7 +30,7 @@ func Test_Take(t *testing.T) { }, { name: "take a string from slice", - args: []interface{}{ + args: []any{ []string{"a", "b", "c"}, 2, }, @@ -38,7 +38,7 @@ func Test_Take(t *testing.T) { }, { name: "take a string from a slice", - args: []interface{}{ + args: []any{ []string{"a", "b", "c"}, 2, }, @@ -46,7 +46,7 @@ func Test_Take(t *testing.T) { }, { name: "take a string from a slice with invalid count", - args: []interface{}{ + args: []any{ []string{"a", "b", "c"}, 10, }, diff --git a/pkg/iac/scanners/azure/functions/trim.go b/pkg/iac/scanners/azure/functions/trim.go index 5215bbe7f43d..76006154fc7b 100644 --- a/pkg/iac/scanners/azure/functions/trim.go +++ b/pkg/iac/scanners/azure/functions/trim.go @@ -2,7 +2,7 @@ package functions import "strings" -func Trim(args ...interface{}) interface{} { +func Trim(args ...any) any { if len(args) != 1 { return "" } diff --git a/pkg/iac/scanners/azure/functions/trim_test.go b/pkg/iac/scanners/azure/functions/trim_test.go index 44a787b0f268..b77e7b2e229d 100644 --- a/pkg/iac/scanners/azure/functions/trim_test.go +++ b/pkg/iac/scanners/azure/functions/trim_test.go @@ -5,33 +5,33 @@ import "testing" func Test_Trim(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "trim a string", - args: []interface{}{ + args: []any{ " hello ", }, expected: "hello", }, { name: "trim a string with multiple spaces", - args: []interface{}{ + args: []any{ " hello ", }, expected: "hello", }, { name: "trim a string with tabs", - args: []interface{}{ + args: []any{ " hello ", }, expected: "hello", }, { name: "trim a string with new lines", - args: []interface{}{ + args: []any{ ` hello @@ -42,7 +42,7 @@ hello }, { name: "trim a string with tabs, spaces and new lines", - args: []interface{}{ + args: []any{ ` hello @@ -53,7 +53,7 @@ hello }, { name: "trim a string with non string input", - args: []interface{}{ + args: []any{ 10, }, expected: "", diff --git a/pkg/iac/scanners/azure/functions/true.go b/pkg/iac/scanners/azure/functions/true.go index 9f13af580757..422b2beb58eb 100644 --- a/pkg/iac/scanners/azure/functions/true.go +++ b/pkg/iac/scanners/azure/functions/true.go @@ -1,5 +1,5 @@ package functions -func True(args ...interface{}) interface{} { +func True(args ...any) any { return true } diff --git a/pkg/iac/scanners/azure/functions/union.go b/pkg/iac/scanners/azure/functions/union.go index b0db1c3d6ed0..0f55b0b90c62 100644 --- a/pkg/iac/scanners/azure/functions/union.go +++ b/pkg/iac/scanners/azure/functions/union.go @@ -2,30 +2,30 @@ package functions import "sort" -func Union(args ...interface{}) interface{} { +func Union(args ...any) any { if len(args) == 0 { - return []interface{}{} + return []any{} } if len(args) == 1 { return args[0] } switch args[0].(type) { - case map[string]interface{}: + case map[string]any: return unionMap(args...) - case interface{}: + case any: return unionArray(args...) } - return []interface{}{} + return []any{} } -func unionMap(args ...interface{}) interface{} { - result := make(map[string]interface{}) +func unionMap(args ...any) any { + result := make(map[string]any) for _, arg := range args { - if iType, ok := arg.(map[string]interface{}); ok { + if iType, ok := arg.(map[string]any); ok { for k, v := range iType { result[k] = v } @@ -35,12 +35,12 @@ func unionMap(args ...interface{}) interface{} { return result } -func unionArray(args ...interface{}) interface{} { - var result []interface{} - union := make(map[interface{}]bool) +func unionArray(args ...any) any { + var result []any + union := make(map[any]bool) for _, arg := range args { - if iType, ok := arg.([]interface{}); ok { + if iType, ok := arg.([]any); ok { for _, item := range iType { union[item] = true } diff --git a/pkg/iac/scanners/azure/functions/union_test.go b/pkg/iac/scanners/azure/functions/union_test.go index 56d5bf809088..6c030f7d8b8b 100644 --- a/pkg/iac/scanners/azure/functions/union_test.go +++ b/pkg/iac/scanners/azure/functions/union_test.go @@ -9,43 +9,43 @@ import ( func Test_Union(t *testing.T) { tests := []struct { name string - args []interface{} - expected interface{} + args []any + expected any }{ { name: "union single array", - args: []interface{}{ - []interface{}{"a", "b", "c"}, + args: []any{ + []any{"a", "b", "c"}, }, - expected: []interface{}{"a", "b", "c"}, + expected: []any{"a", "b", "c"}, }, { name: "union two arrays", - args: []interface{}{ - []interface{}{"a", "b", "c"}, - []interface{}{"b", "c", "d"}, + args: []any{ + []any{"a", "b", "c"}, + []any{"b", "c", "d"}, }, - expected: []interface{}{"a", "b", "c", "d"}, + expected: []any{"a", "b", "c", "d"}, }, { name: "union two arrays", - args: []interface{}{ - []interface{}{"a", "b", "c"}, - []interface{}{"b", "c", "d"}, - []interface{}{"b", "c", "d", "e"}, + args: []any{ + []any{"a", "b", "c"}, + []any{"b", "c", "d"}, + []any{"b", "c", "d", "e"}, }, - expected: []interface{}{"a", "b", "c", "d", "e"}, + expected: []any{"a", "b", "c", "d", "e"}, }, { name: "union single maps", - args: []interface{}{ - map[string]interface{}{ + args: []any{ + map[string]any{ "a": "a", "b": "b", "c": "c", }, }, - expected: map[string]interface{}{ + expected: map[string]any{ "a": "a", "b": "b", "c": "c", @@ -53,19 +53,19 @@ func Test_Union(t *testing.T) { }, { name: "union two maps", - args: []interface{}{ - map[string]interface{}{ + args: []any{ + map[string]any{ "a": "a", "b": "b", "c": "c", }, - map[string]interface{}{ + map[string]any{ "b": "b", "c": "c", "d": "d", }, }, - expected: map[string]interface{}{ + expected: map[string]any{ "a": "a", "b": "b", "c": "c", @@ -74,24 +74,24 @@ func Test_Union(t *testing.T) { }, { name: "union three maps", - args: []interface{}{ - map[string]interface{}{ + args: []any{ + map[string]any{ "a": "a", "b": "b", "c": "c", }, - map[string]interface{}{ + map[string]any{ "b": "b", "c": "c", "d": "d", }, - map[string]interface{}{ + map[string]any{ "b": "b", "c": "c", "e": "e", }, }, - expected: map[string]interface{}{ + expected: map[string]any{ "a": "a", "b": "b", "c": "c", diff --git a/pkg/iac/scanners/azure/functions/unique_string.go b/pkg/iac/scanners/azure/functions/unique_string.go index fba35c6459ac..1411d2b1fdc0 100644 --- a/pkg/iac/scanners/azure/functions/unique_string.go +++ b/pkg/iac/scanners/azure/functions/unique_string.go @@ -6,7 +6,7 @@ import ( "strings" ) -func UniqueString(args ...interface{}) interface{} { +func UniqueString(args ...any) any { if len(args) == 0 { return "" } diff --git a/pkg/iac/scanners/azure/functions/unique_string_test.go b/pkg/iac/scanners/azure/functions/unique_string_test.go index 035591eb46aa..1cbc3ba03476 100644 --- a/pkg/iac/scanners/azure/functions/unique_string_test.go +++ b/pkg/iac/scanners/azure/functions/unique_string_test.go @@ -9,19 +9,19 @@ import ( func Test_UniqueString(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "unique string from a string", - args: []interface{}{ + args: []any{ "hello", }, expected: "68656c6c6fe3b", }, { name: "unique string from a string", - args: []interface{}{ + args: []any{ "hello", "world", }, diff --git a/pkg/iac/scanners/azure/functions/uri.go b/pkg/iac/scanners/azure/functions/uri.go index 949e12235dea..f3ff499740ba 100644 --- a/pkg/iac/scanners/azure/functions/uri.go +++ b/pkg/iac/scanners/azure/functions/uri.go @@ -5,7 +5,7 @@ import ( "path" ) -func Uri(args ...interface{}) interface{} { +func Uri(args ...any) any { if len(args) != 2 { return "" } diff --git a/pkg/iac/scanners/azure/functions/uri_test.go b/pkg/iac/scanners/azure/functions/uri_test.go index 1a63fe6bbd01..0577093ce4eb 100644 --- a/pkg/iac/scanners/azure/functions/uri_test.go +++ b/pkg/iac/scanners/azure/functions/uri_test.go @@ -9,12 +9,12 @@ import ( func Test_Uri(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "uri from a base and relative with no trailing slash", - args: []interface{}{ + args: []any{ "http://contoso.org/firstpath", "myscript.sh", }, @@ -22,7 +22,7 @@ func Test_Uri(t *testing.T) { }, { name: "uri from a base and relative with trailing slash", - args: []interface{}{ + args: []any{ "http://contoso.org/firstpath/", "myscript.sh", }, @@ -30,7 +30,7 @@ func Test_Uri(t *testing.T) { }, { name: "uri from a base with trailing slash and relative with ../", - args: []interface{}{ + args: []any{ "http://contoso.org/firstpath/", "../myscript.sh", }, diff --git a/pkg/iac/scanners/azure/functions/utc_now.go b/pkg/iac/scanners/azure/functions/utc_now.go index 68c93bd58fee..dcb53d9d2740 100644 --- a/pkg/iac/scanners/azure/functions/utc_now.go +++ b/pkg/iac/scanners/azure/functions/utc_now.go @@ -5,7 +5,7 @@ import ( "time" ) -func UTCNow(args ...interface{}) interface{} { +func UTCNow(args ...any) any { if len(args) > 1 { return nil } diff --git a/pkg/iac/scanners/azure/functions/utc_now_test.go b/pkg/iac/scanners/azure/functions/utc_now_test.go index c203c3e70a0a..5ee8a3c78021 100644 --- a/pkg/iac/scanners/azure/functions/utc_now_test.go +++ b/pkg/iac/scanners/azure/functions/utc_now_test.go @@ -12,19 +12,19 @@ func Test_UTCNow(t *testing.T) { tests := []struct { name string - args []interface{} + args []any expected string }{ { name: "utc now day", - args: []interface{}{ + args: []any{ "d", }, expected: fmt.Sprintf("%d", time.Now().UTC().Day()), }, { name: "utc now date", - args: []interface{}{ + args: []any{ "yyyy-M-d", }, expected: fmt.Sprintf("%d-%d-%d", time.Now().UTC().Year(), time.Now().UTC().Month(), time.Now().UTC().Day()), diff --git a/pkg/iac/scanners/azure/value.go b/pkg/iac/scanners/azure/value.go index 1b21d62de380..c511517b0a57 100644 --- a/pkg/iac/scanners/azure/value.go +++ b/pkg/iac/scanners/azure/value.go @@ -27,7 +27,7 @@ const ( type Value struct { types.Metadata - rLit interface{} + rLit any rMap map[string]Value rArr []Value Kind Kind @@ -38,14 +38,14 @@ var NullValue = Value{ Kind: KindNull, } -func NewValue(value interface{}, metadata types.Metadata) Value { +func NewValue(value any, metadata types.Metadata) Value { v := Value{ Metadata: metadata, } switch ty := value.(type) { - case []interface{}: + case []any: v.Kind = KindArray for _, child := range ty { if internal, ok := child.(Value); ok { @@ -58,7 +58,7 @@ func NewValue(value interface{}, metadata types.Metadata) Value { v.Kind = KindArray v.rArr = append(v.rArr, ty...) - case map[string]interface{}: + case map[string]any: v.Kind = KindObject v.rMap = make(map[string]Value) for key, val := range ty { @@ -261,7 +261,7 @@ func (v Value) AsBoolValue(defaultValue bool, metadata types.Metadata) types.Boo return types.Bool(v.rLit.(bool), v.GetMetadata()) } -func (v Value) EqualTo(value interface{}) bool { +func (v Value) EqualTo(value any) bool { switch ty := value.(type) { case string: return v.AsString() == ty @@ -302,7 +302,7 @@ func (v Value) AsList() []Value { return v.rArr } -func (v Value) Raw() interface{} { +func (v Value) Raw() any { switch v.Kind { case KindArray: // TODO: recursively build raw array diff --git a/pkg/iac/scanners/cloudformation/cftypes/types.go b/pkg/iac/scanners/cloudformation/cftypes/types.go index 0dc3b8b586a2..21949ae099cf 100644 --- a/pkg/iac/scanners/cloudformation/cftypes/types.go +++ b/pkg/iac/scanners/cloudformation/cftypes/types.go @@ -14,7 +14,7 @@ const ( Unknown CfType = "unknown" ) -func TypeFromGoValue(value interface{}) CfType { +func TypeFromGoValue(value any) CfType { switch reflect.TypeOf(value).Kind() { case reflect.String: return String diff --git a/pkg/iac/scanners/cloudformation/parser/file_context.go b/pkg/iac/scanners/cloudformation/parser/file_context.go index 746dae7e024b..78a267cabb04 100644 --- a/pkg/iac/scanners/cloudformation/parser/file_context.go +++ b/pkg/iac/scanners/cloudformation/parser/file_context.go @@ -19,11 +19,11 @@ type FileContext struct { lines []string SourceFormat SourceFormat Ignores ignore.Rules - Parameters map[string]*Parameter `json:"Parameters" yaml:"Parameters"` - Resources map[string]*Resource `json:"Resources" yaml:"Resources"` - Globals map[string]*Resource `json:"Globals" yaml:"Globals"` - Mappings map[string]interface{} `json:"Mappings,omitempty" yaml:"Mappings"` - Conditions map[string]Property `json:"Conditions,omitempty" yaml:"Conditions"` + Parameters map[string]*Parameter `json:"Parameters" yaml:"Parameters"` + Resources map[string]*Resource `json:"Resources" yaml:"Resources"` + Globals map[string]*Resource `json:"Globals" yaml:"Globals"` + Mappings map[string]any `json:"Mappings,omitempty" yaml:"Mappings"` + Conditions map[string]Property `json:"Conditions,omitempty" yaml:"Conditions"` } func (t *FileContext) GetResourceByLogicalID(name string) *Resource { diff --git a/pkg/iac/scanners/cloudformation/parser/fn_find_in_map.go b/pkg/iac/scanners/cloudformation/parser/fn_find_in_map.go index 7767f0126456..b379cba3527e 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_find_in_map.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_find_in_map.go @@ -28,14 +28,14 @@ func ResolveFindInMap(property *Property) (resolved *Property, success bool) { return abortIntrinsic(property, "could not find map %s, returning original Property") } - mapContents := m.(map[string]interface{}) + mapContents := m.(map[string]any) k, ok := mapContents[topLevelKey] if !ok { return abortIntrinsic(property, "could not find %s in the %s map, returning original Property", topLevelKey, mapName) } - mapValues := k.(map[string]interface{}) + mapValues := k.(map[string]any) if prop, ok := mapValues[secondaryLevelKey]; !ok { return abortIntrinsic(property, "could not find a value for %s in %s, returning original Property", secondaryLevelKey, topLevelKey) diff --git a/pkg/iac/scanners/cloudformation/parser/parameter.go b/pkg/iac/scanners/cloudformation/parser/parameter.go index ecd727270022..671058a9f83c 100644 --- a/pkg/iac/scanners/cloudformation/parser/parameter.go +++ b/pkg/iac/scanners/cloudformation/parser/parameter.go @@ -18,8 +18,8 @@ type Parameter struct { } type parameterInner struct { - Type string `yaml:"Type"` - Default interface{} `yaml:"Default"` + Type string `yaml:"Type"` + Default any `yaml:"Default"` } func (p *Parameter) UnmarshalYAML(node *yaml.Node) error { @@ -43,11 +43,11 @@ func (p *Parameter) Type() cftypes.CfType { } } -func (p *Parameter) Default() interface{} { +func (p *Parameter) Default() any { return p.inner.Default } -func (p *Parameter) UpdateDefault(inVal interface{}) { +func (p *Parameter) UpdateDefault(inVal any) { passedVal := inVal.(string) switch p.inner.Type { diff --git a/pkg/iac/scanners/cloudformation/parser/property.go b/pkg/iac/scanners/cloudformation/parser/property.go index ae0c57050a23..4748126e0892 100644 --- a/pkg/iac/scanners/cloudformation/parser/property.go +++ b/pkg/iac/scanners/cloudformation/parser/property.go @@ -32,7 +32,7 @@ type Property struct { type PropertyInner struct { Type cftypes.CfType - Value interface{} `json:"Value" yaml:"Value"` + Value any `json:"Value" yaml:"Value"` } func (p *Property) Comment() string { @@ -129,7 +129,7 @@ func (p *Property) isFunction() bool { return false } -func (p *Property) RawValue() interface{} { +func (p *Property) RawValue() any { return p.Inner.Value } @@ -262,7 +262,7 @@ func (p *Property) GetProperty(path string) *Property { return &Property{} } -func (p *Property) deriveResolved(propType cftypes.CfType, propValue interface{}) *Property { +func (p *Property) deriveResolved(propType cftypes.CfType, propValue any) *Property { return &Property{ ctx: p.ctx, name: p.name, @@ -369,7 +369,7 @@ func (p *Property) GetJsonBytes(squashList ...bool) []byte { lines = removeLeftMargin(lines) yamlContent := strings.Join(lines, "\n") - var body interface{} + var body any if err := yaml.Unmarshal([]byte(yamlContent), &body); err != nil { return nil } @@ -399,15 +399,15 @@ func removeLeftMargin(lines []string) []string { return lines } -func convert(input interface{}) interface{} { +func convert(input any) any { switch x := input.(type) { - case map[interface{}]interface{}: - outpMap := make(map[string]interface{}) + case map[any]any: + outpMap := make(map[string]any) for k, v := range x { outpMap[k.(string)] = convert(v) } return outpMap - case []interface{}: + case []any: for i, v := range x { x[i] = convert(v) } diff --git a/pkg/iac/scanners/cloudformation/parser/property_helpers.go b/pkg/iac/scanners/cloudformation/parser/property_helpers.go index 260ea106be79..868f4d9231cd 100644 --- a/pkg/iac/scanners/cloudformation/parser/property_helpers.go +++ b/pkg/iac/scanners/cloudformation/parser/property_helpers.go @@ -169,7 +169,7 @@ func (p *Property) Len() int { return len(p.AsList()) } -func (p *Property) EqualTo(checkValue interface{}, equalityOptions ...EqualityOptions) bool { +func (p *Property) EqualTo(checkValue any, equalityOptions ...EqualityOptions) bool { var ignoreCase bool for _, option := range equalityOptions { if option == IgnoreCase { @@ -235,7 +235,7 @@ func (p *Property) IsEmpty() bool { } } -func (p *Property) Contains(checkVal interface{}) bool { +func (p *Property) Contains(checkVal any) bool { if p == nil || p.IsNil() { return false } diff --git a/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go b/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go index a537a3c1e9ab..36f2cba89289 100644 --- a/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go +++ b/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go @@ -22,7 +22,7 @@ func Test_EqualTo(t *testing.T) { tests := []struct { name string property *Property - checkValue interface{} + checkValue any opts []EqualityOptions isEqual bool }{ diff --git a/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go b/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go index ab825f02b8fd..814ba52ee61e 100644 --- a/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go +++ b/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go @@ -6,8 +6,8 @@ import ( type pseudoParameter struct { t cftypes.CfType - val interface{} - raw interface{} + val any + raw any } var pseudoParameters = map[string]pseudoParameter{ @@ -38,7 +38,7 @@ var pseudoParameters = map[string]pseudoParameter{ "AWS::URLSuffix": {t: cftypes.String, val: "amazonaws.com"}, } -func (p pseudoParameter) getRawValue() interface{} { +func (p pseudoParameter) getRawValue() any { switch p.t { case cftypes.List: return p.raw diff --git a/pkg/iac/scanners/cloudformation/parser/pseudo_parameters_test.go b/pkg/iac/scanners/cloudformation/parser/pseudo_parameters_test.go index 281bf9083a14..e653b74e65ef 100644 --- a/pkg/iac/scanners/cloudformation/parser/pseudo_parameters_test.go +++ b/pkg/iac/scanners/cloudformation/parser/pseudo_parameters_test.go @@ -10,7 +10,7 @@ func Test_Raw(t *testing.T) { tests := []struct { name string key string - expected interface{} + expected any }{ { name: "parameter with a string type value", diff --git a/pkg/iac/scanners/helm/parser/parser.go b/pkg/iac/scanners/helm/parser/parser.go index ddcfac09682f..8768ac459867 100644 --- a/pkg/iac/scanners/helm/parser/parser.go +++ b/pkg/iac/scanners/helm/parser/parser.go @@ -186,7 +186,7 @@ func (p *Parser) extractChartName(chartPath string) error { } defer func() { _ = chrt.Close() }() - var chartContent map[string]interface{} + var chartContent map[string]any if err := yaml.NewDecoder(chrt).Decode(&chartContent); err != nil { // the chart likely has the name templated and so cannot be parsed as yaml - use a temporary name if dir := filepath.Dir(chartPath); dir != "" && dir != "." { diff --git a/pkg/iac/scanners/helm/parser/vals.go b/pkg/iac/scanners/helm/parser/vals.go index b54cd7c3a521..f2589b3caec7 100644 --- a/pkg/iac/scanners/helm/parser/vals.go +++ b/pkg/iac/scanners/helm/parser/vals.go @@ -21,12 +21,12 @@ type ValueOptions struct { // MergeValues merges values from files specified via -f/--values and directly // via --set, --set-string, or --set-file, marshaling them to YAML -func (opts *ValueOptions) MergeValues() (map[string]interface{}, error) { - base := make(map[string]interface{}) +func (opts *ValueOptions) MergeValues() (map[string]any, error) { + base := make(map[string]any) // User specified a values files via -f/--values for _, filePath := range opts.ValueFiles { - currentMap := make(map[string]interface{}) + currentMap := make(map[string]any) bytes, err := readFile(filePath) if err != nil { @@ -56,7 +56,7 @@ func (opts *ValueOptions) MergeValues() (map[string]interface{}, error) { // User specified a value via --set-file for _, value := range opts.FileValues { - reader := func(rs []rune) (interface{}, error) { + reader := func(rs []rune) (any, error) { bytes, err := readFile(string(rs)) if err != nil { return nil, err @@ -71,15 +71,15 @@ func (opts *ValueOptions) MergeValues() (map[string]interface{}, error) { return base, nil } -func mergeMaps(a, b map[string]interface{}) map[string]interface{} { - out := make(map[string]interface{}, len(a)) +func mergeMaps(a, b map[string]any) map[string]any { + out := make(map[string]any, len(a)) for k, v := range a { out[k] = v } for k, v := range b { - if v, ok := v.(map[string]interface{}); ok { + if v, ok := v.(map[string]any); ok { if bv, ok := out[k]; ok { - if bv, ok := bv.(map[string]interface{}); ok { + if bv, ok := bv.(map[string]any); ok { out[k] = mergeMaps(bv, v) continue } diff --git a/pkg/iac/scanners/json/parser/parser.go b/pkg/iac/scanners/json/parser/parser.go index 3504e3e10a3c..340f23c9d11c 100644 --- a/pkg/iac/scanners/json/parser/parser.go +++ b/pkg/iac/scanners/json/parser/parser.go @@ -36,9 +36,9 @@ func New(opts ...options.ParserOption) *Parser { return p } -func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]interface{}, error) { +func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]any, error) { - files := make(map[string]interface{}) + files := make(map[string]any) if err := fs.WalkDir(target, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error { select { case <-ctx.Done(): @@ -68,13 +68,13 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st } // ParseFile parses Dockerfile content from the provided filesystem path. -func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (interface{}, error) { +func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (any, error) { f, err := fsys.Open(filepath.ToSlash(path)) if err != nil { return nil, err } defer func() { _ = f.Close() }() - var target interface{} + var target any if err := json.NewDecoder(f).Decode(&target); err != nil { return nil, err } diff --git a/pkg/iac/scanners/json/parser/parser_test.go b/pkg/iac/scanners/json/parser/parser_test.go index 2af3936d6124..ed7b87492d96 100644 --- a/pkg/iac/scanners/json/parser/parser_test.go +++ b/pkg/iac/scanners/json/parser/parser_test.go @@ -19,13 +19,13 @@ func Test_Parser(t *testing.T) { data, err := New().ParseFile(context.TODO(), memfs, "something.json") require.NoError(t, err) - msi, ok := data.(map[string]interface{}) + msi, ok := data.(map[string]any) require.True(t, ok) xObj, ok := msi["x"] require.True(t, ok) - xMsi, ok := xObj.(map[string]interface{}) + xMsi, ok := xObj.(map[string]any) require.True(t, ok) yRaw, ok := xMsi["y"] @@ -39,7 +39,7 @@ func Test_Parser(t *testing.T) { zRaw, ok := xMsi["z"] require.True(t, ok) - z, ok := zRaw.([]interface{}) + z, ok := zRaw.([]any) require.True(t, ok) require.Len(t, z, 3) diff --git a/pkg/iac/scanners/kubernetes/parser/manifest.go b/pkg/iac/scanners/kubernetes/parser/manifest.go index 82da971b3e30..078829b156da 100644 --- a/pkg/iac/scanners/kubernetes/parser/manifest.go +++ b/pkg/iac/scanners/kubernetes/parser/manifest.go @@ -28,6 +28,6 @@ func (m *Manifest) UnmarshalYAML(value *yaml.Node) error { return nil } -func (m *Manifest) ToRego() interface{} { +func (m *Manifest) ToRego() any { return m.Content.ToRego() } diff --git a/pkg/iac/scanners/kubernetes/parser/manifest_node.go b/pkg/iac/scanners/kubernetes/parser/manifest_node.go index 17111e70b1e5..4110c9035646 100644 --- a/pkg/iac/scanners/kubernetes/parser/manifest_node.go +++ b/pkg/iac/scanners/kubernetes/parser/manifest_node.go @@ -23,12 +23,12 @@ type ManifestNode struct { StartLine int EndLine int Offset int - Value interface{} + Value any Type TagType Path string } -func (r *ManifestNode) ToRego() interface{} { +func (r *ManifestNode) ToRego() any { if r == nil { return nil } @@ -36,14 +36,14 @@ func (r *ManifestNode) ToRego() interface{} { case TagBool, TagInt, TagString, TagStr: return r.Value case TagSlice: - var output []interface{} + var output []any for _, node := range r.Value.([]ManifestNode) { output = append(output, node.ToRego()) } return output case TagMap: - output := make(map[string]interface{}) - output["__defsec_metadata"] = map[string]interface{}{ + output := make(map[string]any) + output["__defsec_metadata"] = map[string]any{ "startline": r.StartLine, "endline": r.EndLine, "filepath": r.Path, diff --git a/pkg/iac/scanners/kubernetes/parser/parser.go b/pkg/iac/scanners/kubernetes/parser/parser.go index 658d7a2e6cf4..a1c5ecf46962 100644 --- a/pkg/iac/scanners/kubernetes/parser/parser.go +++ b/pkg/iac/scanners/kubernetes/parser/parser.go @@ -43,8 +43,8 @@ func New(opts ...options.ParserOption) *Parser { return p } -func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string][]interface{}, error) { - files := make(map[string][]interface{}) +func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string][]any, error) { + files := make(map[string][]any) if err := fs.WalkDir(target, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error { select { case <-ctx.Done(): @@ -74,7 +74,7 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st } // ParseFile parses Kubernetes manifest from the provided filesystem path. -func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]interface{}, error) { +func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]any, error) { f, err := fsys.Open(filepath.ToSlash(path)) if err != nil { return nil, err @@ -98,7 +98,7 @@ func (p *Parser) required(fsys fs.FS, path string) bool { return false } -func (p *Parser) Parse(r io.Reader, path string) ([]interface{}, error) { +func (p *Parser) Parse(r io.Reader, path string) ([]any, error) { contents, err := io.ReadAll(r) if err != nil { @@ -110,7 +110,7 @@ func (p *Parser) Parse(r io.Reader, path string) ([]interface{}, error) { } if strings.TrimSpace(string(contents))[0] == '{' { - var target interface{} + var target any if err := json.Unmarshal(contents, &target); err != nil { return nil, err } @@ -121,7 +121,7 @@ func (p *Parser) Parse(r io.Reader, path string) ([]interface{}, error) { } } - var results []interface{} + var results []any re := regexp.MustCompile(`(?m:^---\r?\n)`) pos := 0 diff --git a/pkg/iac/scanners/terraform/attribute_test.go b/pkg/iac/scanners/terraform/attribute_test.go index 5c97985d7146..875c8fc6cc42 100644 --- a/pkg/iac/scanners/terraform/attribute_test.go +++ b/pkg/iac/scanners/terraform/attribute_test.go @@ -297,7 +297,7 @@ func Test_AttributeIsAny(t *testing.T) { name string source string checkAttribute string - checkValue []interface{} + checkValue []any expectedResult bool }{ { @@ -308,7 +308,7 @@ resource "aws_s3_bucket" "my-bucket" { acl = "public-read" }`, checkAttribute: "acl", - checkValue: []interface{}{"private", "authenticated-read"}, + checkValue: []any{"private", "authenticated-read"}, expectedResult: false, }, { @@ -319,7 +319,7 @@ resource "aws_s3_bucket" "my-bucket" { acl = "private" }`, checkAttribute: "acl", - checkValue: []interface{}{"private", "authenticated-read"}, + checkValue: []any{"private", "authenticated-read"}, expectedResult: true, }, { @@ -329,7 +329,7 @@ resource "aws_security_group" "my-security_group" { count = 1 }`, checkAttribute: "count", - checkValue: []interface{}{1, 2}, + checkValue: []any{1, 2}, expectedResult: true, }, } @@ -355,7 +355,7 @@ func Test_AttributeIsNone(t *testing.T) { name string source string checkAttribute string - checkValue []interface{} + checkValue []any expectedResult bool }{ { @@ -366,7 +366,7 @@ resource "aws_s3_bucket" "my-bucket" { acl = "public-read" }`, checkAttribute: "acl", - checkValue: []interface{}{"private", "authenticated-read"}, + checkValue: []any{"private", "authenticated-read"}, expectedResult: true, }, { @@ -377,7 +377,7 @@ resource "aws_s3_bucket" "my-bucket" { acl = "private" }`, checkAttribute: "acl", - checkValue: []interface{}{"private", "authenticated-read"}, + checkValue: []any{"private", "authenticated-read"}, expectedResult: false, }, { @@ -387,7 +387,7 @@ resource "aws_security_group" "my-security_group" { count = 0 }`, checkAttribute: "count", - checkValue: []interface{}{1, 2}, + checkValue: []any{1, 2}, expectedResult: true, }, } @@ -413,7 +413,7 @@ func Test_AttributeIsEmpty(t *testing.T) { name string source string checkAttribute string - checkValue []interface{} + checkValue []any expectedResult bool }{ { diff --git a/pkg/iac/scanners/terraform/executor/pool.go b/pkg/iac/scanners/terraform/executor/pool.go index 69b8405ee3a7..cc2091ff71ed 100644 --- a/pkg/iac/scanners/terraform/executor/pool.go +++ b/pkg/iac/scanners/terraform/executor/pool.go @@ -250,7 +250,7 @@ type Worker struct { incoming <-chan Job mu sync.Mutex results scan.Results - panic interface{} + panic any } func NewWorker(incoming <-chan Job) *Worker { diff --git a/pkg/iac/scanners/terraform/parser/funcs/redact.go b/pkg/iac/scanners/terraform/parser/funcs/redact.go index f5908fc7da57..9f1e8d3551f6 100644 --- a/pkg/iac/scanners/terraform/parser/funcs/redact.go +++ b/pkg/iac/scanners/terraform/parser/funcs/redact.go @@ -7,7 +7,7 @@ import ( "github.com/zclconf/go-cty/cty" ) -func redactIfSensitive(value interface{}, markses ...cty.ValueMarks) string { +func redactIfSensitive(value any, markses ...cty.ValueMarks) string { if Has(cty.DynamicVal.WithMarks(markses...), MarkedSensitive) { return "(sensitive value)" } diff --git a/pkg/iac/scanners/terraform/parser/resolvers/options.go b/pkg/iac/scanners/terraform/parser/resolvers/options.go index 8373a78525cd..1a676ae9e733 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/options.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/options.go @@ -23,6 +23,6 @@ func (o *Options) hasPrefix(prefixes ...string) bool { return false } -func (o *Options) Debug(format string, args ...interface{}) { +func (o *Options) Debug(format string, args ...any) { o.DebugLogger.Log(format, args...) } diff --git a/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go b/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go index 4d4d04feb093..85d565bd307f 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go +++ b/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go @@ -101,14 +101,14 @@ func getResources(module Module, resourceChanges []ResourceChange, configuration // process the changes to get the after state for k, v := range changes.After { switch t := v.(type) { - case []interface{}: + case []any: if len(t) == 0 { continue } val := t[0] switch v := val.(type) { // is it a HCL block? - case map[string]interface{}: + case map[string]any: res.Blocks[k] = v // just a normal attribute then default: diff --git a/pkg/iac/scanners/terraformplan/tfjson/parser/plan_file.go b/pkg/iac/scanners/terraformplan/tfjson/parser/plan_file.go index 42dfccb2d579..6275c228182e 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/parser/plan_file.go +++ b/pkg/iac/scanners/terraformplan/tfjson/parser/plan_file.go @@ -17,12 +17,12 @@ type ResourceChange struct { type ConfigurationResource struct { Resource - Expressions map[string]interface{} `json:"expressions"` + Expressions map[string]any `json:"expressions"` } type Change struct { - Before map[string]interface{} `json:"before"` - After map[string]interface{} `json:"after"` + Before map[string]any `json:"before"` + After map[string]any `json:"after"` } type Module struct { diff --git a/pkg/iac/scanners/toml/parser/parser.go b/pkg/iac/scanners/toml/parser/parser.go index 9b572267fdf9..a8cdcd730f86 100644 --- a/pkg/iac/scanners/toml/parser/parser.go +++ b/pkg/iac/scanners/toml/parser/parser.go @@ -37,9 +37,9 @@ func New(opts ...options.ParserOption) *Parser { return p } -func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]interface{}, error) { +func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]any, error) { - files := make(map[string]interface{}) + files := make(map[string]any) if err := fs.WalkDir(target, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error { select { case <-ctx.Done(): @@ -69,13 +69,13 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st } // ParseFile parses toml content from the provided filesystem path. -func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (interface{}, error) { +func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (any, error) { f, err := fsys.Open(filepath.ToSlash(path)) if err != nil { return nil, err } defer func() { _ = f.Close() }() - var target interface{} + var target any if _, err := toml.NewDecoder(f).Decode(&target); err != nil { return nil, err } diff --git a/pkg/iac/scanners/toml/parser/parser_test.go b/pkg/iac/scanners/toml/parser/parser_test.go index d6ae51d1bf7f..1c9e9bdc341e 100644 --- a/pkg/iac/scanners/toml/parser/parser_test.go +++ b/pkg/iac/scanners/toml/parser/parser_test.go @@ -23,13 +23,13 @@ z = ["a", "b", "c"] data, err := New().ParseFile(context.TODO(), memfs, "something.yaml") require.NoError(t, err) - msi, ok := data.(map[string]interface{}) + msi, ok := data.(map[string]any) require.True(t, ok) xObj, ok := msi["x"] require.True(t, ok) - xMsi, ok := xObj.(map[string]interface{}) + xMsi, ok := xObj.(map[string]any) require.True(t, ok) yRaw, ok := xMsi["y"] @@ -43,7 +43,7 @@ z = ["a", "b", "c"] zRaw, ok := xMsi["z"] require.True(t, ok) - z, ok := zRaw.([]interface{}) + z, ok := zRaw.([]any) require.True(t, ok) require.Len(t, z, 3) diff --git a/pkg/iac/scanners/yaml/parser/parser.go b/pkg/iac/scanners/yaml/parser/parser.go index 6f22a0742d1e..6f113635be4a 100644 --- a/pkg/iac/scanners/yaml/parser/parser.go +++ b/pkg/iac/scanners/yaml/parser/parser.go @@ -39,9 +39,9 @@ func New(opts ...options.ParserOption) *Parser { return p } -func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string][]interface{}, error) { +func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string][]any, error) { - files := make(map[string][]interface{}) + files := make(map[string][]any) if err := fs.WalkDir(target, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error { select { case <-ctx.Done(): @@ -71,7 +71,7 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st } // ParseFile parses yaml content from the provided filesystem path. -func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]interface{}, error) { +func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]any, error) { f, err := fsys.Open(filepath.ToSlash(path)) if err != nil { return nil, err @@ -83,7 +83,7 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]interf return nil, err } - var results []interface{} + var results []any marker := "\n---\n" altMarker := "\r\n---\r\n" @@ -92,7 +92,7 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]interf } for _, partial := range strings.Split(string(contents), marker) { - var target interface{} + var target any if err := yaml.Unmarshal([]byte(partial), &target); err != nil { return nil, err } diff --git a/pkg/iac/scanners/yaml/parser/parser_test.go b/pkg/iac/scanners/yaml/parser/parser_test.go index 8d89ce78223b..f7817d5990fa 100644 --- a/pkg/iac/scanners/yaml/parser/parser_test.go +++ b/pkg/iac/scanners/yaml/parser/parser_test.go @@ -28,13 +28,13 @@ x: assert.Len(t, data, 1) - msi, ok := data[0].(map[string]interface{}) + msi, ok := data[0].(map[string]any) require.True(t, ok) xObj, ok := msi["x"] require.True(t, ok) - xMsi, ok := xObj.(map[string]interface{}) + xMsi, ok := xObj.(map[string]any) require.True(t, ok) yRaw, ok := xMsi["y"] @@ -48,7 +48,7 @@ x: zRaw, ok := xMsi["z"] require.True(t, ok) - z, ok := zRaw.([]interface{}) + z, ok := zRaw.([]any) require.True(t, ok) require.Len(t, z, 3) @@ -86,13 +86,13 @@ x: assert.Len(t, data, 2) { - msi, ok := data[0].(map[string]interface{}) + msi, ok := data[0].(map[string]any) require.True(t, ok) xObj, ok := msi["x"] require.True(t, ok) - xMsi, ok := xObj.(map[string]interface{}) + xMsi, ok := xObj.(map[string]any) require.True(t, ok) yRaw, ok := xMsi["y"] @@ -106,7 +106,7 @@ x: zRaw, ok := xMsi["z"] require.True(t, ok) - z, ok := zRaw.([]interface{}) + z, ok := zRaw.([]any) require.True(t, ok) require.Len(t, z, 3) @@ -117,13 +117,13 @@ x: } { - msi, ok := data[1].(map[string]interface{}) + msi, ok := data[1].(map[string]any) require.True(t, ok) xObj, ok := msi["x"] require.True(t, ok) - xMsi, ok := xObj.(map[string]interface{}) + xMsi, ok := xObj.(map[string]any) require.True(t, ok) yRaw, ok := xMsi["y"] @@ -137,7 +137,7 @@ x: zRaw, ok := xMsi["z"] require.True(t, ok) - z, ok := zRaw.([]interface{}) + z, ok := zRaw.([]any) require.True(t, ok) require.Len(t, z, 3) diff --git a/pkg/iac/state/state.go b/pkg/iac/state/state.go index 580c2e4813f8..bf04db71aa54 100755 --- a/pkg/iac/state/state.go +++ b/pkg/iac/state/state.go @@ -29,6 +29,6 @@ type State struct { Nifcloud nifcloud.Nifcloud } -func (a *State) ToRego() interface{} { +func (a *State) ToRego() any { return convert.StructToRego(reflect.ValueOf(a)) } diff --git a/pkg/iac/state/state_test.go b/pkg/iac/state/state_test.go index 89169693b8c9..60b4b061a2d3 100644 --- a/pkg/iac/state/state_test.go +++ b/pkg/iac/state/state_test.go @@ -32,12 +32,12 @@ func Test_RegoConversion(t *testing.T) { }, } converted := s.ToRego() - assert.Equal(t, map[string]interface{}{ - "aws": map[string]interface{}{ - "s3": map[string]interface{}{ - "buckets": []interface{}{ - map[string]interface{}{ - "__defsec_metadata": map[string]interface{}{ + assert.Equal(t, map[string]any{ + "aws": map[string]any{ + "s3": map[string]any{ + "buckets": []any{ + map[string]any{ + "__defsec_metadata": map[string]any{ "resource": "aws_s3_bucket.example", "sourceprefix": "", "filepath": "main.tf", @@ -47,7 +47,7 @@ func Test_RegoConversion(t *testing.T) { "explicit": false, "fskey": "", }, - "name": map[string]interface{}{ + "name": map[string]any{ "resource": "aws_s3_bucket.example.bucket", "sourceprefix": "", "filepath": "main.tf", diff --git a/pkg/iac/terraform/attribute.go b/pkg/iac/terraform/attribute.go index 28b94bf71743..39161a72a379 100644 --- a/pkg/iac/terraform/attribute.go +++ b/pkg/iac/terraform/attribute.go @@ -65,7 +65,7 @@ func (a *Attribute) GetMetadata() iacTypes.Metadata { return a.metadata } -func (a *Attribute) GetRawValue() interface{} { +func (a *Attribute) GetRawValue() any { switch typ := a.Type(); typ { case cty.String: return a.Value().AsString() @@ -470,13 +470,13 @@ func (a *Attribute) extractListValues() []string { return values } -func (a *Attribute) mapContains(checkValue interface{}, val cty.Value) bool { +func (a *Attribute) mapContains(checkValue any, val cty.Value) bool { if a == nil { return false } valueMap := val.AsValueMap() switch t := checkValue.(type) { - case map[interface{}]interface{}: + case map[any]any: for k, v := range t { for key, value := range valueMap { rawValue := getRawValue(value) @@ -486,7 +486,7 @@ func (a *Attribute) mapContains(checkValue interface{}, val cty.Value) bool { } } return false - case map[string]interface{}: + case map[string]any: for k, v := range t { for key, value := range valueMap { rawValue := getRawValue(value) @@ -506,11 +506,11 @@ func (a *Attribute) mapContains(checkValue interface{}, val cty.Value) bool { } } -func (a *Attribute) NotContains(checkValue interface{}, equalityOptions ...EqualityOption) bool { +func (a *Attribute) NotContains(checkValue any, equalityOptions ...EqualityOption) bool { return !a.Contains(checkValue, equalityOptions...) } -func (a *Attribute) Contains(checkValue interface{}, equalityOptions ...EqualityOption) bool { +func (a *Attribute) Contains(checkValue any, equalityOptions ...EqualityOption) bool { if a == nil { return false } @@ -542,7 +542,7 @@ func (a *Attribute) Contains(checkValue interface{}, equalityOptions ...Equality return strings.Contains(val.AsString(), stringToLookFor) } -func (a *Attribute) OnlyContains(checkValue interface{}) bool { +func (a *Attribute) OnlyContains(checkValue any) bool { if a == nil { return false } @@ -551,7 +551,7 @@ func (a *Attribute) OnlyContains(checkValue interface{}) bool { return false } - checkSlice, ok := checkValue.([]interface{}) + checkSlice, ok := checkValue.([]any) if !ok { return false } @@ -600,7 +600,7 @@ func containsIgnoreCase(left, substring string) bool { return strings.Contains(strings.ToLower(left), strings.ToLower(substring)) } -func (a *Attribute) StartsWith(prefix interface{}) bool { +func (a *Attribute) StartsWith(prefix any) bool { if a == nil { return false } @@ -610,7 +610,7 @@ func (a *Attribute) StartsWith(prefix interface{}) bool { return false } -func (a *Attribute) EndsWith(suffix interface{}) bool { +func (a *Attribute) EndsWith(suffix any) bool { if a == nil { return false } @@ -626,7 +626,7 @@ const ( IgnoreCase EqualityOption = iota ) -func (a *Attribute) Equals(checkValue interface{}, equalityOptions ...EqualityOption) bool { +func (a *Attribute) Equals(checkValue any, equalityOptions ...EqualityOption) bool { if a == nil { return false } @@ -653,7 +653,7 @@ func (a *Attribute) Equals(checkValue interface{}, equalityOptions ...EqualityOp return false } -func (a *Attribute) NotEqual(checkValue interface{}, equalityOptions ...EqualityOption) bool { +func (a *Attribute) NotEqual(checkValue any, equalityOptions ...EqualityOption) bool { return !a.Equals(checkValue, equalityOptions...) } @@ -668,11 +668,11 @@ func (a *Attribute) RegexMatches(re regexp.Regexp) bool { return false } -func (a *Attribute) IsNotAny(options ...interface{}) bool { +func (a *Attribute) IsNotAny(options ...any) bool { return !a.IsAny(options...) } -func (a *Attribute) IsAny(options ...interface{}) bool { +func (a *Attribute) IsAny(options ...any) bool { if a == nil { return false } @@ -698,7 +698,7 @@ func (a *Attribute) IsAny(options ...interface{}) bool { return false } -func (a *Attribute) IsNone(options ...interface{}) bool { +func (a *Attribute) IsNone(options ...any) bool { if a == nil { return false } @@ -844,7 +844,7 @@ func (a *Attribute) AsMapValue() iacTypes.MapValue { return iacTypes.Map(values, a.GetMetadata()) } -func (a *Attribute) LessThan(checkValue interface{}) bool { +func (a *Attribute) LessThan(checkValue any) bool { if a == nil { return false } @@ -859,7 +859,7 @@ func (a *Attribute) LessThan(checkValue interface{}) bool { return false } -func (a *Attribute) LessThanOrEqualTo(checkValue interface{}) bool { +func (a *Attribute) LessThanOrEqualTo(checkValue any) bool { if a == nil { return false } @@ -874,7 +874,7 @@ func (a *Attribute) LessThanOrEqualTo(checkValue interface{}) bool { return false } -func (a *Attribute) GreaterThan(checkValue interface{}) bool { +func (a *Attribute) GreaterThan(checkValue any) bool { if a == nil { return false } @@ -889,7 +889,7 @@ func (a *Attribute) GreaterThan(checkValue interface{}) bool { return false } -func (a *Attribute) GreaterThanOrEqualTo(checkValue interface{}) bool { +func (a *Attribute) GreaterThanOrEqualTo(checkValue any) bool { if a == nil { return false } @@ -1053,7 +1053,7 @@ func (a *Attribute) References(r Reference) bool { return false } -func getRawValue(value cty.Value) interface{} { +func getRawValue(value cty.Value) any { if value.IsNull() || !value.IsKnown() { return value } @@ -1080,7 +1080,7 @@ func (a *Attribute) IsNotNil() bool { return !a.IsNil() } -func (a *Attribute) HasIntersect(checkValues ...interface{}) bool { +func (a *Attribute) HasIntersect(checkValues ...any) bool { if !a.Type().IsListType() && !a.Type().IsTupleType() { return false } diff --git a/pkg/iac/terraform/block.go b/pkg/iac/terraform/block.go index 6807fddd0f7d..8b49225b6e69 100644 --- a/pkg/iac/terraform/block.go +++ b/pkg/iac/terraform/block.go @@ -132,7 +132,7 @@ func (b *Block) GetMetadata() iacTypes.Metadata { return b.metadata } -func (b *Block) GetRawValue() interface{} { +func (b *Block) GetRawValue() any { return nil } diff --git a/pkg/iac/terraform/reference.go b/pkg/iac/terraform/reference.go index 978773da5010..a84fb6175180 100644 --- a/pkg/iac/terraform/reference.go +++ b/pkg/iac/terraform/reference.go @@ -160,7 +160,7 @@ func (r Reference) Key() string { return fmt.Sprintf("%v", key(r)) } -func key(r Reference) interface{} { +func key(r Reference) any { if r.key.IsNull() || !r.key.IsKnown() { return "" } diff --git a/pkg/iac/terraform/resource_block.go b/pkg/iac/terraform/resource_block.go index 3339675ee304..339843423112 100644 --- a/pkg/iac/terraform/resource_block.go +++ b/pkg/iac/terraform/resource_block.go @@ -9,15 +9,15 @@ import ( ) type PlanReference struct { - Value interface{} + Value any } type PlanBlock struct { Type string Name string BlockType string - Blocks map[string]map[string]interface{} - Attributes map[string]interface{} + Blocks map[string]map[string]any + Attributes map[string]any } func NewPlanBlock(blockType, resourceType, resourceName string) *PlanBlock { @@ -29,8 +29,8 @@ func NewPlanBlock(blockType, resourceType, resourceName string) *PlanBlock { Type: resourceType, Name: resourceName, BlockType: blockType, - Blocks: make(map[string]map[string]interface{}), - Attributes: make(map[string]interface{}), + Blocks: make(map[string]map[string]any), + Attributes: make(map[string]any), } } @@ -54,7 +54,7 @@ func (rb *PlanBlock) ToHCL() string { } var res bytes.Buffer - if err := resourceTmpl.Execute(&res, map[string]interface{}{ + if err := resourceTmpl.Execute(&res, map[string]any{ "BlockType": rb.BlockType, "Type": rb.Type, "Name": rb.Name, @@ -73,11 +73,11 @@ var resourceTemplate = `{{ .BlockType }} "{{ .Type }}" "{{ .Name }}" { {{end}}{{ end }}} {{end}}}` -func renderTemplateValue(val interface{}) string { +func renderTemplateValue(val any) string { switch t := val.(type) { - case map[string]interface{}: + case map[string]any: return fmt.Sprintf("= %s", renderMap(t)) - case []interface{}: + case []any: if isMapSlice(t) { return renderSlice(t) } @@ -87,15 +87,15 @@ func renderTemplateValue(val interface{}) string { } } -func renderPrimitive(val interface{}) string { +func renderPrimitive(val any) string { switch t := val.(type) { case PlanReference: return fmt.Sprintf("%v", t.Value) case string: return parseStringPrimitive(t) - case map[string]interface{}: + case map[string]any: return renderMap(t) - case []interface{}: + case []any: return renderSlice(t) default: return fmt.Sprintf("%#v", t) @@ -121,20 +121,20 @@ func parseStringPrimitive(input string) string { return fmt.Sprintf("%q", ff) } -func isMapSlice(vars []interface{}) bool { +func isMapSlice(vars []any) bool { if len(vars) == 0 { return false } val := vars[0] switch val.(type) { - case map[string]interface{}: + case map[string]any: return true default: return false } } -func renderSlice(vals []interface{}) string { +func renderSlice(vals []any) string { if len(vals) == 0 { return "[]" } @@ -143,7 +143,7 @@ func renderSlice(vals []interface{}) string { switch t := val.(type) { // if vals[0] is a map[string]interface this is a block, so render it as a map - case map[string]interface{}: + case map[string]any: return renderMap(t) // otherwise its going to be just a list of primitives default: @@ -156,7 +156,7 @@ func renderSlice(vals []interface{}) string { } } -func renderMap(val map[string]interface{}) string { +func renderMap(val map[string]any) string { if len(val) == 0 { return "{}" } diff --git a/pkg/iac/terraform/value_functions.go b/pkg/iac/terraform/value_functions.go index af987039af54..1071a608e39c 100644 --- a/pkg/iac/terraform/value_functions.go +++ b/pkg/iac/terraform/value_functions.go @@ -13,19 +13,19 @@ const ( valueNameKey = "value" ) -var functions = map[string]func(interface{}, interface{}) bool{ +var functions = map[string]func(any, any) bool{ "isAny": isAny, "isNone": isNone, "regexMatches": regexMatches, } -func evaluate(criteriaValue, testValue interface{}) bool { +func evaluate(criteriaValue, testValue any) bool { switch t := criteriaValue.(type) { - case map[interface{}]interface{}: + case map[any]any: if t[functionNameKey] != nil { return executeFunction(t[functionNameKey].(string), t[valueNameKey], testValue) } - case map[string]interface{}: + case map[string]any: if t[functionNameKey] != nil { return executeFunction(t[functionNameKey].(string), t[valueNameKey], testValue) } @@ -35,16 +35,16 @@ func evaluate(criteriaValue, testValue interface{}) bool { return false } -func executeFunction(functionName string, criteriaValues, testValue interface{}) bool { +func executeFunction(functionName string, criteriaValues, testValue any) bool { if functions[functionName] != nil { return functions[functionName](criteriaValues, testValue) } return false } -func isAny(criteriaValues, testValue interface{}) bool { +func isAny(criteriaValues, testValue any) bool { switch t := criteriaValues.(type) { - case []interface{}: + case []any: for _, v := range t { if v == testValue { return true @@ -60,11 +60,11 @@ func isAny(criteriaValues, testValue interface{}) bool { return false } -func isNone(criteriaValues, testValue interface{}) bool { +func isNone(criteriaValues, testValue any) bool { return !isAny(criteriaValues, testValue) } -func regexMatches(criteriaValue, testValue interface{}) bool { +func regexMatches(criteriaValue, testValue any) bool { var patternVal string switch t := criteriaValue.(type) { case string: diff --git a/pkg/iac/types/bool.go b/pkg/iac/types/bool.go index c897206b8b19..ae12d39198a2 100755 --- a/pkg/iac/types/bool.go +++ b/pkg/iac/types/bool.go @@ -10,14 +10,14 @@ type BoolValue struct { } func (b BoolValue) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]interface{}{ + return json.Marshal(map[string]any{ "value": b.value, "metadata": b.metadata, }) } func (b *BoolValue) UnmarshalJSON(data []byte) error { - var keys map[string]interface{} + var keys map[string]any if err := json.Unmarshal(data, &keys); err != nil { return err } @@ -71,7 +71,7 @@ func (b BoolValue) Value() bool { return b.value } -func (b BoolValue) GetRawValue() interface{} { +func (b BoolValue) GetRawValue() any { return b.value } @@ -89,8 +89,8 @@ func (b BoolValue) IsFalse() bool { return !b.Value() } -func (s BoolValue) ToRego() interface{} { - m := s.metadata.ToRego().(map[string]interface{}) +func (s BoolValue) ToRego() any { + m := s.metadata.ToRego().(map[string]any) m["value"] = s.Value() return m } diff --git a/pkg/iac/types/bytes.go b/pkg/iac/types/bytes.go index 03405b48c5b2..01358ad3c8d9 100755 --- a/pkg/iac/types/bytes.go +++ b/pkg/iac/types/bytes.go @@ -10,14 +10,14 @@ type BytesValue struct { } func (b BytesValue) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]interface{}{ + return json.Marshal(map[string]any{ "value": b.value, "metadata": b.metadata, }) } func (b *BytesValue) UnmarshalJSON(data []byte) error { - var keys map[string]interface{} + var keys map[string]any if err := json.Unmarshal(data, &keys); err != nil { return err } @@ -50,7 +50,7 @@ func (b BytesValue) Value() []byte { return b.value } -func (b BytesValue) GetRawValue() interface{} { +func (b BytesValue) GetRawValue() any { return b.value } @@ -87,8 +87,8 @@ func BytesUnresolvable(m Metadata) BytesValue { return b } -func (s BytesValue) ToRego() interface{} { - m := s.metadata.ToRego().(map[string]interface{}) +func (s BytesValue) ToRego() any { + m := s.metadata.ToRego().(map[string]any) m["value"] = string(s.Value()) return m } diff --git a/pkg/iac/types/int.go b/pkg/iac/types/int.go index 34ffc05c49c3..24c06b72753e 100755 --- a/pkg/iac/types/int.go +++ b/pkg/iac/types/int.go @@ -10,14 +10,14 @@ type IntValue struct { } func (b IntValue) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]interface{}{ + return json.Marshal(map[string]any{ "value": b.value, "metadata": b.metadata, }) } func (b *IntValue) UnmarshalJSON(data []byte) error { - var keys map[string]interface{} + var keys map[string]any if err := json.Unmarshal(data, &keys); err != nil { return err } @@ -79,7 +79,7 @@ func (b IntValue) Value() int { return b.value } -func (b IntValue) GetRawValue() interface{} { +func (b IntValue) GetRawValue() any { return b.value } @@ -111,8 +111,8 @@ func (b IntValue) GreaterThan(i int) bool { return b.value > i } -func (s IntValue) ToRego() interface{} { - m := s.metadata.ToRego().(map[string]interface{}) +func (s IntValue) ToRego() any { + m := s.metadata.ToRego().(map[string]any) m["value"] = s.Value() return m } diff --git a/pkg/iac/types/map.go b/pkg/iac/types/map.go index 95a0e6851700..01d90543b4e8 100755 --- a/pkg/iac/types/map.go +++ b/pkg/iac/types/map.go @@ -10,14 +10,14 @@ type MapValue struct { } func (b MapValue) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]interface{}{ + return json.Marshal(map[string]any{ "value": b.value, "metadata": b.metadata, }) } func (b *MapValue) UnmarshalJSON(data []byte) error { - var keys map[string]interface{} + var keys map[string]any if err := json.Unmarshal(data, &keys); err != nil { return err } @@ -69,7 +69,7 @@ func (b MapValue) Value() map[string]string { return b.value } -func (b MapValue) GetRawValue() interface{} { +func (b MapValue) GetRawValue() any { return b.value } @@ -85,8 +85,8 @@ func (b MapValue) HasKey(key string) bool { return ok } -func (s MapValue) ToRego() interface{} { - m := s.metadata.ToRego().(map[string]interface{}) +func (s MapValue) ToRego() any { + m := s.metadata.ToRego().(map[string]any) m["value"] = s.Value() return m } diff --git a/pkg/iac/types/metadata.go b/pkg/iac/types/metadata.go index 6b130eb3d17e..359654bdc43b 100755 --- a/pkg/iac/types/metadata.go +++ b/pkg/iac/types/metadata.go @@ -14,11 +14,11 @@ type Metadata struct { isExplicit bool isUnresolvable bool parent *Metadata - internal interface{} + internal any } func (m Metadata) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]interface{}{ + return json.Marshal(map[string]any{ "range": m.rnge, "ref": m.ref, "managed": m.isManaged, @@ -30,7 +30,7 @@ func (m Metadata) MarshalJSON() ([]byte, error) { } func (m *Metadata) UnmarshalJSON(data []byte) error { - var keys map[string]interface{} + var keys map[string]any if err := json.Unmarshal(data, &keys); err != nil { return err } @@ -61,7 +61,7 @@ func (m *Metadata) UnmarshalJSON(data []byte) error { m.isUnresolvable = keys["unresolvable"].(bool) } if keys["parent"] != nil { - if _, ok := keys["parent"].(map[string]interface{}); ok { + if _, ok := keys["parent"].(map[string]any); ok { raw, err := json.Marshal(keys["parent"]) if err != nil { return err @@ -76,8 +76,8 @@ func (m *Metadata) UnmarshalJSON(data []byte) error { return nil } -func (m *Metadata) ToRego() interface{} { - input := map[string]interface{}{ +func (m *Metadata) ToRego() any { + input := map[string]any{ "filepath": m.Range().GetLocalFilename(), "startline": m.Range().GetStartLine(), "endline": m.Range().GetEndLine(), @@ -134,12 +134,12 @@ func (m Metadata) Root() Metadata { return *meta } -func (m Metadata) WithInternal(internal interface{}) Metadata { +func (m Metadata) WithInternal(internal any) Metadata { m.internal = internal return m } -func (m Metadata) Internal() interface{} { +func (m Metadata) Internal() any { return m.internal } @@ -209,7 +209,7 @@ func (m Metadata) GetMetadata() Metadata { return m } -func (m Metadata) GetRawValue() interface{} { +func (m Metadata) GetRawValue() any { return nil } diff --git a/pkg/iac/types/metadata_test.go b/pkg/iac/types/metadata_test.go index 1b263f3289de..f447c6d85ca2 100644 --- a/pkg/iac/types/metadata_test.go +++ b/pkg/iac/types/metadata_test.go @@ -8,7 +8,7 @@ import ( func Test_MetadataToRego(t *testing.T) { m1 := NewTestMetadata() - expected := map[string]interface{}{ + expected := map[string]any{ "endline": 123, "explicit": false, "filepath": "test.test", @@ -21,7 +21,7 @@ func Test_MetadataToRego(t *testing.T) { assert.Equal(t, expected, m1.ToRego()) m2 := NewTestMetadata() m1.SetParentPtr(&m2) - expected["parent"] = map[string]interface{}{ + expected["parent"] = map[string]any{ "endline": 123, "explicit": false, "filepath": "test.test", diff --git a/pkg/iac/types/range.go b/pkg/iac/types/range.go index 938ba929b682..c06a2bf6fe1a 100755 --- a/pkg/iac/types/range.go +++ b/pkg/iac/types/range.go @@ -56,7 +56,7 @@ type Range struct { } func (r Range) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]interface{}{ + return json.Marshal(map[string]any{ "filename": r.filename, "startLine": r.startLine, "endLine": r.endLine, @@ -67,7 +67,7 @@ func (r Range) MarshalJSON() ([]byte, error) { } func (r *Range) UnmarshalJSON(data []byte) error { - var keys map[string]interface{} + var keys map[string]any if err := json.Unmarshal(data, &keys); err != nil { return err } diff --git a/pkg/iac/types/string.go b/pkg/iac/types/string.go index 1db865740cd3..0630d18b9c8e 100755 --- a/pkg/iac/types/string.go +++ b/pkg/iac/types/string.go @@ -59,14 +59,14 @@ func (l StringValueList) AsStrings() (output []string) { type stringCheckFunc func(string, string) bool func (b StringValue) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]interface{}{ + return json.Marshal(map[string]any{ "value": b.value, "metadata": b.metadata, }) } func (b *StringValue) UnmarshalJSON(data []byte) error { - var keys map[string]interface{} + var keys map[string]any if err := json.Unmarshal(data, &keys); err != nil { return err } @@ -87,8 +87,8 @@ func (b *StringValue) UnmarshalJSON(data []byte) error { return nil } -func (s StringValue) ToRego() interface{} { - m := s.metadata.ToRego().(map[string]interface{}) +func (s StringValue) ToRego() any { + m := s.metadata.ToRego().(map[string]any) m["value"] = s.Value() return m } @@ -113,7 +113,7 @@ func (s StringValue) Value() string { return s.value } -func (b StringValue) GetRawValue() interface{} { +func (b StringValue) GetRawValue() any { return b.value } diff --git a/pkg/iac/types/time.go b/pkg/iac/types/time.go index 4f3c8c455f2f..e04d090964a8 100755 --- a/pkg/iac/types/time.go +++ b/pkg/iac/types/time.go @@ -11,14 +11,14 @@ type TimeValue struct { } func (b TimeValue) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]interface{}{ + return json.Marshal(map[string]any{ "value": b.value.Format(time.RFC3339), "metadata": b.metadata, }) } func (b *TimeValue) UnmarshalJSON(data []byte) error { - var keys map[string]interface{} + var keys map[string]any if err := json.Unmarshal(data, &keys); err != nil { return err } @@ -70,7 +70,7 @@ func (t TimeValue) Value() time.Time { return t.value } -func (t TimeValue) GetRawValue() interface{} { +func (t TimeValue) GetRawValue() any { return t.value } @@ -95,8 +95,8 @@ func (t TimeValue) After(i time.Time) bool { return t.value.After(i) } -func (t TimeValue) ToRego() interface{} { - m := t.metadata.ToRego().(map[string]interface{}) +func (t TimeValue) ToRego() any { + m := t.metadata.ToRego().(map[string]any) m["value"] = t.Value().Format(time.RFC3339) return m } diff --git a/pkg/k8s/report/summary.go b/pkg/k8s/report/summary.go index de81e1a7b532..a637a55cbd0d 100644 --- a/pkg/k8s/report/summary.go +++ b/pkg/k8s/report/summary.go @@ -40,7 +40,7 @@ func ColumnHeading(scanners types.Scanners, availableColumns []string) []string NamespaceColumn, ResourceColumn, } - securityOptions := make(map[string]interface{}, 0) + securityOptions := make(map[string]any, 0) // maintain column order (vuln,config,secret) for _, check := range scanners { switch check { diff --git a/pkg/k8s/scanner/scanner_test.go b/pkg/k8s/scanner/scanner_test.go index b7eb8156a59c..c17b0e622ca2 100644 --- a/pkg/k8s/scanner/scanner_test.go +++ b/pkg/k8s/scanner/scanner_test.go @@ -35,7 +35,7 @@ func TestScanner_Scan(t *testing.T) { Namespace: "kube-system", Kind: "Cluster", Name: "k8s.io/kubernetes", - RawResource: map[string]interface{}{ + RawResource: map[string]any{ "name": "k8s.io/kubernetes", "version": "1.21.1", "type": "ClusterInfo", @@ -49,9 +49,9 @@ func TestScanner_Scan(t *testing.T) { Namespace: "kube-system", Kind: "ControlPlaneComponents", Name: "k8s.io/apiserver", - RawResource: map[string]interface{}{ - "Containers": []interface{}{ - map[string]interface{}{ + RawResource: map[string]any{ + "Containers": []any{ + map[string]any{ "Digest": "18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", "ID": "kube-apiserver:v1.21.1", "Registry": "k8s.gcr.io", @@ -66,7 +66,7 @@ func TestScanner_Scan(t *testing.T) { { Kind: "NodeComponents", Name: "kind-control-plane", - RawResource: map[string]interface{}{ + RawResource: map[string]any{ "ContainerRuntimeVersion": "containerd://1.5.2", "Hostname": "kind-control-plane", "KubeProxyVersion": "6.2.13-300.fc38.aarch64", @@ -507,18 +507,18 @@ func TestFindNodeName(t *testing.T) { Namespace: "kube-system", Kind: "Cluster", Name: "k8s.io/kubernetes", - RawResource: make(map[string]interface{}), + RawResource: make(map[string]any), }, { Namespace: "kube-system", Kind: "ControlPlaneComponents", Name: "k8s.io/apiserver", - RawResource: make(map[string]interface{}), + RawResource: make(map[string]any), }, { Kind: "NodeComponents", Name: "kind-control-plane", - RawResource: make(map[string]interface{}), + RawResource: make(map[string]any), }, }, want: "kind-control-plane", @@ -530,13 +530,13 @@ func TestFindNodeName(t *testing.T) { Namespace: "kube-system", Kind: "Cluster", Name: "k8s.io/kubernetes", - RawResource: make(map[string]interface{}), + RawResource: make(map[string]any), }, { Namespace: "kube-system", Kind: "ControlPlaneComponents", Name: "k8s.io/apiserver", - RawResource: make(map[string]interface{}), + RawResource: make(map[string]any), }, }, want: "", diff --git a/pkg/module/memfs.go b/pkg/module/memfs.go index d407fd21e88c..a62ae01ca5cb 100644 --- a/pkg/module/memfs.go +++ b/pkg/module/memfs.go @@ -67,5 +67,5 @@ func (fakeRootDirInfo) Size() int64 { return 0 } func (fakeRootDirInfo) Mode() fs.FileMode { return fs.ModeDir | 0o500 } func (fakeRootDirInfo) ModTime() time.Time { return time.Unix(0, 0) } func (fakeRootDirInfo) IsDir() bool { return true } -func (fakeRootDirInfo) Sys() interface{} { return nil } +func (fakeRootDirInfo) Sys() any { return nil } func (emptyDir) ReadDir(int) (dirents []fs.DirEntry, err error) { return } diff --git a/pkg/module/serialize/types.go b/pkg/module/serialize/types.go index beddb8175f44..b34a222f67b7 100644 --- a/pkg/module/serialize/types.go +++ b/pkg/module/serialize/types.go @@ -22,7 +22,7 @@ type AnalysisResult struct { type CustomResource struct { Type string FilePath string - Data interface{} + Data any } type PostScanAction string @@ -66,7 +66,7 @@ type DetectedVulnerability struct { DataSource *types.DataSource `json:",omitempty"` // Custom is for extensibility and not supposed to be used in OSS - Custom interface{} `json:",omitempty"` + Custom any `json:",omitempty"` // Embed vulnerability details types.Vulnerability diff --git a/pkg/policy/policy_test.go b/pkg/policy/policy_test.go index e38517a8d3e2..8e1b175da483 100644 --- a/pkg/policy/policy_test.go +++ b/pkg/policy/policy_test.go @@ -143,7 +143,7 @@ func TestClient_NeedsUpdate(t *testing.T) { name string clock clock.Clock digestReturns digestReturns - metadata interface{} + metadata any want bool wantErr bool }{ diff --git a/pkg/report/github/github.go b/pkg/report/github/github.go index 6441d96630e4..87c74639bcba 100644 --- a/pkg/report/github/github.go +++ b/pkg/report/github/github.go @@ -37,7 +37,7 @@ type File struct { SrcLocation string `json:"source_location,omitempty"` } -type Metadata map[string]interface{} +type Metadata map[string]any type Manifest struct { Name string `json:"name,omitempty"` diff --git a/pkg/report/predicate/vuln.go b/pkg/report/predicate/vuln.go index 9c975a4be76a..56a42abdc5e5 100644 --- a/pkg/report/predicate/vuln.go +++ b/pkg/report/predicate/vuln.go @@ -25,10 +25,10 @@ type CosignVulnPredicate struct { } type Invocation struct { - Parameters interface{} `json:"parameters"` - URI string `json:"uri"` - EventID string `json:"event_id"` - BuilderID string `json:"builder.id"` + Parameters any `json:"parameters"` + URI string `json:"uri"` + EventID string `json:"event_id"` + BuilderID string `json:"builder.id"` } type DB struct { diff --git a/pkg/report/sarif_test.go b/pkg/report/sarif_test.go index 0b4f37e26efe..14b5b6027a3b 100644 --- a/pkg/report/sarif_test.go +++ b/pkg/report/sarif_test.go @@ -111,8 +111,8 @@ func TestReportWriter_Sarif(t *testing.T) { Level: "error", }, HelpURI: lo.ToPtr("https://avd.aquasec.com/nvd/cve-2020-0001"), - Properties: map[string]interface{}{ - "tags": []interface{}{ + Properties: map[string]any{ + "tags": []any{ "vulnerability", "security", "HIGH", @@ -175,10 +175,10 @@ func TestReportWriter_Sarif(t *testing.T) { }, }, PropertyBag: sarif.PropertyBag{ - Properties: map[string]interface{}{ + Properties: map[string]any{ "imageName": "debian:9", - "repoDigests": []interface{}{"debian@sha256:a8cc1744bbdd5266678e3e8b3e6387e45c053218438897e86876f2eb104e5534"}, - "repoTags": []interface{}{"debian:9"}, + "repoDigests": []any{"debian@sha256:a8cc1744bbdd5266678e3e8b3e6387e45c053218438897e86876f2eb104e5534"}, + "repoTags": []any{"debian:9"}, }, }, }, @@ -236,8 +236,8 @@ func TestReportWriter_Sarif(t *testing.T) { Level: "error", }, HelpURI: lo.ToPtr("https://avd.aquasec.com/appshield/ksv001"), - Properties: map[string]interface{}{ - "tags": []interface{}{ + Properties: map[string]any{ + "tags": []any{ "misconfiguration", "security", "HIGH", @@ -259,8 +259,8 @@ func TestReportWriter_Sarif(t *testing.T) { Level: "error", }, HelpURI: lo.ToPtr("https://avd.aquasec.com/appshield/ksv002"), - Properties: map[string]interface{}{ - "tags": []interface{}{ + Properties: map[string]any{ + "tags": []any{ "misconfiguration", "security", "CRITICAL", @@ -376,8 +376,8 @@ func TestReportWriter_Sarif(t *testing.T) { Level: "error", }, HelpURI: lo.ToPtr("https://github.com/aquasecurity/trivy/blob/main/pkg/fanal/secret/builtin-rules.go"), - Properties: map[string]interface{}{ - "tags": []interface{}{ + Properties: map[string]any{ + "tags": []any{ "secret", "security", "CRITICAL", @@ -469,8 +469,8 @@ func TestReportWriter_Sarif(t *testing.T) { DefaultConfiguration: sarif.NewReportingConfiguration().WithLevel("error"), Help: sarif.NewMultiformatMessageString("License GPL-3.0\nClassification: restricted\nPkgName: alpine-base\nPath: "). WithMarkdown("**License GPL-3.0**\n| PkgName | Classification | Path |\n| --- | --- | --- |\n|alpine-base|restricted||"), - Properties: map[string]interface{}{ - "tags": []interface{}{ + Properties: map[string]any{ + "tags": []any{ "license", "security", "HIGH", @@ -614,7 +614,7 @@ func TestReportWriter_Sarif(t *testing.T) { Markdown: lo.ToPtr("**Misconfiguration AVD-GCP-0007**\n| Type | Severity | Check | Message | Link |\n| --- | --- | --- | --- | --- |\n|Terraform Security Check|HIGH|Service accounts should not have roles assigned with excessive privileges|Service account is granted a privileged role.|[AVD-GCP-0007](https://avd.aquasec.com/misconfig/avd-gcp-0007)|\n\nService accounts should have a minimal set of permissions assigned in order to do their job. They should never have excessive access as if compromised, an attacker can escalate privileges and take over the entire account."), }, Properties: sarif.Properties{ - "tags": []interface{}{ + "tags": []any{ "misconfiguration", "security", "HIGH", diff --git a/pkg/report/table/licensing.go b/pkg/report/table/licensing.go index f758b9876df5..602dc9033e00 100644 --- a/pkg/report/table/licensing.go +++ b/pkg/report/table/licensing.go @@ -82,7 +82,7 @@ func (r pkgLicenseRenderer) countSeverities() map[string]int { return severityCount } -func (r *pkgLicenseRenderer) printf(format string, args ...interface{}) { +func (r *pkgLicenseRenderer) printf(format string, args ...any) { // nolint _ = tml.Fprintf(r.w, format, args...) } @@ -167,7 +167,7 @@ func (r fileLicenseRenderer) countSeverities() map[string]int { return severityCount } -func (r *fileLicenseRenderer) printf(format string, args ...interface{}) { +func (r *fileLicenseRenderer) printf(format string, args ...any) { // nolint _ = tml.Fprintf(r.w, format, args...) } diff --git a/pkg/report/table/misconfig.go b/pkg/report/table/misconfig.go index 3f9d345c93ea..5f706fbd5787 100644 --- a/pkg/report/table/misconfig.go +++ b/pkg/report/table/misconfig.go @@ -81,7 +81,7 @@ func (r *misconfigRenderer) countSeverities() map[string]int { return severityCount } -func (r *misconfigRenderer) printf(format string, args ...interface{}) { +func (r *misconfigRenderer) printf(format string, args ...any) { // nolint _ = tml.Fprintf(r.w, format, args...) } diff --git a/pkg/report/table/secret.go b/pkg/report/table/secret.go index 1a647a4f55fb..4b4fe4b0d1bc 100644 --- a/pkg/report/table/secret.go +++ b/pkg/report/table/secret.go @@ -63,7 +63,7 @@ func (r *secretRenderer) countSeverities() map[string]int { return severityCount } -func (r *secretRenderer) printf(format string, args ...interface{}) { +func (r *secretRenderer) printf(format string, args ...any) { // nolint _ = tml.Fprintf(r.w, format, args...) } diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index f636b71f9560..51ee946e3814 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -18,7 +18,7 @@ import ( ) var ( - SeverityColor = []func(a ...interface{}) string{ + SeverityColor = []func(a ...any) string{ color.New(color.FgCyan).SprintFunc(), // UNKNOWN color.New(color.FgBlue).SprintFunc(), // LOW color.New(color.FgYellow).SprintFunc(), // MEDIUM diff --git a/pkg/report/table/vulnerability.go b/pkg/report/table/vulnerability.go index 6284993be5af..562f667c8498 100644 --- a/pkg/report/table/vulnerability.go +++ b/pkg/report/table/vulnerability.go @@ -258,7 +258,7 @@ Dependency Origin Tree (Reversed) r.printf(root.String()) } -func (r *vulnerabilityRenderer) printf(format string, args ...interface{}) { +func (r *vulnerabilityRenderer) printf(format string, args ...any) { // nolint _ = tml.Fprintf(r.w, format, args...) } diff --git a/pkg/report/template.go b/pkg/report/template.go index 1ebabdb89cfe..36891e0381c7 100644 --- a/pkg/report/template.go +++ b/pkg/report/template.go @@ -19,7 +19,7 @@ import ( ) // CustomTemplateFuncMap is used to overwrite existing functions for testing. -var CustomTemplateFuncMap = make(map[string]interface{}) +var CustomTemplateFuncMap = make(map[string]any) // TemplateWriter write result in custom format defined by user's template type TemplateWriter struct { diff --git a/pkg/result/filter.go b/pkg/result/filter.go index 17b2b6feb422..936ad8272b12 100644 --- a/pkg/result/filter.go +++ b/pkg/result/filter.go @@ -330,7 +330,7 @@ func applyPolicy(ctx context.Context, result *types.Result, policyFile string) e return nil } -func evaluate(ctx context.Context, query rego.PreparedEvalQuery, input interface{}) (bool, error) { +func evaluate(ctx context.Context, query rego.PreparedEvalQuery, input any) (bool, error) { results, err := query.Eval(ctx, rego.EvalInput(input)) if err != nil { return false, xerrors.Errorf("unable to evaluate the policy: %w", err) diff --git a/pkg/rpc/client/client_test.go b/pkg/rpc/client/client_test.go index e3c0b67ff433..bf5036c6116d 100644 --- a/pkg/rpc/client/client_test.go +++ b/pkg/rpc/client/client_test.go @@ -176,7 +176,7 @@ func TestScanner_Scan(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if tt.expectation == nil { - e := map[string]interface{}{ + e := map[string]any{ "code": "not_found", "msg": "expectation is empty", } diff --git a/pkg/sbom/sbom.go b/pkg/sbom/sbom.go index 5b1055ed7174..26ae9f9158d1 100644 --- a/pkg/sbom/sbom.go +++ b/pkg/sbom/sbom.go @@ -168,7 +168,7 @@ func decodeAttestCycloneDXJSONFormat(r io.ReadSeeker) (Format, bool) { return "", false } - m, ok := s.Predicate.(map[string]interface{}) + m, ok := s.Predicate.(map[string]any) if !ok { return "", false } @@ -182,7 +182,7 @@ func decodeAttestCycloneDXJSONFormat(r io.ReadSeeker) (Format, bool) { func Decode(f io.Reader, format Format) (types.SBOM, error) { var ( - v interface{} + v any bom = core.NewBOM(core.Options{}) decoder interface{ Decode(any) error } ) diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index 2b29c7483024..5923a5b34800 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -81,7 +81,7 @@ type Marshaler struct { appVersion string // Trivy version. It needed for `creator` field } -type Hash func(v interface{}, format hashstructure.Format, opts *hashstructure.HashOptions) (uint64, error) +type Hash func(v any, format hashstructure.Format, opts *hashstructure.HashOptions) (uint64, error) type marshalOption func(*Marshaler) @@ -487,7 +487,7 @@ func getDocumentNamespace(root *core.Component) string { ) } -func calcPkgID(h Hash, v interface{}) (string, error) { +func calcPkgID(h Hash, v any) (string, error) { f, err := h(v, hashstructure.FormatV2, &hashstructure.HashOptions{ ZeroNil: true, SlicesAsSets: true, diff --git a/pkg/sbom/spdx/unmarshal.go b/pkg/sbom/spdx/unmarshal.go index 719cdad15c3d..d2ba05b44999 100644 --- a/pkg/sbom/spdx/unmarshal.go +++ b/pkg/sbom/spdx/unmarshal.go @@ -32,7 +32,7 @@ type TVDecoder struct { r io.Reader } -func (tv *TVDecoder) Decode(v interface{}) error { +func (tv *TVDecoder) Decode(v any) error { spdxDocument, err := tagvalue.Read(tv.r) if err != nil { return xerrors.Errorf("failed to load tag-value spdx: %w", err) diff --git a/pkg/types/vulnerability.go b/pkg/types/vulnerability.go index 693b212d077d..68914b8835c3 100644 --- a/pkg/types/vulnerability.go +++ b/pkg/types/vulnerability.go @@ -24,7 +24,7 @@ type DetectedVulnerability struct { DataSource *types.DataSource `json:",omitempty"` // Custom is for extensibility and not supposed to be used in OSS - Custom interface{} `json:",omitempty"` + Custom any `json:",omitempty"` // Embed vulnerability details types.Vulnerability diff --git a/pkg/x/sync/sync.go b/pkg/x/sync/sync.go index 9841779efacf..ad2b7e9844be 100644 --- a/pkg/x/sync/sync.go +++ b/pkg/x/sync/sync.go @@ -38,7 +38,7 @@ func (m *Map[K, V]) Store(key K, value V) { m.m.Store(key, value) } // Len returns the length of the map func (m *Map[K, V]) Len() int { var i int - m.m.Range(func(k, v interface{}) bool { + m.m.Range(func(k, v any) bool { i++ return true }) From 3a4e845e4b714d29875c0e6e7e6d092e0e0d26ac Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 27 May 2024 04:54:55 +0200 Subject: [PATCH 103/352] ci(deps): simplify gosec rules exclusion (#6778) Signed-off-by: Matthieu MOREL --- .golangci.yaml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 4be442219788..235a833a3c45 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -28,6 +28,7 @@ linters-settings: - G101 - G114 - G204 + - G304 - G402 gci: sections: @@ -127,12 +128,6 @@ issues: linters: - gocritic text: "importShadow:" - - linters: - - gosec - text: "G304: Potential file inclusion" - - linters: - - gosec - text: "Deferring unsafe method" - linters: - errcheck text: "Close` is not checked" From 21114c98bed63fcb2b3085d886ba94b2ef8ed72a Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Mon, 27 May 2024 04:56:54 +0200 Subject: [PATCH 104/352] ci(deps): fix govet in ".*_test.go$" (#6736) Signed-off-by: Matthieu MOREL --- .golangci.yaml | 5 ++++- pkg/dependency/parser/java/jar/parse_test.go | 2 +- pkg/fanal/analyzer/imgconf/apk/apk_test.go | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 235a833a3c45..f4957e6658ae 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -112,10 +112,13 @@ issues: - bodyclose - goconst - gosec - - govet - ineffassign - misspell - unused + - path: ".*_test.go$" + linters: + - govet + text: "copylocks:" - path: ".*_test.go$" linters: - gocritic diff --git a/pkg/dependency/parser/java/jar/parse_test.go b/pkg/dependency/parser/java/jar/parse_test.go index ccf1adc9f73d..20f9c682be3b 100644 --- a/pkg/dependency/parser/java/jar/parse_test.go +++ b/pkg/dependency/parser/java/jar/parse_test.go @@ -203,7 +203,7 @@ type doc struct { ArtifactID string `json:"a"` Version string `json:"v"` P string `json:"p"` - VersionCount int `json:versionCount` + VersionCount int `json:"versionCount"` } func TestParse(t *testing.T) { diff --git a/pkg/fanal/analyzer/imgconf/apk/apk_test.go b/pkg/fanal/analyzer/imgconf/apk/apk_test.go index 9d6449c92808..8577d5c3c054 100644 --- a/pkg/fanal/analyzer/imgconf/apk/apk_test.go +++ b/pkg/fanal/analyzer/imgconf/apk/apk_test.go @@ -202,7 +202,7 @@ var ( }, { Created: v1.Time{ - time.Date(2018, time.October, 15, 21, 28, 51, 35012363, time.UTC), + Time: time.Date(2018, time.October, 15, 21, 28, 51, 35012363, time.UTC), }, CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_VERSION=1.7.2", EmptyLayer: true, @@ -234,7 +234,7 @@ var ( }, { Created: v1.Time{ - time.Date(2018, time.October, 15, 21, 28, 53, 798628678, time.UTC), + Time: time.Date(2018, time.October, 15, 21, 28, 53, 798628678, time.UTC), }, CreatedBy: "/bin/sh -c #(nop) CMD [\"composer\"]", EmptyLayer: true, From 349caf96bc3dd81551d488044f1adfdb947f39fb Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 28 May 2024 04:40:45 +0700 Subject: [PATCH 105/352] feat(misconf): support for VPC resources for inbound/outbound rules (#6779) --- pkg/iac/adapters/terraform/aws/ec2/vpc.go | 28 ++++++++++++ .../adapters/terraform/aws/ec2/vpc_test.go | 44 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/pkg/iac/adapters/terraform/aws/ec2/vpc.go b/pkg/iac/adapters/terraform/aws/ec2/vpc.go index 440de4619e74..92ee730e0947 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/vpc.go +++ b/pkg/iac/adapters/terraform/aws/ec2/vpc.go @@ -134,6 +134,16 @@ func (a *sgAdapter) adaptSecurityGroup(resource *terraform.Block, module terrafo } } + for _, r := range module.GetReferencingResources(resource, "aws_vpc_security_group_ingress_rule", "security_group_id") { + a.sgRuleIDs.Resolve(r.ID()) + ingressRules = append(ingressRules, adaptSingleSGRule(r)) + } + + for _, r := range module.GetReferencingResources(resource, "aws_vpc_security_group_egress_rule", "security_group_id") { + a.sgRuleIDs.Resolve(r.ID()) + egressRules = append(egressRules, adaptSingleSGRule(r)) + } + return ec2.SecurityGroup{ Metadata: resource.GetMetadata(), Description: descriptionVal, @@ -178,6 +188,24 @@ func adaptSGRule(resource *terraform.Block, modules terraform.Modules) ec2.Secur } } +func adaptSingleSGRule(resource *terraform.Block) ec2.SecurityGroupRule { + description := resource.GetAttribute("description").AsStringValueOrDefault("", resource) + + var cidrs []iacTypes.StringValue + if ipv4 := resource.GetAttribute("cidr_ipv4"); ipv4.IsNotNil() { + cidrs = append(cidrs, ipv4.AsStringValueOrDefault("", resource)) + } + if ipv6 := resource.GetAttribute("cidr_ipv6"); ipv6.IsNotNil() { + cidrs = append(cidrs, ipv6.AsStringValueOrDefault("", resource)) + } + + return ec2.SecurityGroupRule{ + Metadata: resource.GetMetadata(), + Description: description, + CIDRs: cidrs, + } +} + func (a *naclAdapter) adaptNetworkACL(resource *terraform.Block, module *terraform.Module) ec2.NetworkACL { var networkRules []ec2.NetworkACLRule rulesBlocks := module.GetReferencingResources(resource, "aws_network_acl_rule", "network_acl_id") diff --git a/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go b/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go index 72005e5b8559..611f8ca8527d 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go +++ b/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go @@ -221,6 +221,50 @@ resource "aws_flow_log" "this" { }, }, }, + { + name: "ingress and egress rules", + terraform: ` +resource "aws_security_group" "example" { + name = "example" + description = "example" +} + +resource "aws_vpc_security_group_egress_rule" "test" { + security_group_id = aws_security_group.example.id + cidr_ipv4 = "0.0.0.0/0" + ip_protocol = "-1" # semantically equivalent to all ports +} + +resource "aws_vpc_security_group_ingress_rule" "test" { + security_group_id = aws_security_group.example.id + cidr_ipv4 = "0.0.0.0/0" + from_port = "22" + to_port = "22" + ip_protocol = "tcp" +} +`, + expected: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Description: iacTypes.StringTest("example"), + IngressRules: []ec2.SecurityGroupRule{ + { + CIDRs: []iacTypes.StringValue{ + iacTypes.StringTest("0.0.0.0/0"), + }, + }, + }, + EgressRules: []ec2.SecurityGroupRule{ + { + CIDRs: []iacTypes.StringValue{ + iacTypes.StringTest("0.0.0.0/0"), + }, + }, + }, + }, + }, + }, + }, } for _, test := range tests { From 03fc5347b5e36a78ebd06e105cb104f34bc0819e Mon Sep 17 00:00:00 2001 From: simar7 <1254783+simar7@users.noreply.github.com> Date: Mon, 27 May 2024 23:13:18 -0600 Subject: [PATCH 106/352] chore(deps): Bump trivy-aws and trivy-checks (#6796) --- go.mod | 30 +++++++++++------------ go.sum | 76 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index 1ee3895f492b..e2150c80aab5 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/aquasecurity/table v1.8.0 github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334 github.com/aquasecurity/tml v0.6.1 - github.com/aquasecurity/trivy-aws v0.8.1-0.20240511051125-4393910b056b - github.com/aquasecurity/trivy-checks v0.10.5-0.20240514040354-93bcb2f8c233 + github.com/aquasecurity/trivy-aws v0.9.0 + github.com/aquasecurity/trivy-checks v0.11.0 github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7 @@ -137,7 +137,7 @@ require ( require ( cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/compute v1.25.0 // indirect + cloud.google.com/go/compute v1.25.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/storage v1.39.1 // indirect @@ -182,12 +182,12 @@ require ( github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6 // indirect github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6 // indirect github.com/aws/aws-sdk-go-v2/service/athena v1.37.3 // indirect - github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.5 // indirect + github.com/aws/aws-sdk-go-v2/service/cloudfront v1.36.4 // indirect github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.35.6 // indirect github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.32.2 // indirect github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.5 // indirect - github.com/aws/aws-sdk-go-v2/service/docdb v1.33.1 // indirect + github.com/aws/aws-sdk-go-v2/service/docdb v1.34.4 // indirect github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 // indirect github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 // indirect github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6 // indirect @@ -205,7 +205,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 // indirect github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5 // indirect github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6 // indirect - github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.32.1 // indirect github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6 // indirect github.com/aws/aws-sdk-go-v2/service/mq v1.20.6 // indirect github.com/aws/aws-sdk-go-v2/service/neptune v1.28.1 // indirect @@ -220,7 +220,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/briandowns/spinner v1.23.0 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.7 // indirect @@ -384,11 +384,11 @@ require ( go.mongodb.org/mongo-driver v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/sdk v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect + go.opentelemetry.io/otel v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/sdk v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect @@ -399,9 +399,9 @@ require ( google.golang.org/api v0.172.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240311173647-c811ad7063a7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect - google.golang.org/grpc v1.63.2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect + google.golang.org/grpc v1.64.0 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index dfb5b4daca88..860e980a9250 100644 --- a/go.sum +++ b/go.sum @@ -175,8 +175,8 @@ cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63 cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= -cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU= -cloud.google.com/go/compute v1.25.0/go.mod h1:GR7F0ZPZH8EhChlMo9FkLd7eUTwEymjqQagxzilIxIE= +cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= @@ -754,8 +754,8 @@ github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30 github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce/go.mod h1:HXgVzOPvXhVGLJs4ZKO817idqr/xhwsTcj17CLYY74s= -github.com/aquasecurity/go-mock-aws v0.0.0-20240109054747-49e4b5da33cb h1:dNxUB2bSbiLGNYcXkbBKrrfuY96+dXhA9FahEFZ4THQ= -github.com/aquasecurity/go-mock-aws v0.0.0-20240109054747-49e4b5da33cb/go.mod h1:iytBd25FZt3N6g+vGnNPO7BfgkV7HCEfIHyg8K/ldUw= +github.com/aquasecurity/go-mock-aws v0.0.0-20240523055201-a4152219967f h1:NRq3oUfkheKgoYPjNUApUtClKaBRcc6KzdcBHqZPrAM= +github.com/aquasecurity/go-mock-aws v0.0.0-20240523055201-a4152219967f/go.mod h1:95xczqqItx1yPSrYG2SQM2gi2lqoYG9i3pLsYKSTpgI= github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 h1:eveqE9ivrt30CJ7dOajOfBavhZ4zPqHcZe/4tKp0alc= github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798/go.mod h1:hxbJZtKlO4P8sZ9nztizR6XLoE33O+BkPmuYQ4ACyz0= github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 h1:vmXNl+HDfqqXgr0uY1UgK1GAhps8nbAAtqHNBcgyf+4= @@ -771,10 +771,10 @@ github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334 h1:MgvbLyL github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334/go.mod h1:TKXn7bPfMM52ETP4sjjwkTKCZ18CqCs+I/vtFePSdBc= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= -github.com/aquasecurity/trivy-aws v0.8.1-0.20240511051125-4393910b056b h1:mBMM6+kLTPaqSxNLO51rL6HiCKL1ElV5RXM+BEAK8fg= -github.com/aquasecurity/trivy-aws v0.8.1-0.20240511051125-4393910b056b/go.mod h1:z638DsULU5CCIk8QZqcj8u2D5IIRzvjq4jI1VDQGda4= -github.com/aquasecurity/trivy-checks v0.10.5-0.20240514040354-93bcb2f8c233 h1:7TnJS1JEmrNfznu1Y9Rzbboxl7J4hxjIKQ8tV3k5UQs= -github.com/aquasecurity/trivy-checks v0.10.5-0.20240514040354-93bcb2f8c233/go.mod h1:+G8Ft1pJAmsSPzfSQHdSQ5zcWHWPOxVdQHHA+eHP3eU= +github.com/aquasecurity/trivy-aws v0.9.0 h1:0Xl5p5LtEwFMwZpuRQ6SSzVJN/fJZZtLenaacxjQFvE= +github.com/aquasecurity/trivy-aws v0.9.0/go.mod h1:KOrgoMtAxHmGa1oIixLxCdJsmyZdplo/9EI+DJ0vUUM= +github.com/aquasecurity/trivy-checks v0.11.0 h1:hS5gSQyuyIITrY/kCY2AWQMUSwXLpdtbHDPaCs6eSaI= +github.com/aquasecurity/trivy-checks v0.11.0/go.mod h1:IAK3eHcKNxIHo/ckxKoHsXmEpUG45/38grW5bBjL9lw= github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTUAkbGqpzt9nJsO24RAdfG+ZSiLFj0G2jO8c= github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= @@ -824,8 +824,8 @@ github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6 h1:bCdxKjM8DpkNJXnOLVx github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6/go.mod h1:zQ6tOYz7oGI7MbLRDBXfo63puDoTroVcVNXWfmRDA1E= github.com/aws/aws-sdk-go-v2/service/athena v1.37.3 h1:qNLkDi/rOaauOuh33a4MNZjyfxvwIgC5qsDiHPvjDk0= github.com/aws/aws-sdk-go-v2/service/athena v1.37.3/go.mod h1:MlpC6swcjh1Il80u6XoeY2BTHIZRZWvoXOfaq3rfh8I= -github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.5 h1:synDXYpTr5FA80g8twNr49Dd7iAKnxerp93l/kNm/cQ= -github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.5/go.mod h1:Dil6nVeCPyPc1gF5EeCrVUTtXexn80MpfqhgSp/Zb64= +github.com/aws/aws-sdk-go-v2/service/cloudfront v1.36.4 h1:8qjQzwztUVdFJi/wrhPXxRgSbyAKDsnJuduHaw+yP30= +github.com/aws/aws-sdk-go-v2/service/cloudfront v1.36.4/go.mod h1:lHdM6itntBCcjvqxEHDoHkXRicwgY9aoPRptXuMdbgk= github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.35.6 h1:Yc+avPLGARzp4A9Oi9VRxvlcGqI+0MYIg4tPSupKv2U= github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.35.6/go.mod h1:zrqdG1b+4AGoTwTMVFzvzY7ARB3GPo4gKRuK8WPEo8w= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.32.2 h1:vQfCIHSDouEvbE4EuDrlCGKcrtABEqF3cMt61nGEV4g= @@ -834,8 +834,8 @@ github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.30.1 h1:ZMgx58Tqyr8kTSR9z github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.30.1/go.mod h1:4Oeb7n2r/ApBIHphQkprve380p/RpPWBotumd44EDGg= github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.5 h1:EPnlDd4V2EXywlOPAw/pMUW4PHUgSulKm4zXFU6bixE= github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.5/go.mod h1:G2JUWf01sbb5/A8qGcM4dqy4nbl4y4IGWmaCDWAvA2Y= -github.com/aws/aws-sdk-go-v2/service/docdb v1.33.1 h1:EblElvt9qFGCUquNs5F6pnM2MBkb6PLwxKXSvdJTsGw= -github.com/aws/aws-sdk-go-v2/service/docdb v1.33.1/go.mod h1:aaffY3Gzlg8/aZh8zb9qKQaSxgtDVCMCQ6xJurB7Ub4= +github.com/aws/aws-sdk-go-v2/service/docdb v1.34.4 h1:0hvzmeEwiNthBmi2mpTnZgqFCKUxKoLWaQYzulEnqk4= +github.com/aws/aws-sdk-go-v2/service/docdb v1.34.4/go.mod h1:KSNSbXXGchzkLYCDwq9H9ZfPs2zn0SIVgs7LXsfPlRQ= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 h1:XKO0BswTDeZMLDBd/b5pCEZGttNXrzRUVtFvp2Ak/Vo= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8/go.mod h1:N5tqZcYMM0N1PN7UQYJNWuGyO886OfnMhf/3MAbqMcI= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= @@ -874,8 +874,8 @@ github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5 h1:yCkyZDGahaCaAkdpVx8Te05t6e github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5/go.mod h1:/KmX+vXMPJGAB56reo95tnsXa6QPNx6qli4L1AmYb7E= github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6 h1:FO/aIHk86VePDUh/3Q/A5pnvu45miO1GZB8rIq2BUlA= github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6/go.mod h1:Sj7qc+P/GOGOPMDn8+B7Cs+WPq1Gk+R6CXRXVhZtWcA= -github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 h1:yS0JkEdV6h9JOo8sy2JSpjX+i7vsKifU8SIeHrqiDhU= -github.com/aws/aws-sdk-go-v2/service/kms v1.30.0/go.mod h1:+I8VUUSVD4p5ISQtzpgSva4I8cJ4SQ4b1dcBcof7O+g= +github.com/aws/aws-sdk-go-v2/service/kms v1.32.1 h1:FARrQLRQXpCFYylIUVF1dRij6YbPCmtwudq9NBk4kFc= +github.com/aws/aws-sdk-go-v2/service/kms v1.32.1/go.mod h1:8lETO9lelSG2B6KMXFh2OwPPqGV6WQM3RqLAEjP1xaU= github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6 h1:w8lI9zlVwRTL9f4KB9fRThddhRivv+EQQzv2nU8JDQo= github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6/go.mod h1:0V5z1X/8NA9eQ5cZSz5ZaHU8xA/hId2ZAlsHeO7Jrdk= github.com/aws/aws-sdk-go-v2/service/mq v1.20.6 h1:n86T5yw0kS6a5nbpkEpDzLPCBXXb35lx3iDkmQWlizA= @@ -941,8 +941,8 @@ github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HV github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= @@ -1529,8 +1529,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -2222,22 +2222,22 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 h1:o8iWeVFa1BcLtVEV0LzrCxV2/55tB3xLxADr6Kyoey4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1/go.mod h1:SEVfdK4IoBnbT2FXNM/k8yC08MrfbhWk3U4ljM8B3HE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.23.1 h1:cfuy3bXmLJS7M1RZmAL6SuhGtKUp2KEsrm00OlAXkq4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.23.1/go.mod h1:22jr92C6KwlwItJmQzfixzQM3oyyuYLCfHiMY+rpsPU= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= -go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -2957,10 +2957,10 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= -google.golang.org/genproto/googleapis/api v0.0.0-20240311173647-c811ad7063a7 h1:oqta3O3AnlWbmIE3bFnWbu4bRxZjfbWCp0cKSuZh01E= -google.golang.org/genproto/googleapis/api v0.0.0-20240311173647-c811ad7063a7/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -3006,8 +3006,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 5ccfd17fd85c0309ec0ef390d9fe33d2cf8709c4 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Tue, 28 May 2024 07:49:03 +0200 Subject: [PATCH 107/352] ci(deps): fix ineffassign and bodyclose in ".*_test.go$" (#6777) Signed-off-by: Matthieu MOREL --- .golangci.yaml | 2 -- pkg/fanal/analyzer/executable/executable_test.go | 1 + pkg/rpc/server/listen_test.go | 7 ++++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index f4957e6658ae..0c051ca4b145 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -109,10 +109,8 @@ issues: exclude-rules: - path: ".*_test.go$" linters: - - bodyclose - goconst - gosec - - ineffassign - misspell - unused - path: ".*_test.go$" diff --git a/pkg/fanal/analyzer/executable/executable_test.go b/pkg/fanal/analyzer/executable/executable_test.go index 709836a0804e..774359163a6a 100644 --- a/pkg/fanal/analyzer/executable/executable_test.go +++ b/pkg/fanal/analyzer/executable/executable_test.go @@ -47,6 +47,7 @@ func Test_executableAnalyzer_Analyze(t *testing.T) { Content: f, Info: stat, }) + require.NoError(t, err) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/rpc/server/listen_test.go b/pkg/rpc/server/listen_test.go index d0715e835a1a..e1ffaba23878 100644 --- a/pkg/rpc/server/listen_test.go +++ b/pkg/rpc/server/listen_test.go @@ -262,6 +262,8 @@ func Test_newServeMux(t *testing.T) { url := ts.URL + tt.path if tt.header == nil { resp, err = http.Get(url) + require.NoError(t, err) + defer resp.Body.Close() } else { req, err := http.NewRequest(http.MethodPost, url, http.NoBody) require.NoError(t, err) @@ -269,11 +271,10 @@ func Test_newServeMux(t *testing.T) { req.Header = tt.header client := new(http.Client) resp, err = client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() } - - require.NoError(t, err) assert.Equal(t, tt.want, resp.StatusCode) - defer resp.Body.Close() }) } } From 56dbe1f6768fe67fbc1153b74fde0f83eaa1b281 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Tue, 28 May 2024 11:22:45 +0400 Subject: [PATCH 108/352] fix: include packages unless it is not needed (#6765) Signed-off-by: knqyf263 --- .../references/configuration/cli/trivy_aws.md | 2 +- .../configuration/cli/trivy_convert.md | 2 +- .../configuration/cli/trivy_filesystem.md | 2 +- .../configuration/cli/trivy_image.md | 2 +- .../configuration/cli/trivy_kubernetes.md | 2 +- .../configuration/cli/trivy_repository.md | 2 +- .../configuration/cli/trivy_rootfs.md | 2 +- .../configuration/cli/trivy_sbom.md | 2 +- .../references/configuration/cli/trivy_vm.md | 2 +- integration/client_server_test.go | 13 +- integration/repo_test.go | 8 + .../fixtures/repo/trivy-ci-test/Cargo.lock | 666 +++++++++++++ .../fixtures/repo/trivy-ci-test/Pipfile.lock | 872 ++++++++++++++++++ integration/testdata/test-repo.json.golden | 7 +- pkg/commands/app_test.go | 4 + pkg/commands/artifact/run.go | 1 - pkg/flag/options.go | 37 +- pkg/flag/report_flags.go | 32 +- pkg/flag/report_flags_test.go | 34 +- pkg/report/json.go | 16 +- pkg/report/writer.go | 6 +- pkg/rpc/client/client.go | 1 - pkg/rpc/server/server.go | 7 +- pkg/scanner/langpkg/scan.go | 6 +- pkg/scanner/local/scan.go | 2 +- pkg/scanner/local/scan_test.go | 516 +++-------- pkg/scanner/ospkg/scan.go | 6 +- pkg/types/scan.go | 1 - pkg/types/target.go | 15 +- rpc/scanner/service.pb.go | 136 ++- rpc/scanner/service.proto | 3 +- rpc/scanner/service.twirp.go | 85 +- 32 files changed, 1880 insertions(+), 612 deletions(-) create mode 100644 integration/testdata/fixtures/repo/trivy-ci-test/Cargo.lock create mode 100644 integration/testdata/fixtures/repo/trivy-ci-test/Pipfile.lock diff --git a/docs/docs/references/configuration/cli/trivy_aws.md b/docs/docs/references/configuration/cli/trivy_aws.md index aa0255a7ebcd..fad5d106bc16 100644 --- a/docs/docs/references/configuration/cli/trivy_aws.md +++ b/docs/docs/references/configuration/cli/trivy_aws.md @@ -89,7 +89,7 @@ trivy aws [flags] --ignorefile string specify .trivyignore file (default ".trivyignore") --include-deprecated-checks include deprecated checks --include-non-failures include successes and exceptions, available with '--scanners misconfig' - --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --list-all-pkgs output all packages in the JSON report regardless of vulnerability --max-cache-age duration The maximum age of the cloud cache. Cached data will be required from the cloud provider if it is older than this. (default 24h0m0s) --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) -o, --output string output file name diff --git a/docs/docs/references/configuration/cli/trivy_convert.md b/docs/docs/references/configuration/cli/trivy_convert.md index e8fe589500ea..d76d303e3b03 100644 --- a/docs/docs/references/configuration/cli/trivy_convert.md +++ b/docs/docs/references/configuration/cli/trivy_convert.md @@ -26,7 +26,7 @@ trivy convert [flags] RESULT_JSON -h, --help help for convert --ignore-policy string specify the Rego file path to evaluate each vulnerability --ignorefile string specify .trivyignore file (default ".trivyignore") - --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --list-all-pkgs output all packages in the JSON report regardless of vulnerability -o, --output string output file name --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --report string specify a report format for the output (all,summary) (default "all") diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index e79b923e3786..4d90f4e87e63 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -55,7 +55,7 @@ trivy filesystem [flags] PATH --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") --license-confidence-level float specify license classifier's confidence level (default 0.9) --license-full eagerly look for licenses in source code headers and license files - --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --list-all-pkgs output all packages in the JSON report regardless of vulnerability --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 980cf68a795f..c61c6b648d7c 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -73,7 +73,7 @@ trivy image [flags] IMAGE_NAME --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") --license-confidence-level float specify license classifier's confidence level (default 0.9) --license-full eagerly look for licenses in source code headers and license files - --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --list-all-pkgs output all packages in the JSON report regardless of vulnerability --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index edd2efe6c5de..54dc2db07f75 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -69,7 +69,7 @@ trivy kubernetes [flags] [CONTEXT] --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") --k8s-version string specify k8s version to validate outdated api by it (example: 1.21.0) --kubeconfig string specify the kubeconfig file path to use - --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --list-all-pkgs output all packages in the JSON report regardless of vulnerability --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --no-progress suppress progress bar --node-collector-imageref string indicate the image reference for the node-collector scan job (default "ghcr.io/aquasecurity/node-collector:0.2.1") diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 7efde1657cc7..4f73c0d4e13d 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -55,7 +55,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") --license-confidence-level float specify license classifier's confidence level (default 0.9) --license-full eagerly look for licenses in source code headers and license files - --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --list-all-pkgs output all packages in the JSON report regardless of vulnerability --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index ea6a3093802f..6f264b5c2362 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -57,7 +57,7 @@ trivy rootfs [flags] ROOTDIR --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") --license-confidence-level float specify license classifier's confidence level (default 0.9) --license-full eagerly look for licenses in source code headers and license files - --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --list-all-pkgs output all packages in the JSON report regardless of vulnerability --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index 5d941e9744ba..d5c4030d7281 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -39,7 +39,7 @@ trivy sbom [flags] SBOM_PATH --ignored-licenses strings specify a list of license to ignore --ignorefile string specify .trivyignore file (default ".trivyignore") --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") - --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --list-all-pkgs output all packages in the JSON report regardless of vulnerability --no-progress suppress progress bar --offline-scan do not issue API requests to identify dependencies -o, --output string output file name diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index fc9b536a418d..51b7ad43cf38 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -49,7 +49,7 @@ trivy vm [flags] VM_IMAGE --ignorefile string specify .trivyignore file (default ".trivyignore") --include-non-failures include successes and exceptions, available with '--scanners misconfig' --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") - --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --list-all-pkgs output all packages in the JSON report regardless of vulnerability --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules") --no-progress suppress progress bar diff --git a/integration/client_server_test.go b/integration/client_server_test.go index 32ef4a972d26..a8a5f83f6941 100644 --- a/integration/client_server_test.go +++ b/integration/client_server_test.go @@ -39,10 +39,10 @@ type csArgs struct { func TestClientServer(t *testing.T) { tests := []struct { - name string - args csArgs - golden string - wantErr string + name string + args csArgs + golden string + override func(t *testing.T, want, got *types.Report) }{ { name: "alpine 3.9", @@ -270,6 +270,9 @@ func TestClientServer(t *testing.T) { Target: "https://github.com/knqyf263/trivy-ci-test", }, golden: "testdata/test-repo.json.golden", + override: func(t *testing.T, want, got *types.Report) { + want.ArtifactName = "https://github.com/knqyf263/trivy-ci-test" + }, }, } @@ -284,7 +287,7 @@ func TestClientServer(t *testing.T) { } runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{ - override: overrideUID, + override: overrideFuncs(overrideUID, tt.override), }) }) } diff --git a/integration/repo_test.go b/integration/repo_test.go index 00665f17ab8d..e11e5a21a8b4 100644 --- a/integration/repo_test.go +++ b/integration/repo_test.go @@ -234,6 +234,14 @@ func TestRepository(t *testing.T) { }, golden: "testdata/composer.lock.json.golden", }, + { + name: "multiple lockfiles", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/trivy-ci-test", + }, + golden: "testdata/test-repo.json.golden", + }, { name: "dockerfile", args: args{ diff --git a/integration/testdata/fixtures/repo/trivy-ci-test/Cargo.lock b/integration/testdata/fixtures/repo/trivy-ci-test/Cargo.lock new file mode 100644 index 000000000000..ca70dc137dd5 --- /dev/null +++ b/integration/testdata/fixtures/repo/trivy-ci-test/Cargo.lock @@ -0,0 +1,666 @@ +# It is not intended for manual editing. +[[package]] +name = "ammonia" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "html5ever 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tendril 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "autocfg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gdi32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "html5ever" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "markup5ever 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libressl-pnacl-sys" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pnacl-build-helper 1.4.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "maplit" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "markup5ever" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tendril 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "new_debug_unreachable" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "normal" +version = "0.1.0" +dependencies = [ + "ammonia 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-sys" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", + "libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "phf" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "phf_codegen" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "phf_generator" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "phf_shared" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pkg-config" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pnacl-build-helper" +version = "1.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "same-file" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "siphasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smallvec" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "string_cache" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "string_cache_codegen" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "string_cache_shared" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "0.15.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tendril" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "user32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "utf-8" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "walkdir" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum ammonia 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c799ecf1ad77acb48b643e2f45b12d60ee41576287fc575031aa020de88b8f45" +"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" +"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" +"checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" +"checksum html5ever 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5ce65ac8028cf5a287a7dbf6c4e0a6cf2dcf022ed5b167a81bae66ebf599a8b7" +"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)" = "c6785aa7dd976f5fbf3b71cfd9cd49d7f783c1ff565a858d71031c6c313aa5c6" +"checksum libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "cbc058951ab6a3ef35ca16462d7642c4867e6403520811f28537a4e2f2db3e71" +"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" +"checksum mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +"checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" +"checksum markup5ever 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f1af46a727284117e09780d05038b1ce6fc9c76cc6df183c3dae5a8955a25e21" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f40f005c60db6e03bae699e414c58bf9aa7ea02a2d0b9bfbcf19286cc4c82b30" +"checksum openssl 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b11754cb6c81bb9e62faaf0eb6d94dde2aab0928c04db5078b74242880f35eb1" +"checksum openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)" = "89c47ee94c352eea9ddaf8e364be7f978a3bb6d66d73176572484238dd5a5c3f" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +"checksum phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" +"checksum phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" +"checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" +"checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" +"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" +"checksum pnacl-build-helper 1.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dfbe13ee77c06fb633d71c72438bd983286bb3521863a753ade8e951c7efb090" +"checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" +"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" +"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" +"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7" +"checksum serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "a72e9b96fa45ce22a4bc23da3858dfccfd60acd28a25bcd328a98fdd6bea43fd" +"checksum serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "101b495b109a3e3ca8c4cbe44cf62391527cdfb6ba15821c5ce80bcd5ea23f9f" +"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" +"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" +"checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" +"checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423" +"checksum string_cache_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eea1eee654ef80933142157fdad9dd8bc43cf7c74e999e369263496f04ff4da" +"checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" +"checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe" +"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +"checksum tendril 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "707feda9f2582d5d680d733e38755547a3e8fb471e7ba11452ecfd9ce93a5d3b" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +"checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" +"checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" +"checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/integration/testdata/fixtures/repo/trivy-ci-test/Pipfile.lock b/integration/testdata/fixtures/repo/trivy-ci-test/Pipfile.lock new file mode 100644 index 000000000000..ce297a6a0515 --- /dev/null +++ b/integration/testdata/fixtures/repo/trivy-ci-test/Pipfile.lock @@ -0,0 +1,872 @@ +{ + "_meta": { + "hash": { + "sha256": "ad1805ab0e16cf08032c3fe45eeaa29b79e9c196650411977af14e31b12ff0cd" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "amqp": { + "hashes": [ + "sha256:043beb485774ca69718a35602089e524f87168268f0d1ae115f28b88d27f92d7", + "sha256:35a3b5006ca00b21aaeec8ceea07130f07b902dd61bfe42815039835f962f5f1" + ], + "version": "==2.4.2" + }, + "autopep8": { + "hashes": [ + "sha256:33d2b5325b7e1afb4240814fe982eea3a92ebea712869bfd08b3c0393404248c" + ], + "version": "==1.4.3" + }, + "babel": { + "hashes": [ + "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669", + "sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23" + ], + "version": "==2.6.0" + }, + "billiard": { + "hashes": [ + "sha256:756bf323f250db8bf88462cd042c992ba60d8f5e07fc5636c24ba7d6f4261d84" + ], + "version": "==3.6.0.0" + }, + "boto3": { + "hashes": [ + "sha256:bb69628f933a8dba22817c85289b3421b23ac643ff3202b13dd2e933c2717109", + "sha256:c75c45bae9dbdb2ff3fc3482d421a3901e552574a882dba1cffa064715acfbe7" + ], + "index": "pypi", + "version": "==1.9.130" + }, + "botocore": { + "hashes": [ + "sha256:128130b12f8ba4bf07a673b119135264060eb98f6a4a7419cbd1f2c6dc926827", + "sha256:59376112fdee707927b644dd44a1771861f8fe354a48d596131ced83d7a3c05b" + ], + "version": "==1.12.130" + }, + "celery": { + "extras": [ + "redis" + ], + "hashes": [ + "sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9", + "sha256:528e56767ae7e43a16cfef24ee1062491f5754368d38fcfffa861cdb9ef219be" + ], + "index": "pypi", + "version": "==4.3.0" + }, + "certifi": { + "hashes": [ + "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", + "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" + ], + "version": "==2019.3.9" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "decorator": { + "hashes": [ + "sha256:86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", + "sha256:f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6" + ], + "version": "==4.4.0" + }, + "django": { + "hashes": [ + "sha256:665457d4146bbd34ae9d2970fa3b37082d7b225b0671bfd24c337458f229db78", + "sha256:bde46d4dbc410678e89bc95ea5d312dd6eb4c37d0fa0e19c9415cad94addf22f" + ], + "index": "pypi", + "version": "==2.0.9" + }, + "django-celery-beat": { + "hashes": [ + "sha256:3c2c22647455be5503aca7450db64ea53acacee2d0aef3d7ac49aa3ef3845724", + "sha256:bfc22dad2884524697e1fcdfa63c0555a65151a97902c3045cd2cf7bf63970e4" + ], + "index": "pypi", + "version": "==1.4.0" + }, + "django-cors-headers": { + "hashes": [ + "sha256:1ccedec2973087be9d73f96d58c4f6660c823efc0385581e13efb77f060d0e02", + "sha256:fb44f6b9f10de847919305c3f0d38fcfbadfe0dd5cf1c866f37df66ad0dda1bb" + ], + "index": "pypi", + "version": "==2.5.2" + }, + "django-extensions": { + "hashes": [ + "sha256:109004f80b6f45ad1f56addaa59debca91d94aa0dc1cb19678b9364b4fe9b6f4", + "sha256:307766e5e6c1caffe76c5d99239d8115d14ae3f7cab2cd991fcffd763dad904b" + ], + "index": "pypi", + "version": "==2.1.6" + }, + "django-postgres-extra": { + "editable": true, + "git": "https://github.com/SectorLabs/django-postgres-extra", + "ref": "eef2ed5504d225858d4e4f5d77a838082ca6053e" + }, + "django-redis-cache": { + "hashes": [ + "sha256:77dcb9d11beef5ce77dadfb95328b7712c3d9bde8419a0ba92968712b9bff48b" + ], + "index": "pypi", + "version": "==2.0.0" + }, + "django-silk": { + "hashes": [ + "sha256:ab6b7151a54eaa14d4fc77a58fd75e7c0c8bd60d29c87e55575845a304b0c0eb", + "sha256:bce0e35d2a6ec3688a0c062c6964695beef4a452be48085f2c1e25f685652d9d" + ], + "index": "pypi", + "version": "==3.0.1" + }, + "django-timezone-field": { + "hashes": [ + "sha256:7d7a37cfeacec5b1e81cd2f0aa334d46ebaa369cd516028579ed343cbc676c38", + "sha256:d9fdab77c443b78c362ffaeb50fe7d7b54692c89aaae8ca1cae67848139b82ac" + ], + "version": "==3.0" + }, + "djangorestframework": { + "hashes": [ + "sha256:8a435df9007c8b7d8e69a21ef06650e3c0cbe0d4b09e55dd1bd74c89a75a9fcd", + "sha256:f7a266260d656e1cf4ca54d7a7349609dc8af4fe2590edd0ecd7d7643ea94a17" + ], + "index": "pypi", + "version": "==3.9.2" + }, + "djangorestframework-jwt": { + "hashes": [ + "sha256:5efe33032f3a4518a300dc51a51c92145ad95fb6f4b272e5aa24701db67936a7", + "sha256:ab15dfbbe535eede8e2e53adaf52ef0cf018ee27dbfad10cbc4cbec2ab63d38c" + ], + "index": "pypi", + "version": "==1.11.0" + }, + "docutils": { + "hashes": [ + "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", + "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", + "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" + ], + "version": "==0.14" + }, + "flower": { + "hashes": [ + "sha256:7f45acb297ab7cf3dd40140816143a2588f6938dbd70b8c46b59c7d8d1e93d55" + ], + "index": "pypi", + "version": "==0.9.3" + }, + "gprof2dot": { + "hashes": [ + "sha256:48c1e168c28b8a8eb23bf30fda78fe2ef218269a41505341ec27c27083e47cf4" + ], + "version": "==2016.10.13" + }, + "gunicorn": { + "hashes": [ + "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", + "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" + ], + "index": "pypi", + "version": "==19.9.0" + }, + "hiredis": { + "hashes": [ + "sha256:0124911115f2cb7deb4f8e221e109a53d3d718174b238a2c5e2162175a3929a5", + "sha256:0656658d0448c2c82c4890ae933c2c2e51196101d3d06fc19cc92e062410c2fd", + "sha256:09d284619f7142ddd7a4ffa94c12a0445e834737f4ce8739a737f2b1ca0f6142", + "sha256:12299b7026e5dc22ed0ff603375c1bf583cf59adbb0e4d062df434e9140d72dd", + "sha256:12fc6210f8dc3e9c8ce4b95e8f5db404b838dbdeb25bca41e33497de6d89334f", + "sha256:197febe5e63c77f4ad19b36e15ed33152064dc606c8b7413c7a0ca3fd04672cc", + "sha256:20e48289fbffb59a5ac7cc677fc02c2726c1da22488e5f7636b9feb9afde199f", + "sha256:26bed296b92b88db02afe214aa1fefad7f9e8ba88a5a7c0e355b55c4b168d212", + "sha256:321b19d2a21fd576111032fe7694d317de2c11b265ef775f2e3f22734a6b94c8", + "sha256:32d5f2c461250f5fc7ccef647682651b1d9f69443f16c213d7fa5e183222b233", + "sha256:36bfcc86715d109a5ef6edefd52b893de97d555cb5cb0e9cab83eb9665942ccc", + "sha256:438ddfd1484e98110959dc4648c0ba22c3307c9c0ae7e2a856755067f9ce9cef", + "sha256:66f17c1633b2fb967bf4165f7b3d369a1bdfe3537d3646cf9a7c208506c96c49", + "sha256:94ab0fa3ac93ab36a5400c474439881d182b43fd38a2766d984470c57931ae88", + "sha256:955f12da861f2608c181049f623bbb52851769e10639c4919cc586395b89813f", + "sha256:b1fd831f96ce0f715e9356574f5184b840b59eb8901fc5f9124fedbe84ad2a59", + "sha256:b3813c641494fca2eda66c32a2117816472a5a39b12f59f7887c6d17bdb8c77e", + "sha256:bbc3ee8663024c82a1226a0d56ad882f42a2fd8c2999bf52d27bdd25f1320f4b", + "sha256:bd12c2774b574f5b209196e25b03b5d62c7919bf69046bc7b955ebe84e0ec1fe", + "sha256:c54d2b3d7a2206df35f3c1140ac20ca6faf7819ff92ea5be8bf4d1cbdb433216", + "sha256:c7b0bcaf2353a2ad387dd8b5e1b5f55991adc3a7713ac3345a4ef0de58276690", + "sha256:c9319a1503efb3b5a4ec13b2f8fae2c23610a645e999cb8954d330f0610b0f6d", + "sha256:cbe5c0273224babe2ec77058643312d07aa5e8fed08901b3f7bccaa744c5728e", + "sha256:cc884ea50185009d794b31314a144110efc76b71beb0a5827a8bff970ae6d248", + "sha256:d1e2e751327781ad81df5a5a29d7c7b19ee0ebfbeddf037fd8df19ec1c06e18b", + "sha256:d2ef58cece6cae4b354411df498350d836f10b814c8a890df0d8079aff30c518", + "sha256:e97c953f08729900a5e740f1760305434d62db9f281ac351108d6c4b5bf51795", + "sha256:fcdf2e10f56113e1cb4326dbca7bf7edbfdbd246cd6d7ec088688e5439129e2c" + ], + "index": "pypi", + "version": "==1.0.0" + }, + "httplib2": { + "hashes": [ + "sha256:4ba6b8fd77d0038769bf3c33c9a96a6f752bc4cdf739701fdcaf210121f399d4" + ], + "version": "==0.12.1" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "jinja2": { + "hashes": [ + "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", + "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" + ], + "version": "==2.10.1" + }, + "jmespath": { + "hashes": [ + "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6", + "sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c" + ], + "version": "==0.9.4" + }, + "kombu": { + "hashes": [ + "sha256:389ba09e03b15b55b1a7371a441c894fd8121d174f5583bbbca032b9ea8c9edd", + "sha256:7b92303af381ef02fad6899fd5f5a9a96031d781356cd8e505fa54ae5ddee181" + ], + "version": "==4.5.0" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + ], + "version": "==1.1.1" + }, + "oauth2": { + "hashes": [ + "sha256:15b5c42301f46dd63113f1214b0d81a8b16254f65a86d3c32a1b52297f3266e6", + "sha256:c006a85e7c60107c7cc6da1b184b5c719f6dd7202098196dfa6e55df669b59bf" + ], + "index": "pypi", + "version": "==1.9.0.post1" + }, + "psycopg2-binary": { + "hashes": [ + "sha256:163d3ee445a0b4c0109877da9e46271aacf4e5e3d60ae7368669555c30f13e7c", + "sha256:1af0bfe7b0c13a0e613a27311fd4f9c5d024e8fc0f4b3d284e7df02a58a11fc0", + "sha256:2169c3a1bf52d5b30cc98625b5919a964c571a32e8646be20be6c7e3e82079de", + "sha256:218f079fa48e2ef812dc3d3ce6ec2f67ac56427ba4b038d5d6331f2cceb489c2", + "sha256:26a958930687e94c4c6c73c171e4d4783b82ae4e16aa3424e6bcd4529bceedf0", + "sha256:2c7c195aef3acdbc853942bc674844031a732890d2fee88a324298ed376b6c2b", + "sha256:2ecdbfed7004669472bfa27c8d51012c717c241c7154ae17e4c8f93024043525", + "sha256:345fc31b71a90ada1b51826537917b19a1af685a91c0f066787069c184d7d00f", + "sha256:378a06649503f548be5f1e9eec2e94cc1d6138250b82a08dcc6151bca8cec107", + "sha256:3f300bf2930e501dde09605de85cb2b84c2638e2c954be02a3c86f28176d3525", + "sha256:6c2f66c653ce8bbd7e789d0f7f92c3f9fea881b55226f0ae5ee550cce9e3cf0e", + "sha256:6fccbac2633831b877a8fbf865f7082d34895e82a015795a9f80f99a2efe2576", + "sha256:7a166f8ccb6888358d3e67795b057540ea7caa71ab9e089b0cb0097f01088965", + "sha256:8f6b84f887ec6fef6c1796779f8ec2603dc7e9ef52bc9269de719d4bcbdaebbb", + "sha256:92cf3ceb7bb90cf35b8bd993c640b15d4832ba0e142a3b9da5006ef217da595d", + "sha256:a20dfdf73f56da674926a3811929cff9fd23b9af90be9a6c36ac246a3486eef3", + "sha256:a84415df4689251556c961e4fe3b25d30e32f00faa8064ce0909458dbe0d67b2", + "sha256:ab1aa1cd50df3860f624c9713ee9e690eefd4e049d3a4d86577bab6e741e9616", + "sha256:abc9dcf85e75a8687f2a6d560c0c1a2593e8e34ba6f9ad6721f8212c5de179a2", + "sha256:c10454710a81a2f4b1ff4d1c83ac2cec63e0e55845a56324991514af5b1299d0", + "sha256:c38f80719e4dfae7a6311a4f091f07f4fb2fb5d602352015d5639f63f8fabb68", + "sha256:d75cf00605630b2cfefa5c62373c605dcda1cc0d607902847dbb8e8e9b67c1ce", + "sha256:dce15cb6ef604c9e38fdaa848f58f83153ade9f4aa5e4cf5812aa27163561750", + "sha256:e7e0db4311bb76bf3f6e0380f71912cfa6d0be7cc635e3772476050b0dabdabd", + "sha256:eac59cae78dfe3fbf7ece25c170d7a152f88df7643381aa5e7344c2028a8d8d4", + "sha256:ead7b3e1567bd14cacd44279c5e42cd19f54b9feed39180220253f4fbe3abd56", + "sha256:ed772a5e8e7e5dd6bede960a86940c17cf653c7f158dafa5d52e919b676f10ba", + "sha256:f2d73131acb94afa45de8b6b8a4bfb21bbe3736633d6478e53247f19dd8c299c" + ], + "index": "pypi", + "version": "==2.8.1" + }, + "py": { + "hashes": [ + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + ], + "version": "==1.8.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + ], + "version": "==2.5.0" + }, + "pycurl": { + "hashes": [ + "sha256:0f0cdfc7a92d4f2a5c44226162434e34f7d6967d3af416a6f1448649c09a25a4", + "sha256:10510a0016c862af467c6e069e051409f15f5831552bed03f5104b395a5d7dd1", + "sha256:208dd2c89e80d32a69397ba8a5cdb3bc0dc60f961a4f2a9662e5e1624dc799d1", + "sha256:6dc6ee5e7628400083471cba8044010860fe8b22e4dee05e42150a68047d7d9d", + "sha256:794bda39ea6fe434b6e1f58ab3bea9f0e6123fb43702fecd760eed6f1547b20a", + "sha256:dae7277e7c06da00947f3cd32c095b1e65eae09f07478ada4ea9dfa57020b646", + "sha256:eccea049aef47decc380746b3ff242d95636d578c907d0eab3b00918292d6c48" + ], + "index": "pypi", + "version": "==7.43.0.2" + }, + "pygments": { + "hashes": [ + "sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a", + "sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d" + ], + "version": "==2.3.1" + }, + "pyjwt": { + "hashes": [ + "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e", + "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96" + ], + "version": "==1.7.1" + }, + "python-crontab": { + "hashes": [ + "sha256:91ce4b245ee5e5c117aa0b21b485bc43f2d80df854a36e922b707643f50d7923" + ], + "version": "==2.3.6" + }, + "python-dateutil": { + "hashes": [ + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + ], + "markers": "python_version >= '2.7'", + "version": "==2.8.0" + }, + "python-http-client": { + "hashes": [ + "sha256:7e430f4b9dd2b621b0051f6a362f103447ea8e267594c602a5c502a0c694ee38" + ], + "version": "==3.1.0" + }, + "pytz": { + "hashes": [ + "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", + "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + ], + "version": "==2019.1" + }, + "pyyaml": { + "hashes": [ + "sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c", + "sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95", + "sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2", + "sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4", + "sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad", + "sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba", + "sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1", + "sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e", + "sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673", + "sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13", + "sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19" + ], + "index": "pypi", + "version": "==5.1" + }, + "redis": { + "hashes": [ + "sha256:6946b5dca72e86103edc8033019cc3814c031232d339d5f4533b02ea85685175", + "sha256:8ca418d2ddca1b1a850afa1680a7d2fd1f3322739271de4b704e0d4668449273" + ], + "version": "==3.2.1" + }, + "requests": { + "hashes": [ + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + ], + "index": "pypi", + "version": "==2.21.0" + }, + "retry": { + "hashes": [ + "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606", + "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4" + ], + "index": "pypi", + "version": "==0.9.2" + }, + "s3transfer": { + "hashes": [ + "sha256:7b9ad3213bff7d357f888e0fab5101b56fa1a0548ee77d121c3a3dbfbef4cb2e", + "sha256:f23d5cb7d862b104401d9021fc82e5fa0e0cf57b7660a1331425aab0c691d021" + ], + "version": "==0.2.0" + }, + "sendgrid": { + "hashes": [ + "sha256:351a7fc501d2b9d5afdcbc70a02490917057d6ce5cc22c558cadfc16229f157b", + "sha256:e1f93c72b3db3bd00d86f79ee926a093ee7e65533936a140855916569b08e0b0" + ], + "index": "pypi", + "version": "==6.0.4" + }, + "sentry-sdk": { + "hashes": [ + "sha256:ca2723556c102a1fabdf461b9a038d1d8631608c4d10085a7c06a0b590e79ad4", + "sha256:ced85a48171b3421d71f14f1682168f8008581411893e42359469c397fdf6285" + ], + "index": "pypi", + "version": "==0.7.10" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "sqlparse": { + "hashes": [ + "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", + "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + ], + "version": "==0.3.0" + }, + "tornado": { + "hashes": [ + "sha256:0662d28b1ca9f67108c7e3b77afabfb9c7e87bde174fbda78186ecedc2499a9d", + "sha256:4e5158d97583502a7e2739951553cbd88a72076f152b4b11b64b9a10c4c49409", + "sha256:732e836008c708de2e89a31cb2fa6c0e5a70cb60492bee6f1ea1047500feaf7f", + "sha256:8154ec22c450df4e06b35f131adc4f2f3a12ec85981a203301d310abf580500f", + "sha256:8e9d728c4579682e837c92fdd98036bd5cdefa1da2aaf6acf26947e6dd0c01c5", + "sha256:d4b3e5329f572f055b587efc57d29bd051589fb5a43ec8898c77a47ec2fa2bbb", + "sha256:e5f2585afccbff22390cddac29849df463b252b711aa2ce7c5f3f342a5b3b444" + ], + "version": "==5.1.1" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "markers": "python_version >= '3.4'", + "version": "==1.24.1" + }, + "vine": { + "hashes": [ + "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87", + "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af" + ], + "version": "==1.3.0" + }, + "xmltodict": { + "hashes": [ + "sha256:50d8c638ed7ecb88d90561beedbf720c9b4e851a9fa6c47ebd64e99d166d8a21", + "sha256:8bbcb45cc982f48b2ca8fe7e7827c5d792f217ecf1792626f808bf41c3b86051" + ], + "index": "pypi", + "version": "==0.12.0" + } + }, + "develop": { + "appdirs": { + "hashes": [ + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + ], + "version": "==1.4.3" + }, + "aspy.yaml": { + "hashes": [ + "sha256:ae249074803e8b957c83fdd82a99160d0d6d26dff9ba81ba608b42eebd7d8cd3", + "sha256:c7390d79f58eb9157406966201abf26da0d56c07e0ff0deadc39c8f4dbc13482" + ], + "version": "==1.2.0" + }, + "attrs": { + "hashes": [ + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + ], + "version": "==19.1.0" + }, + "autoflake": { + "hashes": [ + "sha256:c103e63466f11db3617167a2c68ff6a0cda35b940222920631c6eeec6b67e807" + ], + "index": "pypi", + "version": "==1.2" + }, + "black": { + "hashes": [ + "sha256:22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44", + "sha256:4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191" + ], + "index": "pypi", + "version": "==18.6b4" + }, + "cfgv": { + "hashes": [ + "sha256:6e9f2feea5e84bc71e56abd703140d7a2c250fc5ba38b8702fd6a68ed4e3b2ef", + "sha256:e7f186d4a36c099a9e20b04ac3108bd8bb9b9257e692ce18c8c3764d5cb12172" + ], + "version": "==1.6.0" + }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "version": "==7.0" + }, + "coverage": { + "hashes": [ + "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9", + "sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74", + "sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390", + "sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8", + "sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe", + "sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf", + "sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e", + "sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741", + "sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09", + "sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd", + "sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034", + "sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420", + "sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c", + "sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab", + "sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba", + "sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e", + "sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609", + "sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2", + "sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49", + "sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b", + "sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d", + "sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce", + "sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9", + "sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4", + "sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773", + "sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723", + "sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c", + "sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f", + "sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1", + "sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260", + "sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a" + ], + "index": "pypi", + "version": "==4.5.3" + }, + "dredd-hooks": { + "hashes": [ + "sha256:7d0527ee269d716126de912098b6d8750fcb3755232cb902e5a360f1921df780" + ], + "index": "pypi", + "version": "==0.2.0" + }, + "entrypoints": { + "hashes": [ + "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", + "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" + ], + "version": "==0.3" + }, + "factory-boy": { + "hashes": [ + "sha256:6f25cc4761ac109efd503f096e2ad99421b1159f01a29dbb917359dcd68e08ca", + "sha256:d552cb872b310ae78bd7429bf318e42e1e903b1a109e899a523293dfa762ea4f" + ], + "index": "pypi", + "version": "==2.11.1" + }, + "faker": { + "hashes": [ + "sha256:00b7011757c4907546f17d0e47df098b542ea2b04c966ee0e80a493aae2c13c8", + "sha256:745ac8b9c9526e338696e07b7f2e206e5e317e5744e22fdd7c2894bf19af41f1" + ], + "version": "==1.0.4" + }, + "flake8": { + "hashes": [ + "sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661", + "sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8" + ], + "index": "pypi", + "version": "==3.7.7" + }, + "identify": { + "hashes": [ + "sha256:244e7864ef59f0c7c50c6db73f58564151d91345cd9b76ed793458953578cadd", + "sha256:8ff062f90ad4b09cfe79b5dfb7a12e40f19d2e68a5c9598a49be45f16aba7171" + ], + "version": "==1.4.1" + }, + "importlib-metadata": { + "hashes": [ + "sha256:46fc60c34b6ed7547e2a723fc8de6dc2e3a1173f8423246b3ce497f064e9c3de", + "sha256:bc136180e961875af88b1ab85b4009f4f1278f8396a60526c0009f503a1a96ca" + ], + "version": "==0.9" + }, + "isort": { + "hashes": [ + "sha256:01cb7e1ca5e6c5b3f235f0385057f70558b70d2f00320208825fa62887292f43", + "sha256:268067462aed7eb2a1e237fcb287852f22077de3fb07964e87e00f829eea2d1a" + ], + "index": "pypi", + "version": "==4.3.17" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "more-itertools": { + "hashes": [ + "sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7", + "sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a" + ], + "index": "pypi", + "version": "==7.0.0" + }, + "nodeenv": { + "hashes": [ + "sha256:ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a" + ], + "version": "==1.3.3" + }, + "numpy": { + "hashes": [ + "sha256:1980f8d84548d74921685f68096911585fee393975f53797614b34d4f409b6da", + "sha256:22752cd809272671b273bb86df0f505f505a12368a3a5fc0aa811c7ece4dfd5c", + "sha256:23cc40313036cffd5d1873ef3ce2e949bdee0646c5d6f375bf7ee4f368db2511", + "sha256:2b0b118ff547fecabc247a2668f48f48b3b1f7d63676ebc5be7352a5fd9e85a5", + "sha256:3a0bd1edf64f6a911427b608a894111f9fcdb25284f724016f34a84c9a3a6ea9", + "sha256:3f25f6c7b0d000017e5ac55977a3999b0b1a74491eacb3c1aa716f0e01f6dcd1", + "sha256:4061c79ac2230594a7419151028e808239450e676c39e58302ad296232e3c2e8", + "sha256:560ceaa24f971ab37dede7ba030fc5d8fa173305d94365f814d9523ffd5d5916", + "sha256:62be044cd58da2a947b7e7b2252a10b42920df9520fc3d39f5c4c70d5460b8ba", + "sha256:6c692e3879dde0b67a9dc78f9bfb6f61c666b4562fd8619632d7043fb5b691b0", + "sha256:6f65e37b5a331df950ef6ff03bd4136b3c0bbcf44d4b8e99135d68a537711b5a", + "sha256:7a78cc4ddb253a55971115f8320a7ce28fd23a065fc33166d601f51760eecfa9", + "sha256:80a41edf64a3626e729a62df7dd278474fc1726836552b67a8c6396fd7e86760", + "sha256:893f4d75255f25a7b8516feb5766c6b63c54780323b9bd4bc51cdd7efc943c73", + "sha256:972ea92f9c1b54cc1c1a3d8508e326c0114aaf0f34996772a30f3f52b73b942f", + "sha256:9f1d4865436f794accdabadc57a8395bd3faa755449b4f65b88b7df65ae05f89", + "sha256:9f4cd7832b35e736b739be03b55875706c8c3e5fe334a06210f1a61e5c2c8ca5", + "sha256:adab43bf657488300d3aeeb8030d7f024fcc86e3a9b8848741ea2ea903e56610", + "sha256:bd2834d496ba9b1bdda3a6cf3de4dc0d4a0e7be306335940402ec95132ad063d", + "sha256:d20c0360940f30003a23c0adae2fe50a0a04f3e48dc05c298493b51fd6280197", + "sha256:d3b3ed87061d2314ff3659bb73896e622252da52558f2380f12c421fbdee3d89", + "sha256:dc235bf29a406dfda5790d01b998a1c01d7d37f449128c0b1b7d1c89a84fae8b", + "sha256:fb3c83554f39f48f3fa3123b9c24aecf681b1c289f9334f8215c1d3c8e2f6e5b" + ], + "version": "==1.16.2" + }, + "pandas": { + "hashes": [ + "sha256:071e42b89b57baa17031af8c6b6bbd2e9a5c68c595bc6bf9adabd7a9ed125d3b", + "sha256:17450e25ae69e2e6b303817bdf26b2cd57f69595d8550a77c308be0cd0fd58fa", + "sha256:17916d818592c9ec891cbef2e90f98cc85e0f1e89ed0924c9b5220dc3209c846", + "sha256:2538f099ab0e9f9c9d09bbcd94b47fd889bad06dc7ae96b1ed583f1dc1a7a822", + "sha256:366f30710172cb45a6b4f43b66c220653b1ea50303fbbd94e50571637ffb9167", + "sha256:42e5ad741a0d09232efbc7fc648226ed93306551772fc8aecc6dce9f0e676794", + "sha256:4e718e7f395ba5bfe8b6f6aaf2ff1c65a09bb77a36af6394621434e7cc813204", + "sha256:4f919f409c433577a501e023943e582c57355d50a724c589e78bc1d551a535a2", + "sha256:4fe0d7e6438212e839fc5010c78b822664f1a824c0d263fd858f44131d9166e2", + "sha256:5149a6db3e74f23dc3f5a216c2c9ae2e12920aa2d4a5b77e44e5b804a5f93248", + "sha256:627594338d6dd995cfc0bacd8e654cd9e1252d2a7c959449228df6740d737eb8", + "sha256:83c702615052f2a0a7fb1dd289726e29ec87a27272d775cb77affe749cca28f8", + "sha256:8c872f7fdf3018b7891e1e3e86c55b190e6c5cee70cab771e8f246c855001296", + "sha256:90f116086063934afd51e61a802a943826d2aac572b2f7d55caaac51c13db5b5", + "sha256:a3352bacac12e1fc646213b998bce586f965c9d431773d9e91db27c7c48a1f7d", + "sha256:bcdd06007cca02d51350f96debe51331dec429ac8f93930a43eb8fb5639e3eb5", + "sha256:c1bd07ebc15285535f61ddd8c0c75d0d6293e80e1ee6d9a8d73f3f36954342d0", + "sha256:c9a4b7c55115eb278c19aa14b34fcf5920c8fe7797a09b7b053ddd6195ea89b3", + "sha256:cc8fc0c7a8d5951dc738f1c1447f71c43734244453616f32b8aa0ef6013a5dfb", + "sha256:d7b460bc316064540ce0c41c1438c416a40746fd8a4fb2999668bf18f3c4acf1" + ], + "index": "pypi", + "version": "==0.24.2" + }, + "pre-commit": { + "hashes": [ + "sha256:75a9110eae00d009c913616c0fc8a6a02e7716c4a29a14cac9b313d2c7338ab0", + "sha256:f882c65316eb5b705fe4613e92a7c91055c1800102e4d291cfd18912ec9cf90e" + ], + "index": "pypi", + "version": "==1.15.1" + }, + "pycodestyle": { + "hashes": [ + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + ], + "version": "==2.5.0" + }, + "pydot": { + "hashes": [ + "sha256:67be714300c78fda5fd52f79ec994039e3f76f074948c67b5ff539b433ad354f", + "sha256:d49c9d4dd1913beec2a997f831543c8cbd53e535b1a739e921642fe416235f01" + ], + "index": "pypi", + "version": "==1.4.1" + }, + "pyflakes": { + "hashes": [ + "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", + "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + ], + "version": "==2.1.1" + }, + "pyparsing": { + "hashes": [ + "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", + "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03" + ], + "index": "pypi", + "version": "==2.4.0" + }, + "python-dateutil": { + "hashes": [ + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + ], + "markers": "python_version >= '2.7'", + "version": "==2.8.0" + }, + "pytz": { + "hashes": [ + "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", + "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + ], + "version": "==2019.1" + }, + "pyyaml": { + "hashes": [ + "sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c", + "sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95", + "sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2", + "sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4", + "sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad", + "sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba", + "sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1", + "sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e", + "sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673", + "sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13", + "sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19" + ], + "index": "pypi", + "version": "==5.1" + }, + "selenium": { + "hashes": [ + "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c", + "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d" + ], + "index": "pypi", + "version": "==3.141.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "tblib": { + "hashes": [ + "sha256:436e4200e63d92316551179dc540906652878df4ff39b43db30fcf6400444fe7", + "sha256:9bae4b8c44b06af0e114bfc4d5f6aa3eafd2119af5a4dcab34f51f1665f16c59" + ], + "index": "pypi", + "version": "==1.3.2" + }, + "text-unidecode": { + "hashes": [ + "sha256:5a1375bb2ba7968740508ae38d92e1f889a0832913cb1c447d5e2046061a396d", + "sha256:801e38bd550b943563660a91de8d4b6fa5df60a542be9093f7abf819f86050cc" + ], + "version": "==1.2" + }, + "toml": { + "hashes": [ + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + ], + "version": "==0.10.0" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "markers": "python_version >= '3.4'", + "version": "==1.24.1" + }, + "virtualenv": { + "hashes": [ + "sha256:6aebaf4dd2568a0094225ebbca987859e369e3e5c22dc7d52e5406d504890417", + "sha256:984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39" + ], + "version": "==16.4.3" + }, + "zipp": { + "hashes": [ + "sha256:55ca87266c38af6658b84db8cfb7343cdb0bf275f93c7afaea0d8e7a209c7478", + "sha256:682b3e1c62b7026afe24eadf6be579fb45fec54c07ea218bded8092af07a68c4" + ], + "version": "==0.3.3" + } + } +} diff --git a/integration/testdata/test-repo.json.golden b/integration/testdata/test-repo.json.golden index 360ba1f3f097..07cb12e69eef 100644 --- a/integration/testdata/test-repo.json.golden +++ b/integration/testdata/test-repo.json.golden @@ -1,7 +1,7 @@ { "SchemaVersion": 2, "CreatedAt": "2021-08-25T12:20:30.000000005Z", - "ArtifactName": "https://github.com/knqyf263/trivy-ci-test", + "ArtifactName": "testdata/fixtures/repo/trivy-ci-test", "ArtifactType": "repository", "Metadata": { "ImageConfig": { @@ -109,6 +109,11 @@ "LastModifiedDate": "2021-08-16T16:37:00Z" } ] + }, + { + "Target": "Pipfile.lock", + "Class": "lang-pkgs", + "Type": "pipenv" } ] } diff --git a/pkg/commands/app_test.go b/pkg/commands/app_test.go index 858e2ed6b245..143de739caf0 100644 --- a/pkg/commands/app_test.go +++ b/pkg/commands/app_test.go @@ -197,6 +197,7 @@ func TestFlags(t *testing.T) { scanners: types.Scanners{ types.VulnerabilityScanner, types.SecretScanner, + types.SBOMScanner, }, }, }, @@ -216,6 +217,7 @@ func TestFlags(t *testing.T) { scanners: types.Scanners{ types.VulnerabilityScanner, types.SecretScanner, + types.SBOMScanner, }, }, }, @@ -237,6 +239,7 @@ func TestFlags(t *testing.T) { scanners: types.Scanners{ types.VulnerabilityScanner, types.SecretScanner, + types.SBOMScanner, }, }, }, @@ -257,6 +260,7 @@ func TestFlags(t *testing.T) { scanners: types.Scanners{ types.VulnerabilityScanner, types.SecretScanner, + types.SBOMScanner, }, }, }, diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index f61e84928265..afdbf2ee6966 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -538,7 +538,6 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi Scanners: opts.Scanners, ImageConfigScanners: opts.ImageConfigScanners, // this is valid only for 'image' subcommand ScanRemovedPackages: opts.ScanRemovedPkgs, // this is valid only for 'image' subcommand - ListAllPackages: opts.ListAllPkgs, LicenseCategories: opts.LicenseCategories, FilePatterns: opts.FilePatterns, IncludeDevDeps: opts.IncludeDevDeps, diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 9f4032fb9711..13ed256b1c35 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -354,16 +354,7 @@ type Options struct { // Align takes consistency of options func (o *Options) Align() error { - if o.Format == types.FormatSPDX || o.Format == types.FormatSPDXJSON { - log.Info(`"--format spdx" and "--format spdx-json" disable security scanning`) - o.Scanners = nil - } - - // Vulnerability scanning is disabled by default for CycloneDX. - if o.Format == types.FormatCycloneDX && !viper.IsSet(ScannersFlag.ConfigName) { - log.Info(`"--format cyclonedx" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the CycloneDX report.`) - o.Scanners = nil - } + o.enableSBOM() if o.Compliance.Spec.ID != "" { if viper.IsSet(ScannersFlag.ConfigName) { @@ -394,6 +385,32 @@ func (o *Options) Align() error { return nil } +func (o *Options) enableSBOM() { + // Always need packages when the vulnerability scanner is enabled + if o.Scanners.Enabled(types.VulnerabilityScanner) { + o.Scanners.Enable(types.SBOMScanner) + } + + // Enable the SBOM scanner when a list of packages is necessary. + if o.ListAllPkgs || slices.Contains(types.SupportedSBOMFormats, o.Format) { + o.Scanners.Enable(types.SBOMScanner) + } + + if o.Format == types.FormatSPDX || o.Format == types.FormatSPDXJSON { + log.Info(`"--format spdx" and "--format spdx-json" disable security scanning`) + o.Scanners = types.Scanners{types.SBOMScanner} + } + + if o.Format == types.FormatCycloneDX { + // Vulnerability scanning is disabled by default for CycloneDX. + if !viper.IsSet(ScannersFlag.ConfigName) { + log.Info(`"--format cyclonedx" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the CycloneDX report.`) + o.Scanners = nil + } + o.Scanners.Enable(types.SBOMScanner) + } +} + // RegistryOpts returns options for OCI registries func (o *Options) RegistryOpts() ftypes.RegistryOptions { return ftypes.RegistryOptions{ diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go index 852710248972..d359f1f1b5b6 100644 --- a/pkg/flag/report_flags.go +++ b/pkg/flag/report_flags.go @@ -54,7 +54,7 @@ var ( ListAllPkgsFlag = Flag[bool]{ Name: "list-all-pkgs", ConfigName: "list-all-pkgs", - Usage: "enabling the option will output all packages regardless of vulnerability", + Usage: "output all packages in the JSON report regardless of vulnerability", } IgnoreFileFlag = Flag[string]{ Name: "ignorefile", @@ -208,10 +208,10 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { } } - // "--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 == types.FormatTable { - log.Warn(`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`) + // "--list-all-pkgs" option is unavailable with other than "--format json". + // If user specifies "--list-all-pkgs" with "--format table" or other formats, we should warn it. + if listAllPkgs && format != types.FormatJSON { + log.Warn(`"--list-all-pkgs" is only valid for the JSON format, for other formats a list of packages is automatically included.`) } // "--dependency-tree" option is available only with "--format table". @@ -224,11 +224,6 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { } } - // Enable '--list-all-pkgs' if needed - if f.forceListAllPkgs(format, listAllPkgs, dependencyTree) { - listAllPkgs = true - } - cs, err := loadComplianceTypes(f.Compliance.Value()) if err != nil { return ReportOptions{}, xerrors.Errorf("unable to load compliance spec: %w", err) @@ -273,23 +268,6 @@ func loadComplianceTypes(compliance string) (spec.ComplianceSpec, error) { return cs, nil } -func (f *ReportFlagGroup) forceListAllPkgs(format types.Format, listAllPkgs, dependencyTree bool) bool { - if slices.Contains(types.SupportedSBOMFormats, format) && !listAllPkgs { - log.Debugf("%q automatically enables '--list-all-pkgs'.", types.SupportedSBOMFormats) - return true - } - // We need this flag to insert dependency locations into Sarif('Package' struct contains 'Locations') - if format == types.FormatSarif && !listAllPkgs { - log.Debug("Sarif format automatically enables '--list-all-pkgs' to get locations") - return true - } - if dependencyTree && !listAllPkgs { - log.Debug("'--dependency-tree' enables '--list-all-pkgs'.") - return true - } - return false -} - func toSeverity(severity []string) []dbTypes.Severity { if len(severity) == 0 { return nil diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go index 58b050fd095b..6e56904c4dcf 100644 --- a/pkg/flag/report_flags_test.go +++ b/pkg/flag/report_flags_test.go @@ -46,34 +46,12 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { { name: "happy path with an cyclonedx", fields: fields{ - severities: "CRITICAL", - format: "cyclonedx", - listAllPkgs: true, - }, - want: flag.ReportOptions{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - Format: types.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'.`, - `Parsed severities severities=[CRITICAL]`, + severities: "CRITICAL", + format: "cyclonedx", }, want: flag.ReportOptions{ - Severities: []dbTypes.Severity{ - dbTypes.SeverityCritical, - }, - Format: types.FormatCycloneDX, - ListAllPkgs: true, + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + Format: types.FormatCycloneDX, }, }, { @@ -128,7 +106,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { listAllPkgs: true, }, wantLogs: []string{ - `"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`, + `"--list-all-pkgs" is only valid for the JSON format, for other formats a list of packages is automatically included.`, }, want: flag.ReportOptions{ Format: "table", @@ -224,7 +202,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { got, err := f.ToOptions() require.NoError(t, err) - assert.Equalf(t, tt.want, got, "ToOptions()") + assert.EqualExportedValuesf(t, tt.want, got, "ToOptions()") // Assert log messages assert.Equal(t, tt.wantLogs, out.Messages(), tt.name) diff --git a/pkg/report/json.go b/pkg/report/json.go index 57ac92cfe8ca..8529b5274560 100644 --- a/pkg/report/json.go +++ b/pkg/report/json.go @@ -6,6 +6,7 @@ import ( "fmt" "io" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/types" @@ -13,11 +14,22 @@ import ( // JSONWriter implements result Writer type JSONWriter struct { - Output io.Writer + Output io.Writer + ListAllPkgs bool } // Write writes the results in JSON format -func (jw JSONWriter) Write(ctx context.Context, report types.Report) error { +func (jw JSONWriter) Write(_ context.Context, report types.Report) error { + if !jw.ListAllPkgs { + // Delete packages + for i := range report.Results { + report.Results[i].Packages = nil + } + } + report.Results = lo.Filter(report.Results, func(r types.Result, _ int) bool { + return r.Target != "" || !r.IsEmpty() + }) + output, err := json.MarshalIndent(report, "", " ") if err != nil { return xerrors.Errorf("failed to marshal json: %w", err) diff --git a/pkg/report/writer.go b/pkg/report/writer.go index b4966d88a700..041c9acdfe8e 100644 --- a/pkg/report/writer.go +++ b/pkg/report/writer.go @@ -55,7 +55,10 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e IgnoredLicenses: option.IgnoredLicenses, } case types.FormatJSON: - writer = &JSONWriter{Output: output} + writer = &JSONWriter{ + Output: output, + ListAllPkgs: option.ListAllPkgs, + } case types.FormatGitHub: writer = &github.Writer{ Output: output, @@ -76,7 +79,6 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e } break } - var err error if writer, err = NewTemplateWriter(output, option.Template, option.AppVersion); err != nil { return xerrors.Errorf("failed to initialize template writer: %w", err) } diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index 36a69b88c04f..c2bcae2793a4 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -84,7 +84,6 @@ func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys Options: &rpc.ScanOptions{ VulnType: opts.VulnType, Scanners: xstrings.ToStringSlice(opts.Scanners), - ListAllPackages: opts.ListAllPackages, LicenseCategories: licenseCategories, IncludeDevDeps: opts.IncludeDevDeps, }, diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 5d9bc426703f..79801b3bd212 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -47,10 +47,9 @@ func (s *ScanServer) Scan(ctx context.Context, in *rpcScanner.ScanRequest) (*rpc return types.Scanner(s) }) options := types.ScanOptions{ - VulnType: in.Options.VulnType, - Scanners: scanners, - ListAllPackages: in.Options.ListAllPackages, - IncludeDevDeps: in.Options.IncludeDevDeps, + VulnType: in.Options.VulnType, + Scanners: scanners, + IncludeDevDeps: in.Options.IncludeDevDeps, } results, os, err := s.localScanner.Scan(ctx, in.Target, in.ArtifactId, in.BlobIds, options) if err != nil { diff --git a/pkg/scanner/langpkg/scan.go b/pkg/scanner/langpkg/scan.go index 6fb394d31336..d3923033e3f0 100644 --- a/pkg/scanner/langpkg/scan.go +++ b/pkg/scanner/langpkg/scan.go @@ -54,10 +54,8 @@ func (s *scanner) Scan(ctx context.Context, target types.ScanTarget, opts types. Type: app.Type, } - if opts.ListAllPackages { - sort.Sort(app.Packages) - result.Packages = app.Packages - } + sort.Sort(app.Packages) + result.Packages = app.Packages if opts.Scanners.Enabled(types.VulnerabilityScanner) { var err error diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 99146de0d55b..9a73ad97dbfa 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -153,7 +153,7 @@ func (s Scanner) ScanTarget(ctx context.Context, target types.ScanTarget, option func (s Scanner) scanVulnerabilities(ctx context.Context, target types.ScanTarget, options types.ScanOptions) ( types.Results, bool, error) { - if !options.ListAllPackages && !options.Scanners.Enabled(types.VulnerabilityScanner) { + if !options.Scanners.AnyEnabled(types.SBOMScanner, types.VulnerabilityScanner) { return nil, false, nil } diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go index 9bc8124d10ee..42a189ba5d68 100644 --- a/pkg/scanner/local/scan_test.go +++ b/pkg/scanner/local/scan_test.go @@ -21,6 +21,58 @@ import ( "github.com/aquasecurity/trivy/pkg/vulnerability" ) +var ( + muslPkg = ftypes.Package{ + Name: "musl", + Version: "1.2.3", + SrcName: "musl", + SrcVersion: "1.2.3", + Licenses: []string{"MIT"}, + Layer: ftypes.Layer{ + DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", + }, + } + railsPkg = ftypes.Package{ + Name: "rails", + Version: "4.0.2", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + } + innocentPkg = ftypes.Package{ + Name: "innocent", + Version: "1.2.3", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + } + uuidPkg = ftypes.Package{ + Name: "github.com/google/uuid", + Version: "1.6.0", + FilePath: "", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + Licenses: []string{"LGPL"}, + } + urllib3Pkg = ftypes.Package{ + Name: "urllib3", + Version: "3.2.1", + FilePath: "/usr/lib/python/site-packages/urllib3-3.2.1/METADATA", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + Licenses: []string{"MIT"}, + } + laravelPkg = ftypes.Package{ + Name: "laravel/framework", + Version: "6.0.0", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + } +) + func TestScanner_Scan(t *testing.T) { type args struct { target string @@ -61,28 +113,14 @@ func TestScanner_Scan(t *testing.T) { Name: "3.11", }, Packages: []ftypes.Package{ - { - Name: "musl", - Version: "1.2.3", - SrcName: "musl", - SrcVersion: "1.2.3", - Layer: ftypes.Layer{ - DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - }, + muslPkg, }, Applications: []ftypes.Application{ { Type: ftypes.Bundler, FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ - { - Name: "rails", - Version: "4.0.2", - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, + railsPkg, }, }, }, @@ -94,11 +132,14 @@ func TestScanner_Scan(t *testing.T) { Target: "alpine:latest (alpine 3.11)", Class: types.ClassOSPkg, Type: ftypes.Alpine, + Packages: ftypes.Packages{ + muslPkg, + }, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2020-9999", - PkgName: "musl", - InstalledVersion: "1.2.3", + PkgName: muslPkg.Name, + InstalledVersion: muslPkg.Version, FixedVersion: "1.2.4", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ @@ -117,11 +158,14 @@ func TestScanner_Scan(t *testing.T) { Target: "/app/Gemfile.lock", Class: types.ClassLangPkg, Type: ftypes.Bundler, + Packages: ftypes.Packages{ + railsPkg, + }, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2014-0081", - PkgName: "rails", - InstalledVersion: "4.0.2", + PkgName: railsPkg.Name, + InstalledVersion: railsPkg.Version, FixedVersion: "4.0.3, 3.2.17", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ @@ -169,46 +213,21 @@ func TestScanner_Scan(t *testing.T) { Name: "3.11", }, Packages: []ftypes.Package{ - { - Name: "musl", - Version: "1.2.3", - SrcName: "musl", - SrcVersion: "1.2.3", - Licenses: []string{"MIT"}, - Layer: ftypes.Layer{ - DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - }, + muslPkg, }, Applications: []ftypes.Application{ { Type: ftypes.GoModule, FilePath: "/app/go.mod", Packages: []ftypes.Package{ - { - Name: "github.com/google/uuid", - Version: "1.6.0", - FilePath: "", - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - Licenses: []string{"LGPL"}, - }, + uuidPkg, }, }, { Type: ftypes.PythonPkg, FilePath: "", Packages: []ftypes.Package{ - { - Name: "urllib3", - Version: "3.2.1", - FilePath: "/usr/lib/python/site-packages/urllib3-3.2.1/METADATA", - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - Licenses: []string{"MIT"}, - }, + urllib3Pkg, }, }, }, @@ -223,7 +242,7 @@ func TestScanner_Scan(t *testing.T) { { Severity: "UNKNOWN", Category: "unknown", - PkgName: "musl", + PkgName: muslPkg.Name, Name: "MIT", Confidence: 1, }, @@ -236,7 +255,7 @@ func TestScanner_Scan(t *testing.T) { { Severity: "UNKNOWN", Category: "unknown", - PkgName: "github.com/google/uuid", + PkgName: uuidPkg.Name, FilePath: "/app/go.mod", Name: "LGPL", Confidence: 1, @@ -251,7 +270,7 @@ func TestScanner_Scan(t *testing.T) { { Severity: "UNKNOWN", Category: "unknown", - PkgName: "urllib3", + PkgName: urllib3Pkg.Name, FilePath: "/usr/lib/python/site-packages/urllib3-3.2.1/METADATA", Name: "MIT", Confidence: 1, @@ -269,268 +288,6 @@ func TestScanner_Scan(t *testing.T) { Eosl: false, }, }, - { - name: "happy path with list all packages", - args: args{ - target: "alpine:latest", - layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - options: types.ScanOptions{ - VulnType: []string{ - types.VulnTypeOS, - types.VulnTypeLibrary, - }, - Scanners: types.Scanners{types.VulnerabilityScanner}, - ListAllPackages: true, - }, - }, - fixtures: []string{"testdata/fixtures/happy.yaml"}, - applyLayersExpectation: ApplierApplyLayersExpectation{ - Args: ApplierApplyLayersArgs{ - BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - }, - Returns: ApplierApplyLayersReturns{ - Detail: ftypes.ArtifactDetail{ - OS: ftypes.OS{ - Family: "alpine", - Name: "3.11", - }, - Packages: []ftypes.Package{ - { - Name: "musl", - Version: "1.2.3", - SrcName: "musl", - SrcVersion: "1.2.3", - Layer: ftypes.Layer{ - DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - }, - { - Name: "ausl", - Version: "1.2.3", - SrcName: "ausl", - SrcVersion: "1.2.3", - Layer: ftypes.Layer{ - DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - }, - }, - Applications: []ftypes.Application{ - { - Type: "bundler", - FilePath: "/app/Gemfile.lock", - Packages: []ftypes.Package{ - { - Name: "rails", - Version: "4.0.2", - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, - }, - }, - }, - }, - }, - }, - wantResults: types.Results{ - { - Target: "alpine:latest (alpine 3.11)", - Class: types.ClassOSPkg, - Type: ftypes.Alpine, - Packages: []ftypes.Package{ - { - Name: "ausl", - Version: "1.2.3", - SrcName: "ausl", - SrcVersion: "1.2.3", - Layer: ftypes.Layer{ - DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - }, - { - Name: "musl", - Version: "1.2.3", - SrcName: "musl", - SrcVersion: "1.2.3", - Layer: ftypes.Layer{ - DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - }, - }, - // For backward compatibility, will be removed - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-9999", - PkgName: "musl", - InstalledVersion: "1.2.3", - FixedVersion: "1.2.4", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999", - Vulnerability: dbTypes.Vulnerability{ - Title: "dos", - Description: "dos vulnerability", - Severity: "HIGH", - }, - }, - }, - }, - { - Target: "/app/Gemfile.lock", - Class: types.ClassLangPkg, - Type: ftypes.Bundler, - Packages: []ftypes.Package{ - { - Name: "rails", - Version: "4.0.2", - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, - }, - // For backward compatibility, will be removed - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2014-0081", - PkgName: "rails", - InstalledVersion: "4.0.2", - FixedVersion: "4.0.3, 3.2.17", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", - Vulnerability: dbTypes.Vulnerability{ - Title: "xss", - Description: "xss vulnerability", - Severity: "MEDIUM", - References: []string{ - "http://example.com", - }, - LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), - PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), - }, - }, - }, - }, - }, - wantOS: ftypes.OS{ - Family: "alpine", - Name: "3.11", - Eosl: true, - }, - }, - { - name: "happy path with list all packages and without vulnerabilities", - args: args{ - target: "alpine:latest", - layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - options: types.ScanOptions{ - VulnType: []string{ - types.VulnTypeOS, - types.VulnTypeLibrary, - }, - Scanners: types.Scanners{types.VulnerabilityScanner}, - ListAllPackages: true, - }, - }, - applyLayersExpectation: ApplierApplyLayersExpectation{ - Args: ApplierApplyLayersArgs{ - BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - }, - Returns: ApplierApplyLayersReturns{ - Detail: ftypes.ArtifactDetail{ - OS: ftypes.OS{ - Family: "alpine", - Name: "3.11", - }, - Packages: []ftypes.Package{ - { - Name: "musl", - Version: "1.2.3", - SrcName: "musl", - SrcVersion: "1.2.3", - Layer: ftypes.Layer{ - DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - }, - { - Name: "ausl", - Version: "1.2.3", - SrcName: "ausl", - SrcVersion: "1.2.3", - Layer: ftypes.Layer{ - DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - }, - }, - Applications: []ftypes.Application{ - { - Type: "bundler", - FilePath: "/app/Gemfile.lock", - Packages: []ftypes.Package{ - { - Name: "rails", - Version: "4.0.2", - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, - }, - }, - }, - }, - }, - }, - wantResults: types.Results{ - { - Target: "alpine:latest (alpine 3.11)", - Class: types.ClassOSPkg, - Type: ftypes.Alpine, - Packages: []ftypes.Package{ - { - Name: "ausl", - Version: "1.2.3", - SrcName: "ausl", - SrcVersion: "1.2.3", - Layer: ftypes.Layer{ - DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - }, - { - Name: "musl", - Version: "1.2.3", - SrcName: "musl", - SrcVersion: "1.2.3", - Layer: ftypes.Layer{ - DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", - }, - }, - }, - }, - { - Target: "/app/Gemfile.lock", - Class: types.ClassLangPkg, - Type: ftypes.Bundler, - Packages: []ftypes.Package{ - { - Name: "rails", - Version: "4.0.2", - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, - }, - }, - }, - wantOS: ftypes.OS{ - Family: "alpine", - Name: "3.11", - Eosl: true, - }, - }, { name: "happy path with empty os", args: args{ @@ -555,28 +312,16 @@ func TestScanner_Scan(t *testing.T) { Applications: []ftypes.Application{ { Type: ftypes.Bundler, - FilePath: "/app/Gemfile.lock", + FilePath: "/app1/Gemfile.lock", Packages: []ftypes.Package{ - { - Name: "innocent", // no vulnerability - Version: "1.2.3", - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, + innocentPkg, // no vulnerability }, }, { Type: ftypes.Bundler, - FilePath: "/app/Gemfile.lock", + FilePath: "/app2/Gemfile.lock", Packages: []ftypes.Package{ - { - Name: "rails", // one vulnerability - Version: "4.0.2", - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, + railsPkg, // one vulnerability }, }, }, @@ -585,18 +330,29 @@ func TestScanner_Scan(t *testing.T) { }, wantResults: types.Results{ { - Target: "/app/Gemfile.lock", + Target: "/app1/Gemfile.lock", Class: types.ClassLangPkg, - Type: "bundler", + Type: ftypes.Bundler, + Packages: ftypes.Packages{ + innocentPkg, + }, + }, + { + Target: "/app2/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: ftypes.Packages{ + railsPkg, + }, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2014-0081", - PkgName: "rails", - InstalledVersion: "4.0.2", + PkgName: railsPkg.Name, + InstalledVersion: railsPkg.Version, FixedVersion: "4.0.3, 3.2.17", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", Vulnerability: dbTypes.Vulnerability{ @@ -621,9 +377,8 @@ func TestScanner_Scan(t *testing.T) { target: "./result.cdx", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - VulnType: []string{types.VulnTypeLibrary}, - Scanners: types.Scanners{types.VulnerabilityScanner}, - ListAllPackages: true, + VulnType: []string{types.VulnTypeLibrary}, + Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, @@ -741,13 +496,7 @@ func TestScanner_Scan(t *testing.T) { Type: "bundler", FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ - { - Name: "rails", - Version: "4.0.2", - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, + railsPkg, }, }, }, @@ -765,6 +514,9 @@ func TestScanner_Scan(t *testing.T) { Target: "/app/Gemfile.lock", Class: types.ClassLangPkg, Type: ftypes.Bundler, + Packages: ftypes.Packages{ + railsPkg, + }, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2014-0081", @@ -822,16 +574,10 @@ func TestScanner_Scan(t *testing.T) { }, Applications: []ftypes.Application{ { - Type: "bundler", + Type: ftypes.Bundler, FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ - { - Name: "rails", - Version: "4.0.2", - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, + railsPkg, }, }, }, @@ -840,18 +586,19 @@ func TestScanner_Scan(t *testing.T) { }, wantResults: types.Results{ { - Target: "/app/Gemfile.lock", - Class: types.ClassLangPkg, - Type: ftypes.Bundler, + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: ftypes.Packages{railsPkg}, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2014-0081", - PkgName: "rails", - InstalledVersion: "4.0.2", + PkgName: railsPkg.Name, + InstalledVersion: railsPkg.Version, FixedVersion: "4.0.3, 3.2.17", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", Vulnerability: dbTypes.Vulnerability{ @@ -901,7 +648,7 @@ func TestScanner_Scan(t *testing.T) { name: "happy path with only language-specific package detection", args: args{ target: "alpine:latest", - layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + layerIDs: []string{"sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33"}, options: types.ScanOptions{ VulnType: []string{types.VulnTypeLibrary}, Scanners: types.Scanners{types.VulnerabilityScanner}, @@ -910,7 +657,7 @@ func TestScanner_Scan(t *testing.T) { fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ - BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, + BlobIDs: []string{"sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ @@ -919,38 +666,21 @@ func TestScanner_Scan(t *testing.T) { Name: "3.11", }, Packages: []ftypes.Package{ - { - Name: "musl", - Version: "1.2.3", - SrcName: "musl", - SrcVersion: "1.2.3", - }, + muslPkg, }, Applications: []ftypes.Application{ { Type: "bundler", FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ - { - Name: "rails", - Version: "4.0.2", - Layer: ftypes.Layer{ - DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", - }, - }, + railsPkg, }, }, { Type: "composer", FilePath: "/app/composer-lock.json", Packages: []ftypes.Package{ - { - Name: "laravel/framework", - Version: "6.0.0", - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, + laravelPkg, }, }, }, @@ -959,18 +689,19 @@ func TestScanner_Scan(t *testing.T) { }, wantResults: types.Results{ { - Target: "/app/Gemfile.lock", - Class: types.ClassLangPkg, - Type: ftypes.Bundler, + Target: "/app/Gemfile.lock", + Class: types.ClassLangPkg, + Type: ftypes.Bundler, + Packages: ftypes.Packages{railsPkg}, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2014-0081", - PkgName: "rails", - InstalledVersion: "4.0.2", + PkgName: railsPkg.Name, + InstalledVersion: railsPkg.Version, FixedVersion: "4.0.3, 3.2.17", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ - DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", Vulnerability: dbTypes.Vulnerability{ @@ -987,18 +718,19 @@ func TestScanner_Scan(t *testing.T) { }, }, { - Target: "/app/composer-lock.json", - Class: types.ClassLangPkg, - Type: ftypes.Composer, + Target: "/app/composer-lock.json", + Class: types.ClassLangPkg, + Type: ftypes.Composer, + Packages: ftypes.Packages{laravelPkg}, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2021-21263", - PkgName: "laravel/framework", - InstalledVersion: "6.0.0", + PkgName: laravelPkg.Name, + InstalledVersion: laravelPkg.Version, FixedVersion: "8.22.1, 7.30.3, 6.20.12", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, }, }, diff --git a/pkg/scanner/ospkg/scan.go b/pkg/scanner/ospkg/scan.go index fee369e369e4..fb6c5719b887 100644 --- a/pkg/scanner/ospkg/scan.go +++ b/pkg/scanner/ospkg/scan.go @@ -42,10 +42,8 @@ func (s *scanner) Scan(ctx context.Context, target types.ScanTarget, opts types. Type: target.OS.Family, } - if opts.ListAllPackages { - sort.Sort(target.Packages) - result.Packages = target.Packages - } + sort.Sort(target.Packages) + result.Packages = target.Packages if !opts.Scanners.Enabled(types.VulnerabilityScanner) { // Return packages only diff --git a/pkg/types/scan.go b/pkg/types/scan.go index 0b5833b12faa..04b6d6230230 100644 --- a/pkg/types/scan.go +++ b/pkg/types/scan.go @@ -26,7 +26,6 @@ type ScanOptions struct { Scanners Scanners ImageConfigScanners Scanners // Scanners for container image configuration ScanRemovedPackages bool - ListAllPackages bool LicenseCategories map[types.LicenseCategory][]string FilePatterns []string IncludeDevDeps bool diff --git a/pkg/types/target.go b/pkg/types/target.go index 134bded69cd7..26386b9d2429 100644 --- a/pkg/types/target.go +++ b/pkg/types/target.go @@ -29,6 +29,9 @@ const ( // NoneScanner is the scanner of none NoneScanner = Scanner("none") + // SBOMScanner is the virtual scanner of SBOM, which cannot be enabled by the user + SBOMScanner = Scanner("sbom") + // VulnerabilityScanner is the scanner of vulnerabilities VulnerabilityScanner = Scanner("vuln") @@ -70,12 +73,18 @@ var ( } ) -func (scanners Scanners) Enabled(s Scanner) bool { - return slices.Contains(scanners, s) +func (scanners *Scanners) Enable(s Scanner) { + if !scanners.Enabled(s) { + *scanners = append(*scanners, s) + } +} + +func (scanners *Scanners) Enabled(s Scanner) bool { + return slices.Contains(*scanners, s) } // AnyEnabled returns true if any of the passed scanners is included. -func (scanners Scanners) AnyEnabled(ss ...Scanner) bool { +func (scanners *Scanners) AnyEnabled(ss ...Scanner) bool { for _, s := range ss { if scanners.Enabled(s) { return true diff --git a/rpc/scanner/service.pb.go b/rpc/scanner/service.pb.go index eb75c1d582b6..50d0ead38681 100644 --- a/rpc/scanner/service.pb.go +++ b/rpc/scanner/service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 +// protoc-gen-go v1.34.0 // protoc v3.19.4 // source: rpc/scanner/service.proto @@ -148,7 +148,6 @@ type ScanOptions struct { VulnType []string `protobuf:"bytes,1,rep,name=vuln_type,json=vulnType,proto3" json:"vuln_type,omitempty"` Scanners []string `protobuf:"bytes,2,rep,name=scanners,proto3" json:"scanners,omitempty"` - ListAllPackages bool `protobuf:"varint,3,opt,name=list_all_packages,json=listAllPackages,proto3" json:"list_all_packages,omitempty"` LicenseCategories map[string]*Licenses `protobuf:"bytes,4,rep,name=license_categories,json=licenseCategories,proto3" json:"license_categories,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` IncludeDevDeps bool `protobuf:"varint,5,opt,name=include_dev_deps,json=includeDevDeps,proto3" json:"include_dev_deps,omitempty"` } @@ -199,13 +198,6 @@ func (x *ScanOptions) GetScanners() []string { return nil } -func (x *ScanOptions) GetListAllPackages() bool { - if x != nil { - return x.ListAllPackages - } - return false -} - func (x *ScanOptions) GetLicenseCategories() map[string]*Licenses { if x != nil { return x.LicenseCategories @@ -406,74 +398,72 @@ var file_rpc_scanner_service_proto_rawDesc = []byte{ 0x53, 0x63, 0x61, 0x6e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x20, 0x0a, 0x08, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xe3, 0x02, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x4f, + 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xbd, 0x02, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x75, 0x6c, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x76, 0x75, 0x6c, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x12, - 0x2a, 0x0a, 0x11, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x61, 0x63, 0x6b, - 0x61, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6c, 0x69, 0x73, 0x74, - 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x63, 0x0a, 0x12, 0x6c, - 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, - 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x4f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, - 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x6c, - 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, - 0x12, 0x28, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x65, 0x76, 0x5f, - 0x64, 0x65, 0x70, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, - 0x75, 0x64, 0x65, 0x44, 0x65, 0x76, 0x44, 0x65, 0x70, 0x73, 0x1a, 0x60, 0x0a, 0x16, 0x4c, 0x69, - 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, - 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, - 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x64, 0x0a, 0x0c, - 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x02, - 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x32, - 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x22, 0xd5, 0x03, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, - 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x45, 0x0a, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, - 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, - 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, - 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0f, 0x76, 0x75, 0x6c, - 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x54, 0x0a, 0x11, - 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, - 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x08, - 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, - 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, - 0x47, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x35, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, - 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, - 0x39, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, - 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x32, 0x50, 0x0a, 0x07, 0x53, 0x63, - 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x04, 0x53, 0x63, 0x61, 0x6e, 0x12, 0x1d, 0x2e, - 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x74, - 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, - 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, - 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x3b, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, - 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x0a, 0x12, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, + 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x63, 0x61, 0x6e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, + 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x11, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, + 0x72, 0x69, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, + 0x64, 0x65, 0x76, 0x5f, 0x64, 0x65, 0x70, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, 0x65, 0x76, 0x44, 0x65, 0x70, 0x73, 0x1a, 0x60, + 0x0a, 0x16, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, + 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x63, + 0x65, 0x6e, 0x73, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x64, 0x0a, 0x0c, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x4f, 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x32, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0xd5, 0x03, 0x0a, + 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, + 0x45, 0x0a, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, + 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, + 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x54, 0x0a, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, + 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, + 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, + 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, + 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x10, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x52, 0x0f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x12, 0x35, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x52, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x6c, 0x69, 0x63, + 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x65, 0x73, 0x32, 0x50, 0x0a, 0x07, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, + 0x45, 0x0a, 0x04, 0x53, 0x63, 0x61, 0x6e, 0x12, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, + 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, + 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, + 0x79, 0x2f, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, + 0x6e, 0x65, 0x72, 0x3b, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( diff --git a/rpc/scanner/service.proto b/rpc/scanner/service.proto index 63f98d2779c6..312930adc89a 100644 --- a/rpc/scanner/service.proto +++ b/rpc/scanner/service.proto @@ -25,9 +25,10 @@ message Licenses { message ScanOptions { repeated string vuln_type = 1; repeated string scanners = 2; - bool list_all_packages = 3; map license_categories = 4; bool include_dev_deps = 5; + + reserved 3; // deleted 'list_all_packages' } message ScanResponse { diff --git a/rpc/scanner/service.twirp.go b/rpc/scanner/service.twirp.go index 4c88aec5db46..0a2fbe5759e7 100644 --- a/rpc/scanner/service.twirp.go +++ b/rpc/scanner/service.twirp.go @@ -1094,47 +1094,46 @@ func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) } var twirpFileDescriptor0 = []byte{ - // 665 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xdd, 0x6e, 0xda, 0x4a, - 0x10, 0x16, 0x10, 0xc0, 0x0c, 0x47, 0x27, 0x64, 0x75, 0x4e, 0xe4, 0x90, 0xa6, 0x45, 0x5c, 0x54, - 0xa8, 0x17, 0xd0, 0x90, 0x56, 0xfd, 0xbb, 0x6a, 0x93, 0xb4, 0x8a, 0xd4, 0x2a, 0xd1, 0x12, 0xf5, - 0xa2, 0x37, 0xee, 0xb2, 0x9e, 0xd0, 0x55, 0x8c, 0xed, 0xec, 0xac, 0x91, 0x78, 0x95, 0xbe, 0x57, - 0x9f, 0xa0, 0x2f, 0x52, 0x79, 0xbd, 0x46, 0x81, 0x24, 0xbd, 0xb2, 0x67, 0xe6, 0x9b, 0x6f, 0xbe, - 0xdd, 0xfd, 0x34, 0xb0, 0xa7, 0x53, 0x39, 0x22, 0x29, 0xe2, 0x18, 0xf5, 0x88, 0x50, 0x2f, 0x94, - 0xc4, 0x61, 0xaa, 0x13, 0x93, 0xb0, 0x8e, 0xd1, 0x6a, 0xb1, 0x1c, 0xba, 0xe2, 0x70, 0x71, 0xd8, - 0xf5, 0x73, 0xb0, 0x4c, 0xe6, 0xf3, 0x24, 0x5e, 0xc7, 0xf6, 0x7f, 0x56, 0xa0, 0x3d, 0x91, 0x22, - 0xe6, 0x78, 0x93, 0x21, 0x19, 0xb6, 0x0b, 0x0d, 0x23, 0xf4, 0x0c, 0x8d, 0x5f, 0xe9, 0x55, 0x06, - 0x2d, 0xee, 0x22, 0xf6, 0x04, 0xda, 0x42, 0x1b, 0x75, 0x25, 0xa4, 0x09, 0x54, 0xe8, 0x57, 0x6d, - 0x11, 0xca, 0xd4, 0x59, 0xc8, 0xf6, 0xc0, 0x9b, 0x46, 0xc9, 0x34, 0x50, 0x21, 0xf9, 0xb5, 0x5e, - 0x6d, 0xd0, 0xe2, 0xcd, 0x3c, 0x3e, 0x0b, 0x89, 0xbd, 0x82, 0x66, 0x92, 0x1a, 0x95, 0xc4, 0xe4, - 0x6f, 0xf5, 0x2a, 0x83, 0xf6, 0xf8, 0x60, 0xb8, 0xa9, 0x70, 0x98, 0x6b, 0x38, 0x2f, 0x40, 0xbc, - 0x44, 0xf7, 0x7b, 0xe0, 0x7d, 0x56, 0x12, 0x63, 0x42, 0x62, 0xff, 0x41, 0x3d, 0x16, 0x73, 0x24, - 0xbf, 0x62, 0xc9, 0x8b, 0xa0, 0xff, 0xbb, 0x5a, 0xc8, 0x77, 0xad, 0x6c, 0x1f, 0x5a, 0x8b, 0x2c, - 0x8a, 0x03, 0xb3, 0x4c, 0xd1, 0x21, 0xbd, 0x3c, 0x71, 0xb9, 0x4c, 0x91, 0x75, 0xc1, 0x73, 0x13, - 0xc9, 0xaf, 0x16, 0xb5, 0x32, 0x66, 0xcf, 0x60, 0x27, 0x52, 0x64, 0x02, 0x11, 0x45, 0x41, 0x2a, - 0xe4, 0xb5, 0x98, 0x61, 0x7e, 0x8e, 0xca, 0xc0, 0xe3, 0xdb, 0x79, 0xe1, 0x7d, 0x14, 0x5d, 0xb8, - 0x34, 0x93, 0xc0, 0xa2, 0x42, 0x56, 0x20, 0x85, 0xc1, 0x59, 0xa2, 0x15, 0xe6, 0x47, 0xab, 0x0d, - 0xda, 0xe3, 0x17, 0x7f, 0x3d, 0xda, 0xd0, 0x1d, 0xe7, 0x78, 0xd5, 0x76, 0x1a, 0x1b, 0xbd, 0xe4, - 0x3b, 0xd1, 0x66, 0x9e, 0x0d, 0xa0, 0xa3, 0x62, 0x19, 0x65, 0x21, 0x06, 0x21, 0x2e, 0x82, 0x10, - 0x53, 0xf2, 0xeb, 0x56, 0xcf, 0xbf, 0x2e, 0x7f, 0x82, 0x8b, 0x13, 0x4c, 0xa9, 0xfb, 0x1d, 0x76, - 0xef, 0xa7, 0x65, 0x1d, 0xa8, 0x5d, 0xe3, 0xd2, 0xbd, 0x64, 0xfe, 0xcb, 0x9e, 0x43, 0x7d, 0x21, - 0xa2, 0x0c, 0xed, 0x03, 0xb6, 0xc7, 0xdd, 0xbb, 0x6a, 0xcb, 0x0b, 0xe7, 0x05, 0xf0, 0x6d, 0xf5, - 0x75, 0xa5, 0x1f, 0xc2, 0x3f, 0x85, 0x47, 0x28, 0x4d, 0x62, 0x42, 0xd6, 0x83, 0x6a, 0x42, 0x96, - 0xb6, 0x3d, 0xee, 0x38, 0x8a, 0xc2, 0x5d, 0xc3, 0xf3, 0x09, 0xaf, 0x26, 0xc4, 0xc6, 0xd0, 0xd4, - 0x48, 0x59, 0x64, 0x0a, 0x33, 0xb4, 0xc7, 0xfe, 0xdd, 0x49, 0xdc, 0x02, 0x78, 0x09, 0xec, 0xff, - 0xaa, 0x41, 0xa3, 0xc8, 0x3d, 0xe8, 0xc2, 0x53, 0xd8, 0xce, 0x5f, 0x13, 0xb5, 0x98, 0xaa, 0x48, - 0x99, 0xfc, 0xda, 0xab, 0x96, 0x7e, 0x7f, 0x5d, 0xc5, 0xd7, 0x5b, 0xa0, 0x25, 0xdf, 0xec, 0x61, - 0x97, 0xb0, 0x33, 0x57, 0x24, 0x93, 0xf8, 0x4a, 0xcd, 0x32, 0x2d, 0x4a, 0x6b, 0xe6, 0x44, 0x4f, - 0xd7, 0x89, 0x4e, 0xd0, 0xa0, 0x34, 0x18, 0x7e, 0xd9, 0x80, 0xf3, 0xbb, 0x04, 0xb9, 0x43, 0x65, - 0x24, 0x88, 0xfc, 0x86, 0xd5, 0x5c, 0x04, 0x8c, 0xc1, 0x96, 0x35, 0x63, 0xcd, 0x26, 0xed, 0x3f, - 0x3b, 0x04, 0x6f, 0xe5, 0xb1, 0xba, 0x1d, 0xfb, 0xff, 0xfa, 0x58, 0x67, 0x35, 0xbe, 0x82, 0xb1, - 0x4f, 0xd0, 0x91, 0x19, 0x99, 0x64, 0x1e, 0x68, 0xa4, 0x24, 0xd3, 0x12, 0xc9, 0x6f, 0xda, 0xd6, - 0x47, 0xeb, 0xad, 0xc7, 0x16, 0xc5, 0x1d, 0x88, 0x6f, 0xcb, 0xb5, 0x98, 0xd8, 0x4b, 0x68, 0x12, - 0x4a, 0x8d, 0x86, 0x7c, 0xef, 0xbe, 0xab, 0x9b, 0xd8, 0xe2, 0x47, 0x15, 0x87, 0x2a, 0x9e, 0xf1, - 0x12, 0xcb, 0xde, 0x80, 0xe7, 0x3c, 0x4a, 0x7e, 0xcb, 0xf6, 0x1d, 0xdc, 0x7f, 0x53, 0xce, 0x3f, - 0x7c, 0x05, 0x1f, 0x5f, 0x40, 0x73, 0x52, 0xbc, 0x3a, 0x3b, 0x85, 0xad, 0xfc, 0x97, 0x3d, 0xb0, - 0x00, 0xdc, 0x12, 0xea, 0x3e, 0x7e, 0xa8, 0x5c, 0xf8, 0xef, 0xc3, 0xd1, 0xb7, 0xc3, 0x99, 0x32, - 0x3f, 0xb2, 0x69, 0x3e, 0x7c, 0x24, 0x6e, 0x32, 0x41, 0x28, 0x33, 0xad, 0xcc, 0x72, 0x64, 0x1b, - 0x47, 0xb7, 0x76, 0xe3, 0x3b, 0xf7, 0x9d, 0x36, 0xec, 0xc2, 0x3b, 0xfa, 0x13, 0x00, 0x00, 0xff, - 0xff, 0x67, 0xa9, 0x8c, 0x3c, 0x39, 0x05, 0x00, 0x00, + // 648 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xdd, 0x4e, 0x1b, 0x3b, + 0x10, 0x56, 0x7e, 0x48, 0x36, 0x93, 0xa3, 0x43, 0xb0, 0xce, 0x41, 0x4b, 0x38, 0x9c, 0x46, 0xb9, + 0xa8, 0x72, 0x95, 0x94, 0xd0, 0xaa, 0x7f, 0x77, 0x05, 0x5a, 0x51, 0xb5, 0x02, 0x39, 0xa8, 0x17, + 0xbd, 0x49, 0x1d, 0xef, 0x90, 0x5a, 0x6c, 0x76, 0x17, 0x8f, 0x37, 0x52, 0x5e, 0xa5, 0xef, 0xd2, + 0xc7, 0xe8, 0xfb, 0x54, 0x6b, 0x7b, 0x11, 0x09, 0xd0, 0xab, 0xf5, 0xcc, 0x7c, 0xf3, 0xcd, 0x27, + 0xcf, 0xb7, 0x86, 0x3d, 0x9d, 0xc9, 0x11, 0x49, 0x91, 0x24, 0xa8, 0x47, 0x84, 0x7a, 0xa9, 0x24, + 0x0e, 0x33, 0x9d, 0x9a, 0x94, 0x75, 0x8c, 0x56, 0xcb, 0xd5, 0xd0, 0x17, 0x87, 0xcb, 0xc3, 0x6e, + 0x58, 0x80, 0x65, 0xba, 0x58, 0xa4, 0xc9, 0x3a, 0xb6, 0xff, 0xa3, 0x02, 0xed, 0x89, 0x14, 0x09, + 0xc7, 0x9b, 0x1c, 0xc9, 0xb0, 0x5d, 0x68, 0x18, 0xa1, 0xe7, 0x68, 0xc2, 0x4a, 0xaf, 0x32, 0x68, + 0x71, 0x1f, 0xb1, 0x27, 0xd0, 0x16, 0xda, 0xa8, 0x2b, 0x21, 0xcd, 0x54, 0x45, 0x61, 0xd5, 0x16, + 0xa1, 0x4c, 0x9d, 0x45, 0x6c, 0x0f, 0x82, 0x59, 0x9c, 0xce, 0xa6, 0x2a, 0xa2, 0xb0, 0xd6, 0xab, + 0x0d, 0x5a, 0xbc, 0x59, 0xc4, 0x67, 0x11, 0xb1, 0x97, 0xd0, 0x4c, 0x33, 0xa3, 0xd2, 0x84, 0xc2, + 0x7a, 0xaf, 0x32, 0x68, 0x8f, 0x0f, 0x86, 0x9b, 0x0a, 0x87, 0x85, 0x86, 0x73, 0x07, 0xe2, 0x25, + 0xba, 0xdf, 0x83, 0xe0, 0x93, 0x92, 0x98, 0x10, 0x12, 0xfb, 0x07, 0xb6, 0x12, 0xb1, 0x40, 0x0a, + 0x2b, 0x96, 0xdc, 0x05, 0xfd, 0x9f, 0x55, 0x27, 0xdf, 0xb7, 0xb2, 0x7d, 0x68, 0x2d, 0xf3, 0x38, + 0x99, 0x9a, 0x55, 0x86, 0x1e, 0x19, 0x14, 0x89, 0xcb, 0x55, 0x86, 0xac, 0x0b, 0x81, 0x9f, 0x48, + 0x61, 0xd5, 0xd5, 0xca, 0x98, 0x49, 0x60, 0xb1, 0x1b, 0x35, 0x95, 0xc2, 0xe0, 0x3c, 0xd5, 0x0a, + 0x0b, 0xb9, 0xb5, 0x41, 0x7b, 0xfc, 0xfc, 0x8f, 0x72, 0x87, 0x5e, 0xe2, 0xf1, 0x6d, 0xdb, 0x69, + 0x62, 0xf4, 0x8a, 0xef, 0xc4, 0x9b, 0x79, 0x36, 0x80, 0x8e, 0x4a, 0x64, 0x9c, 0x47, 0x38, 0x8d, + 0x70, 0x39, 0x8d, 0x30, 0xa3, 0x70, 0xab, 0x57, 0x19, 0x04, 0xfc, 0x6f, 0x9f, 0x3f, 0xc1, 0xe5, + 0x09, 0x66, 0xd4, 0xfd, 0x06, 0xbb, 0x0f, 0xd3, 0xb2, 0x0e, 0xd4, 0xae, 0x71, 0xe5, 0xb7, 0x53, + 0x1c, 0xd9, 0x33, 0xd8, 0x5a, 0x8a, 0x38, 0x47, 0xbb, 0x94, 0xf6, 0xb8, 0x7b, 0x5f, 0x6d, 0x79, + 0x89, 0xdc, 0x01, 0xdf, 0x54, 0x5f, 0x55, 0x3e, 0xd6, 0x83, 0x5a, 0xa7, 0xde, 0x8f, 0xe0, 0x2f, + 0xb7, 0x7d, 0xca, 0xd2, 0x84, 0x90, 0xf5, 0xa0, 0x9a, 0x92, 0x25, 0x6f, 0x8f, 0x3b, 0x9e, 0xc8, + 0xf9, 0x66, 0x78, 0x3e, 0xe1, 0xd5, 0x94, 0xd8, 0x18, 0x9a, 0x1a, 0x29, 0x8f, 0x8d, 0x5b, 0x73, + 0x7b, 0x1c, 0xde, 0x9f, 0xc7, 0x2d, 0x80, 0x97, 0xc0, 0xfe, 0xaf, 0x1a, 0x34, 0x5c, 0xee, 0x51, + 0x7f, 0x9d, 0xc2, 0x76, 0xb1, 0x27, 0xd4, 0x62, 0xa6, 0x62, 0x65, 0x8a, 0xcb, 0xaf, 0x5a, 0xfa, + 0xfd, 0x75, 0x15, 0x5f, 0xee, 0x80, 0x56, 0x7c, 0xb3, 0x87, 0x5d, 0xc2, 0xce, 0x42, 0x91, 0x4c, + 0x93, 0x2b, 0x35, 0xcf, 0xb5, 0x28, 0x4d, 0x57, 0x10, 0x3d, 0x5d, 0x27, 0x3a, 0x41, 0x83, 0xd2, + 0x60, 0xf4, 0x79, 0x03, 0xce, 0xef, 0x13, 0x14, 0xde, 0x93, 0xb1, 0x20, 0x0a, 0x1b, 0x56, 0xb3, + 0x0b, 0x18, 0x83, 0xba, 0xb5, 0x59, 0xcd, 0x26, 0xed, 0x99, 0x1d, 0x42, 0x90, 0x09, 0x79, 0x2d, + 0xe6, 0x58, 0x6c, 0xb6, 0x18, 0xfb, 0xef, 0xfa, 0xd8, 0x0b, 0x57, 0xe5, 0xb7, 0x30, 0xf6, 0x01, + 0x3a, 0x32, 0x27, 0x93, 0x2e, 0xa6, 0x1a, 0x29, 0xcd, 0xb5, 0x44, 0x0a, 0x9b, 0xb6, 0xf5, 0xbf, + 0xf5, 0xd6, 0x63, 0x8b, 0xe2, 0x1e, 0xc4, 0xb7, 0xe5, 0x5a, 0x4c, 0xec, 0x05, 0x34, 0x09, 0xa5, + 0x46, 0x43, 0x61, 0xf0, 0xd0, 0xd5, 0x4d, 0x6c, 0xf1, 0xbd, 0x4a, 0x22, 0x95, 0xcc, 0x79, 0x89, + 0x65, 0xaf, 0x21, 0xf0, 0x4e, 0xa5, 0xb0, 0x65, 0xfb, 0x0e, 0x1e, 0xbe, 0x29, 0xef, 0x22, 0x7e, + 0x0b, 0x1f, 0x5f, 0x40, 0x73, 0xe2, 0xb6, 0xce, 0x4e, 0xa1, 0x5e, 0x1c, 0xd9, 0x23, 0xbf, 0xb6, + 0x7f, 0x5e, 0xba, 0xff, 0x3f, 0x56, 0x76, 0xfe, 0x7b, 0x77, 0xf4, 0xf5, 0x70, 0xae, 0xcc, 0xf7, + 0x7c, 0x56, 0x0c, 0x1f, 0x89, 0x9b, 0x5c, 0x10, 0xca, 0x5c, 0x2b, 0xb3, 0x1a, 0xd9, 0xc6, 0xd1, + 0x9d, 0x57, 0xef, 0xad, 0xff, 0xce, 0x1a, 0xf6, 0x29, 0x3b, 0xfa, 0x1d, 0x00, 0x00, 0xff, 0xff, + 0x2e, 0x74, 0xc8, 0xad, 0x13, 0x05, 0x00, 0x00, } From 0e3560abe54d0a8b9710c94392da83e34146bdc1 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 28 May 2024 13:37:55 +0600 Subject: [PATCH 109/352] docs(plugin): add missed `plugin` section (#6799) --- docs/docs/configuration/reporting.md | 2 +- mkdocs.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index 8671501ad885..b8b61d34a346 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -399,7 +399,7 @@ $ trivy [--format ] --output plugin= [--output-plu ``` This is useful for cases where you want to convert the output into a custom format, or when you want to send the output somewhere. -For more details, please check [here](../plugin/plugins.md#output-plugins). +For more details, please check [here](../plugin/user-guide.md#output-mode-support). ## Converting To generate multiple reports, you can generate the JSON report first and convert it to other formats with the `convert` subcommand. diff --git a/mkdocs.yml b/mkdocs.yml index 42233f0f1395..6b5bd93249d5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -131,6 +131,10 @@ nav: - Compliance: - Built-in Compliance: docs/compliance/compliance.md - Custom Compliance: docs/compliance/contrib-compliance.md + - Plugins: + - Overview: docs/plugin/index.md + - User guide: docs/plugin/user-guide.md + - Developer guide: docs/plugin/developer-guide.md - Advanced: - Modules: docs/advanced/modules.md - Air-Gapped Environment: docs/advanced/air-gap.md From e66dbb935764908f0b2b9a55cbfe6c107f101a31 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 28 May 2024 13:44:06 +0600 Subject: [PATCH 110/352] chore(alpine): add eol date for Alpine 3.20 (#6800) --- docs/docs/coverage/os/index.md | 2 +- pkg/detector/ospkg/alpine/alpine.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docs/coverage/os/index.md b/docs/docs/coverage/os/index.md index 3294557d6a7c..a8d2670d7d65 100644 --- a/docs/docs/coverage/os/index.md +++ b/docs/docs/coverage/os/index.md @@ -11,7 +11,7 @@ Trivy supports operating systems for | OS | Supported Versions | Package Managers | |--------------------------------------|-------------------------------------|------------------| -| [Alpine Linux](alpine.md) | 2.2 - 2.7, 3.0 - 3.19, edge | apk | +| [Alpine Linux](alpine.md) | 2.2 - 2.7, 3.0 - 3.20, edge | apk | | [Wolfi Linux](wolfi.md) | (n/a) | apk | | [Chainguard](chainguard.md) | (n/a) | apk | | [Red Hat Enterprise Linux](rhel.md) | 6, 7, 8 | dnf/yum/rpm | diff --git a/pkg/detector/ospkg/alpine/alpine.go b/pkg/detector/ospkg/alpine/alpine.go index 48f7abcfbec6..063c3018f3f1 100644 --- a/pkg/detector/ospkg/alpine/alpine.go +++ b/pkg/detector/ospkg/alpine/alpine.go @@ -47,6 +47,7 @@ var ( "3.17": time.Date(2024, 11, 22, 23, 59, 59, 0, time.UTC), "3.18": time.Date(2025, 5, 9, 23, 59, 59, 0, time.UTC), "3.19": time.Date(2025, 11, 1, 23, 59, 59, 0, time.UTC), + "3.20": time.Date(2026, 04, 1, 23, 59, 59, 0, time.UTC), "edge": time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC), } ) From a447f6ba94b6f8b14177dc5e4369a788e2020d90 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Tue, 28 May 2024 14:51:07 +0400 Subject: [PATCH 111/352] feat(vex): improve relationship support in CSAF VEX (#6735) Signed-off-by: knqyf263 --- docs/docs/supply-chain/vex.md | 138 +++++++++++++-- pkg/vex/csaf.go | 162 ++++++++++-------- pkg/vex/testdata/csaf-affected.json | 93 ---------- ...omponents.json => csaf-relationships.json} | 18 +- .../{csaf-not-affected.json => csaf.json} | 25 ++- pkg/vex/vex.go | 4 - pkg/vex/vex_test.go | 150 ++++++++-------- 7 files changed, 318 insertions(+), 272 deletions(-) delete mode 100644 pkg/vex/testdata/csaf-affected.json rename pkg/vex/testdata/{csaf-not-affected-sub-components.json => csaf-relationships.json} (87%) rename pkg/vex/testdata/{csaf-not-affected.json => csaf.json} (58%) diff --git a/docs/docs/supply-chain/vex.md b/docs/docs/supply-chain/vex.md index 59f3c5b97353..6dc02b6d088b 100644 --- a/docs/docs/supply-chain/vex.md +++ b/docs/docs/supply-chain/vex.md @@ -11,8 +11,6 @@ Currently, Trivy supports the following three formats: - [OpenVEX](https://github.com/openvex/spec) - [CSAF](https://oasis-open.github.io/csaf-documentation/specification.html) -This is still an experimental implementation, with only minimal functionality added. - ## CycloneDX | Target | Supported | |:---------------:|:---------:| @@ -40,7 +38,7 @@ The following steps are required: ### Generate the SBOM You can generate a CycloneDX SBOM with Trivy as follows: -```shell +```bash $ trivy image --format cyclonedx --output debian11.sbom.cdx debian:11 ``` @@ -49,7 +47,7 @@ Next, create a VEX based on the generated SBOM. Multiple vulnerability statuses can be defined under `vulnerabilities`. Take a look at the example below. -``` +```bash $ cat < trivy.vex.cdx { "bomFormat": "CycloneDX", @@ -105,7 +103,7 @@ For more details on CycloneDX VEX and BOM-Link, please refer to the following li ### Scan SBOM with VEX Provide the VEX when scanning the CycloneDX SBOM. -``` +```bash $ trivy sbom trivy.sbom.cdx --vex trivy.vex.cdx ... 2023-04-13T12:55:44.838+0300 INFO Filtered out the detected vulnerability {"VEX format": "CycloneDX", "vulnerability-id": "CVE-2020-8911", "status": "not_affected", "justification": "code_not_reachable"} @@ -145,10 +143,10 @@ The following steps are required: ### Create the VEX document Please see also [the example](https://github.com/openvex/examples). -In Trivy, [the Package URL (PURL)][purl] is used as the product identifier. +Trivy requires [the Package URL (PURL)][purl] as the product identifier. -``` -$ cat < debian11.openvex +```bash +$ cat < debian11.openvex.json { "@context": "https://openvex.dev/ns/v0.2.0", "@id": "https://openvex.dev/docs/public/vex-2e67563e128250cbcb3e98930df948dd053e43271d70dc50cfa22d57e03fe96f", @@ -169,19 +167,107 @@ $ cat < debian11.openvex EOF ``` -In the above example, PURLs, located in `packages.externalRefs.referenceLocator` in SPDX are used for the product identifier. +In the above example, PURLs, `pkg:deb/debian/libdb5.3@5.3.28+dfsg1-0.8` are used for the product identifier. +You can find PURLs in the JSON report generated by Trivy. +This VEX statement is applied if the PURL specified in the VEX matches the PURL found during the scan. +See [here](#purl-matching) for more details of PURL matching. -!!! note - If a qualifier is specified in the PURL used as the product id in the VEX, the qualifier is compared. - Other qualifiers are ignored in the comparison. - `pkg:deb/debian/curl@7.50.3-1` in OpenVEX matches `pkg:deb/debian/curl@7.50.3-1?arch=i386`, - while `pkg:deb/debian/curl@7.50.3-1?arch=amd64` does not match `pkg:deb/debian/curl@7.50.3-1?arch=i386`. +Trivy also supports [OpenVEX subcomponents][openvex-subcomponent], which allow for more precise specification of the scope of a VEX statement, reducing the risk of incorrect filtering. +Let's say you want to suppress vulnerabilities within a container image. +If you only specify the PURL of the container image as the product, the resulting VEX would look like this: + +

+OpenVEX products only + +```json +"statements": [ + { + "vulnerability": {"name": "CVE-2024-32002"}, + "products": [ + {"@id": "pkg:oci/trivy?repository_url=ghcr.io%2Faquasecurity%2Ftrivy"} + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } +] +``` + +
+ +However, this approach would suppress all instances of CVE-2024-32002 within the container image. +If the intention is to declare that the `git` package distributed by Alpine Linux within this image is not affected, subcomponents can be utilized as follows: + +
+OpenVEX subcomponents + +```json +"statements": [ + { + "vulnerability": {"name": "CVE-2024-32002"}, + "products": [ + { + "@id": "pkg:oci/trivy?repository_url=ghcr.io%2Faquasecurity%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/git"} + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } +] +``` + +
+ +By declaring the subcomponent in this manner, Trivy will filter the results, considering only the `git` package within the `ghcr.io/aquasecurity/trivy` container image as not affected. +Omitting the version in the PURL applies the statement to all versions of the package. +More details about PURL matching can be found [here](#purl-matching). + +Furthermore, the product specified in a VEX statement does not necessarily need to be the target of the scan. +It is possible to specify a component that is included in the scan target as the product. +For example, you can designate a specific Go project as the product and its dependent modules as subcomponents. + +In the following example, the VEX statement declares that the `github.com/docker/docker` module, which is a dependency of the `github.com/aquasecurity/trivy` Go project, is not affected by CVE-2024-29018. + +
+OpenVEX intermediate components + +```json +"statements": [ + { + "vulnerability": {"name": "CVE-2024-29018"}, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "subcomponents": [ + { "@id": "pkg:golang/github.com/docker/docker" } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } +] +``` + +
+ +This VEX document can be used when scanning a container image as well as other targets. +The VEX statement will be applied when Trivy finds the Go binary within the container image. + +```bash +$ trivy image ghcr.io/aquasecurity/trivy:0.50.0 --vex trivy.openvex.json +``` + +VEX documents can indeed be reused across different container images, eliminating the need to issue separate VEX documents for each image. +This is particularly useful when there is a common component or library that is used across multiple projects or container images. ### Scan with VEX Provide the VEX when scanning your target. -``` -$ trivy image debian:11 --vex debian11.openvex +```bash +$ trivy image debian:11 --vex debian11.openvex.json ... 2023-04-26T17:56:05.358+0300 INFO Filtered out the detected vulnerability {"VEX format": "OpenVEX", "vulnerability-id": "CVE-2019-8457", "status": "not_affected", "justification": "vulnerable_code_not_in_execute_path"} @@ -215,7 +301,10 @@ The following steps are required: ### Create the CSAF document Create a CSAF document in JSON format as follows: -``` +
+CSAF VEX + +```bash $ cat < debian11.vex.csaf { "document": { @@ -313,10 +402,20 @@ $ cat < debian11.vex.csaf EOF ``` +
+ +Trivy also supports [CSAF relationships][csaf-relationship], reducing the risk of incorrect filtering. +It works in the same way as OpenVEX subcomponents. +At present, the specified relationship category is not taken into account and all the following categories are treated internally as "depends_on". + +- default_component_of +- installed_on +- installed_with + ### Scan with CSAF VEX Provide the CSAF document when scanning your target. -```console +```bash $ trivy image debian:11 --vex debian11.vex.csaf ... 2024-01-02T10:28:26.704+0100 INFO Filtered out the detected vulnerability {"VEX format": "CSAF", "vulnerability-id": "CVE-2019-8457", "status": "not_affected"} @@ -376,3 +475,6 @@ does not match: [openvex]: https://github.com/openvex/spec [purl]: https://github.com/package-url/purl-spec [purl-matching]: https://github.com/openvex/spec/issues/27 + +[openvex-subcomponent]: https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md#subcomponent +[csaf-relationship]: https://docs.oasis-open.org/csaf/csaf/v2.0/os/csaf-v2.0-os.html#3224-product-tree-property---relationships \ No newline at end of file diff --git a/pkg/vex/csaf.go b/pkg/vex/csaf.go index 8f6ecc9a84cb..35680a8ddfc5 100644 --- a/pkg/vex/csaf.go +++ b/pkg/vex/csaf.go @@ -2,7 +2,6 @@ package vex import ( "github.com/csaf-poc/csaf_distribution/v3/csaf" - "github.com/package-url/packageurl-go" "github.com/samber/lo" "github.com/aquasecurity/trivy/pkg/log" @@ -16,6 +15,11 @@ type CSAF struct { logger *log.Logger } +type relationship struct { + Product *purl.PackageURL + SubProducts []*purl.PackageURL +} + func newCSAF(advisory csaf.Advisory) VEX { return &CSAF{ advisory: advisory, @@ -23,36 +27,28 @@ func newCSAF(advisory csaf.Advisory) VEX { } } -func (v *CSAF) Filter(result *types.Result, _ *core.BOM) { - result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool { - found, ok := lo.Find(v.advisory.Vulnerabilities, func(item *csaf.Vulnerability) bool { - return string(*item.CVE) == vuln.VulnerabilityID - }) - if !ok { - return true - } +func (v *CSAF) Filter(result *types.Result, bom *core.BOM) { + filterVulnerabilities(result, bom, v.NotAffected) +} - if status := v.match(found, vuln.PkgIdentifier.PURL); status != "" { - result.ModifiedFindings = append(result.ModifiedFindings, - types.NewModifiedFinding(vuln, status, statement(found), "CSAF VEX")) - return false - } - return true +func (v *CSAF) NotAffected(vuln types.DetectedVulnerability, product, subProduct *core.Component) (types.ModifiedFinding, bool) { + found, ok := lo.Find(v.advisory.Vulnerabilities, func(item *csaf.Vulnerability) bool { + return string(*item.CVE) == vuln.VulnerabilityID }) -} + if !ok { + return types.ModifiedFinding{}, false + } -func (v *CSAF) match(vuln *csaf.Vulnerability, pkgURL *packageurl.PackageURL) types.FindingStatus { - if pkgURL == nil || vuln.ProductStatus == nil { - return "" + status := v.match(found, product, subProduct) + if status == "" { + return types.ModifiedFinding{}, false } + return types.NewModifiedFinding(vuln, status, v.statement(found), "CSAF VEX"), true +} - matchProduct := func(purls []*purl.PackageURL, pkgURL *packageurl.PackageURL) bool { - for _, p := range purls { - if p.Match(pkgURL) { - return true - } - } - return false +func (v *CSAF) match(vuln *csaf.Vulnerability, product, subProduct *core.Component) types.FindingStatus { + if product == nil || product.PkgIdentifier.PURL == nil || vuln.ProductStatus == nil { + return "" } productStatusMap := map[types.FindingStatus]csaf.Products{ @@ -60,83 +56,115 @@ func (v *CSAF) match(vuln *csaf.Vulnerability, pkgURL *packageurl.PackageURL) ty types.FindingStatusFixed: lo.FromPtr(vuln.ProductStatus.Fixed), } for status, productRange := range productStatusMap { - for _, product := range productRange { - if matchProduct(v.getProductPurls(lo.FromPtr(product)), pkgURL) { - v.logger.Info("Filtered out the detected vulnerability", - log.String("vulnerability-id", string(*vuln.CVE)), - log.String("status", string(status))) + for _, p := range productRange { + productID := lo.FromPtr(p) + logger := v.logger.With(log.String("vulnerability-id", string(*vuln.CVE)), + log.String("product-id", string(productID)), log.String("status", string(status))) + + // Check if the product is affected + if v.matchProduct(productID, product) { + logger.Info("Filtered out the detected vulnerability") return status } - for relationship, purls := range v.inspectProductRelationships(lo.FromPtr(product)) { - if matchProduct(purls, pkgURL) { - v.logger.Warn("Filtered out the detected vulnerability", - log.String("vulnerability-id", string(*vuln.CVE)), - log.String("status", string(status)), - log.String("relationship", string(relationship))) - return status - } + + // Check if the relationship between the product and the subcomponent is affected + if category, match := v.matchRelationship(productID, product, subProduct); match { + logger.Info("Filtered out the detected vulnerability", + log.String("relationship", string(category))) + return status } } } - return "" } -// getProductPurls returns a slice of PackageURLs associated to a given product -func (v *CSAF) getProductPurls(product csaf.ProductID) []*purl.PackageURL { - return purlsFromProductIdentificationHelpers(v.advisory.ProductTree.CollectProductIdentificationHelpers(product)) +func (v *CSAF) matchProduct(productID csaf.ProductID, product *core.Component) bool { + for _, productPURL := range v.productPURLs(productID) { + if productPURL.Match(product.PkgIdentifier.PURL) { + return true + } + } + return false +} + +func (v *CSAF) matchRelationship(fullProductID csaf.ProductID, product, subProduct *core.Component) ( + csaf.RelationshipCategory, bool) { + + for category, relationships := range v.inspectProductRelationships(fullProductID) { + for _, rel := range relationships { + if !rel.Product.Match(product.PkgIdentifier.PURL) { + continue + } + for _, subProductPURL := range rel.SubProducts { + if subProductPURL.Match(subProduct.PkgIdentifier.PURL) { + return category, true + } + } + } + } + return "", false +} + +// productPURLs returns a slice of PackageURLs associated to a given product +func (v *CSAF) productPURLs(product csaf.ProductID) []*purl.PackageURL { + return v.purlsFromProductIdentificationHelpers(v.advisory.ProductTree.CollectProductIdentificationHelpers(product)) } // inspectProductRelationships returns a map of PackageURLs associated to each relationship category // iterating over relationships looking for sub-products that might be part of the original product -func (v *CSAF) inspectProductRelationships(product csaf.ProductID) map[csaf.RelationshipCategory][]*purl.PackageURL { - subProductsMap := make(map[csaf.RelationshipCategory]csaf.Products) +func (v *CSAF) inspectProductRelationships(fullProductID csaf.ProductID) map[csaf.RelationshipCategory][]relationship { if v.advisory.ProductTree.RelationShips == nil { return nil } + relationships := make(map[csaf.RelationshipCategory][]relationship) for _, rel := range lo.FromPtr(v.advisory.ProductTree.RelationShips) { - if rel != nil { - relationship := lo.FromPtr(rel.Category) - switch relationship { - case csaf.CSAFRelationshipCategoryDefaultComponentOf, - csaf.CSAFRelationshipCategoryInstalledOn, - csaf.CSAFRelationshipCategoryInstalledWith: - if fpn := rel.FullProductName; fpn != nil && lo.FromPtr(fpn.ProductID) == product { - subProductsMap[relationship] = append(subProductsMap[relationship], rel.ProductReference) - } - } + if rel == nil || rel.FullProductName == nil { + continue + } else if lo.FromPtr(rel.FullProductName.ProductID) != fullProductID { + continue } - } - purlsMap := make(map[csaf.RelationshipCategory][]*purl.PackageURL) - for relationship, subProducts := range subProductsMap { - var helpers []*csaf.ProductIdentificationHelper - for _, subProductRef := range subProducts { - helpers = append(helpers, v.advisory.ProductTree.CollectProductIdentificationHelpers(lo.FromPtr(subProductRef))...) + category := lo.FromPtr(rel.Category) + switch category { + case csaf.CSAFRelationshipCategoryDefaultComponentOf, + csaf.CSAFRelationshipCategoryInstalledOn, + csaf.CSAFRelationshipCategoryInstalledWith: + + productID := lo.FromPtr(rel.RelatesToProductReference) + productPURLs := v.productPURLs(productID) + + subProductID := lo.FromPtr(rel.ProductReference) + subProductPURLs := v.productPURLs(subProductID) + + for _, productPURL := range productPURLs { + relationships[category] = append(relationships[category], relationship{ + Product: productPURL, + SubProducts: subProductPURLs, + }) + } } - purlsMap[relationship] = purlsFromProductIdentificationHelpers(helpers) } - return purlsMap + return relationships } -// purlsFromProductIdentificationHelpers returns a slice of PackageURLs given a slice of ProductIdentificationHelpers. -func purlsFromProductIdentificationHelpers(helpers []*csaf.ProductIdentificationHelper) []*purl.PackageURL { +// purlsFromProductIdentificationHelpers returns a slice of PURLs given a slice of ProductIdentificationHelpers. +func (v *CSAF) purlsFromProductIdentificationHelpers(helpers []*csaf.ProductIdentificationHelper) []*purl.PackageURL { return lo.FilterMap(helpers, func(helper *csaf.ProductIdentificationHelper, _ int) (*purl.PackageURL, bool) { if helper == nil || helper.PURL == nil { return nil, false } p, err := purl.FromString(string(*helper.PURL)) if err != nil { - log.Error("Invalid PURL", log.String("purl", string(*helper.PURL)), log.Err(err)) + v.logger.Error("Invalid PURL", log.String("purl", string(*helper.PURL)), log.Err(err)) return nil, false } return p, true }) } -func statement(vuln *csaf.Vulnerability) string { +func (v *CSAF) statement(vuln *csaf.Vulnerability) string { threat, ok := lo.Find(vuln.Threats, func(threat *csaf.Threat) bool { return lo.FromPtr(threat.Category) == csaf.CSAFThreatCategoryImpact }) diff --git a/pkg/vex/testdata/csaf-affected.json b/pkg/vex/testdata/csaf-affected.json deleted file mode 100644 index 56e9bb4d8a53..000000000000 --- a/pkg/vex/testdata/csaf-affected.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "document": { - "category": "csaf_vex", - "csaf_version": "2.0", - "notes": [ - { - "category": "summary", - "text": "Example Company VEX document. Unofficial content for demonstration purposes only.", - "title": "Author comment" - } - ], - "publisher": { - "category": "vendor", - "name": "Example Company ProductCERT", - "namespace": "https://psirt.example.com" - }, - "title": "Example VEX Document Use Case 1 - Affected", - "tracking": { - "current_release_date": "2022-03-03T11:00:00.000Z", - "generator": { - "date": "2022-03-03T11:00:00.000Z", - "engine": { - "name": "Secvisogram", - "version": "1.11.0" - } - }, - "id": "2022-EVD-UC-01-A-001", - "initial_release_date": "2022-03-03T11:00:00.000Z", - "revision_history": [ - { - "date": "2022-03-03T11:00:00.000Z", - "number": "1", - "summary": "Initial version." - } - ], - "status": "final", - "version": "1" - } - }, - "product_tree": { - "branches": [ - { - "branches": [ - { - "branches": [ - { - "category": "product_version", - "name": "1.0", - "product": { - "name": "Example Company DEF 1.0", - "product_id": "CSAFPID-0001", - "product_identification_helper": { - "purl": "pkg:maven/org.example.company/def@1.0" - } - } - } - ], - "category": "product_name", - "name": "DEF" - } - ], - "category": "vendor", - "name": "Example Company" - } - ] - }, - "vulnerabilities": [ - { - "cve": "CVE-2021-44228", - "notes": [ - { - "category": "description", - "text": "Apache Log4j2 2.0-beta9 through 2.15.0 (excluding security releases 2.12.2, 2.12.3, and 2.3.1) JNDI features used in configuration, log messages, and parameters do not protect against attacker controlled LDAP and other JNDI related endpoints. An attacker who can control log messages or log message parameters can execute arbitrary code loaded from LDAP servers when message lookup substitution is enabled. From log4j 2.15.0, this behavior has been disabled by default. From version 2.16.0 (along with 2.12.2, 2.12.3, and 2.3.1), this functionality has been completely removed. Note that this vulnerability is specific to log4j-core and does not affect log4net, log4cxx, or other Apache Logging Services projects.", - "title": "CVE description" - } - ], - "product_status": { - "known_affected": [ - "CSAFPID-0001" - ] - }, - "remediations": [ - { - "category": "vendor_fix", - "details": "Customers should update to version 1.1 of product DEF which fixes the issue.", - "product_ids": [ - "CSAFPID-0001" - ] - } - ] - } - ] -} diff --git a/pkg/vex/testdata/csaf-not-affected-sub-components.json b/pkg/vex/testdata/csaf-relationships.json similarity index 87% rename from pkg/vex/testdata/csaf-not-affected-sub-components.json rename to pkg/vex/testdata/csaf-relationships.json index a6fc9f088257..ab58d2bfe492 100644 --- a/pkg/vex/testdata/csaf-not-affected-sub-components.json +++ b/pkg/vex/testdata/csaf-relationships.json @@ -60,18 +60,18 @@ "branches": [ { "category": "product_version", - "name": "v1.24.2", + "name": "v0.24.2", "product": { - "name": "Kubernetes v1.24.2", - "product_id": "kubernetes-v1.24.2", + "name": "client-go v0.24.2", + "product_id": "client-go-v0.24.2", "product_identification_helper": { - "purl": "pkg:golang/k8s.io/kubernetes@v1.24.2" + "purl": "pkg:golang/k8s.io/client-go@0.24.2" } } } ], "category": "product_name", - "name": "kubernetes" + "name": "client-go" } ], "category": "vendor", @@ -80,11 +80,11 @@ ], "relationships": [ { - "product_reference": "kubernetes-v1.24.2", + "product_reference": "client-go-v0.24.2", "category": "default_component_of", "relates_to_product_reference": "argo-cd-2.9.3-2-amd64-debian-12", "full_product_name": { - "product_id": "argo-cd-2.9.3-2-amd64-debian-12-kubernetes", + "product_id": "argo-cd-2.9.3-2-amd64-debian-12-client-go", "name": "Argo CD uses kubernetes golang library" } } @@ -98,7 +98,7 @@ "date": "2024-01-04T17:17:25+01:00", "label": "vulnerable_code_cannot_be_controlled_by_adversary", "product_ids": [ - "argo-cd-2.9.3-2-amd64-debian-12-kubernetes" + "argo-cd-2.9.3-2-amd64-debian-12-client-go" ] } ], @@ -111,7 +111,7 @@ ], "product_status": { "known_not_affected": [ - "argo-cd-2.9.3-2-amd64-debian-12-kubernetes" + "argo-cd-2.9.3-2-amd64-debian-12-client-go" ] }, "threats": [ diff --git a/pkg/vex/testdata/csaf-not-affected.json b/pkg/vex/testdata/csaf.json similarity index 58% rename from pkg/vex/testdata/csaf-not-affected.json rename to pkg/vex/testdata/csaf.json index dce0b4a712d6..28389af24fcc 100644 --- a/pkg/vex/testdata/csaf-not-affected.json +++ b/pkg/vex/testdata/csaf.json @@ -14,7 +14,7 @@ "name": "Example Company ProductCERT", "namespace": "https://psirt.example.com" }, - "title": "AquaSecurity example VEX document", + "title": "Aqua Security example VEX document", "tracking": { "current_release_date": "2022-03-03T11:00:00.000Z", "generator": { @@ -45,47 +45,44 @@ "branches": [ { "category": "product_version", - "name": "2.6.0", + "name": "v0.24.2", "product": { - "name": "Spring Boot 2.6.0", - "product_id": "SPB-00260", + "name": "client-go v0.24.2", + "product_id": "client-go-v0.24.2", "product_identification_helper": { - "purl": "pkg:maven/org.springframework.boot/spring-boot@2.6.0" + "purl": "pkg:golang/k8s.io/client-go@0.24.2" } } } ], "category": "product_name", - "name": "Spring Boot" + "name": "client-go" } ], "category": "vendor", - "name": "Spring" + "name": "k8s.io" } ] }, "vulnerabilities": [ { - "cve": "CVE-2021-44228", + "cve": "CVE-2023-2727", "notes": [ { "category": "description", - "text": "Apache Log4j2 2.0-beta9 through 2.15.0 (excluding security releases 2.12.2, 2.12.3, and 2.3.1) JNDI features used in configuration, log messages, and parameters do not protect against attacker controlled LDAP and other JNDI related endpoints. An attacker who can control log messages or log message parameters can execute arbitrary code loaded from LDAP servers when message lookup substitution is enabled. From log4j 2.15.0, this behavior has been disabled by default. From version 2.16.0 (along with 2.12.2, 2.12.3, and 2.3.1), this functionality has been completely removed. Note that this vulnerability is specific to log4j-core and does not affect log4net, log4cxx, or other Apache Logging Services projects.", + "text": "Users may be able to launch containers using images that are restricted by ImagePolicyWebhook when using ephemeral containers. Kubernetes clusters are only affected if the ImagePolicyWebhook admission plugin is used together with ephemeral containers.", "title": "CVE description" } ], "product_status": { "known_not_affected": [ - "SPB-00260" + "client-go-v0.24.2" ] }, "threats": [ { "category": "impact", - "details": "Class with vulnerable code was removed before shipping.", - "product_ids": [ - "SPB-00260" - ] + "details": "The asset uses the component as a dependency in the code, but the vulnerability only affects Kubernetes clusters https://github.com/kubernetes/kubernetes/issues/118640" } ] } diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index c2b5ad10ea43..f4cf265997a0 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -116,10 +116,6 @@ func filterVulnerabilities(result *types.Result, bom *core.BOM, fn NotAffected) }) result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool { - if vuln.PkgIdentifier.PURL == nil { - return true - } - c, ok := components[vuln.PkgIdentifier.UID] if !ok { log.Error("Component not found", log.String("uid", vuln.PkgIdentifier.UID)) diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go index cce5b31d8517..d951b1795908 100644 --- a/pkg/vex/vex_test.go +++ b/pkg/vex/vex_test.go @@ -129,6 +129,43 @@ var ( }, }, } + argoComponent = core.Component{ + Type: core.TypeLibrary, + Name: "argo-cd", + Version: "2.9.3-2", + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "07", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeBitnami, + Name: "argo-cd", + Version: "2.9.3-2", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "amd64", + }, + { + Key: "distro", + Value: "debian-12", + }, + }, + }, + }, + } + clientGoComponent = core.Component{ + Type: core.TypeLibrary, + Name: "k8s.io/client-go", + Version: "0.24.2", + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "08", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: "k8s.io", + Name: "client-go", + Version: "0.24.2", + }, + }, + } vuln1 = types.DetectedVulnerability{ VulnerabilityID: "CVE-2021-44228", PkgName: springComponent.Name, @@ -165,6 +202,15 @@ var ( PURL: goTransitiveComponent.PkgIdentifier.PURL, }, } + vuln5 = types.DetectedVulnerability{ + VulnerabilityID: "CVE-2023-2727", + PkgName: clientGoComponent.Name, + InstalledVersion: clientGoComponent.Version, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: clientGoComponent.PkgIdentifier.UID, + PURL: clientGoComponent.PkgIdentifier.PURL, + }, + } ) func TestMain(m *testing.M) { @@ -390,90 +436,37 @@ func TestVEX_Filter(t *testing.T) { }, }, { - name: "CSAF (not affected vuln)", + name: "CSAF, not affected", fields: fields{ - filePath: "testdata/csaf-not-affected.json", + filePath: "testdata/csaf.json", }, args: args{ - vulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2021-44228", - PkgName: "spring-boot", - InstalledVersion: "2.6.0", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "org.springframework.boot", - Name: "spring-boot", - Version: "2.6.0", - }, - }, - }, - }, + bom: newTestBOM5(), + vulns: []types.DetectedVulnerability{vuln5}, }, want: []types.DetectedVulnerability{}, }, { - name: "CSAF (affected vuln)", + name: "CSAF with relationships, not affected", fields: fields{ - filePath: "testdata/csaf-affected.json", + filePath: "testdata/csaf-relationships.json", }, args: args{ - vulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2021-44228", - PkgName: "def", - InstalledVersion: "1.0", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "org.example.company", - Name: "def", - Version: "1.0", - }, - }, - }, - }, - }, - want: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2021-44228", - PkgName: "def", - InstalledVersion: "1.0", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "org.example.company", - Name: "def", - Version: "1.0", - }, - }, - }, + bom: newTestBOM5(), + vulns: []types.DetectedVulnerability{vuln5}, }, + want: []types.DetectedVulnerability{}, }, { - name: "CSAF (not affected vuln) with sub components", + name: "CSAF with relationships, affected", fields: fields{ - filePath: "testdata/csaf-not-affected-sub-components.json", + filePath: "testdata/csaf-relationships.json", }, args: args{ - vulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2023-2727", - PkgName: "kubernetes", - InstalledVersion: "v1.24.2", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeGolang, - Namespace: "k8s.io", - Name: "kubernetes", - Version: "v1.24.2", - }, - }, - }, - }, + bom: newTestBOM6(), + vulns: []types.DetectedVulnerability{vuln5}, }, - want: []types.DetectedVulnerability{}, + want: []types.DetectedVulnerability{vuln5}, }, { name: "unknown format", @@ -579,3 +572,26 @@ func newTestBOM4() *core.BOM { bom.AddRelationship(&goDirectComponent2, &goTransitiveComponent, core.RelationshipDependsOn) return bom } + +func newTestBOM5() *core.BOM { + // - oci:debian?tag=12 + // - pkg:bitnami/argo-cd@2.9.3-2?arch=amd64&distro=debian-12 + // - pkg:golang/k8s.io/client-go@0.24.2 + bom := core.NewBOM(core.Options{Parents: true}) + bom.AddComponent(&ociComponent) + bom.AddComponent(&argoComponent) + bom.AddComponent(&clientGoComponent) + bom.AddRelationship(&ociComponent, &argoComponent, core.RelationshipContains) + bom.AddRelationship(&argoComponent, &clientGoComponent, core.RelationshipDependsOn) + return bom +} + +func newTestBOM6() *core.BOM { + // - oci:debian?tag=12 + // - pkg:golang/k8s.io/client-go@0.24.2 + bom := core.NewBOM(core.Options{Parents: true}) + bom.AddComponent(&ociComponent) + bom.AddComponent(&clientGoComponent) + bom.AddRelationship(&ociComponent, &clientGoComponent, core.RelationshipContains) + return bom +} From ea3a124fc7162c30c7f1a59bdb28db0b3c8bb86d Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 29 May 2024 10:53:16 +0600 Subject: [PATCH 112/352] fix(python): add package name and version validation for `requirements.txt` files. (#6804) --- pkg/dependency/parser/python/pip/parse.go | 36 ++++++++-- .../parser/python/pip/parse_test.go | 66 +++++++++++-------- .../requirements_with_templating_engine.txt | 5 ++ 3 files changed, 77 insertions(+), 30 deletions(-) create mode 100644 pkg/dependency/parser/python/pip/testdata/requirements_with_templating_engine.txt diff --git a/pkg/dependency/parser/python/pip/parse.go b/pkg/dependency/parser/python/pip/parse.go index a849eb0cbea2..0d9e040f952b 100644 --- a/pkg/dependency/parser/python/pip/parse.go +++ b/pkg/dependency/parser/python/pip/parse.go @@ -10,7 +10,9 @@ import ( "golang.org/x/text/transform" "golang.org/x/xerrors" + version "github.com/aquasecurity/go-pep440-version" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -22,10 +24,14 @@ const ( endExtras string = "]" ) -type Parser struct{} +type Parser struct { + logger *log.Logger +} func NewParser() *Parser { - return &Parser{} + return &Parser{ + logger: log.WithPrefix("pip"), + } } func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { @@ -40,8 +46,8 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc var lineNumber int for scanner.Scan() { lineNumber++ - line := scanner.Text() - line = strings.ReplaceAll(line, " ", "") + text := scanner.Text() + line := strings.ReplaceAll(text, " ", "") line = strings.ReplaceAll(line, `\`, "") line = removeExtras(line) line = rStripByKey(line, commentMarker) @@ -51,6 +57,12 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc if len(s) != 2 { continue } + + if !isValidName(s[0]) || !isValidVersion(s[1]) { + p.logger.Debug("Invalid package name/version in requirements.txt.", log.String("line", text)) + continue + } + pkgs = append(pkgs, ftypes.Package{ Name: s[0], Version: s[1], @@ -83,3 +95,19 @@ func removeExtras(line string) string { } return line } + +func isValidName(name string) bool { + for _, r := range name { + // only characters [A-Z0-9._-] are allowed (case insensitive) + // cf. https://peps.python.org/pep-0508/#names + if !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '.' && r != '_' && r != '-' { + return false + } + } + return true +} + +func isValidVersion(ver string) bool { + _, err := version.Parse(ver) + return err == nil +} diff --git a/pkg/dependency/parser/python/pip/parse_test.go b/pkg/dependency/parser/python/pip/parse_test.go index d887205fb148..3a13c5272cc8 100644 --- a/pkg/dependency/parser/python/pip/parse_test.go +++ b/pkg/dependency/parser/python/pip/parse_test.go @@ -2,7 +2,6 @@ package pip import ( "os" - "path" "testing" "github.com/stretchr/testify/assert" @@ -12,57 +11,72 @@ import ( ) func TestParse(t *testing.T) { - vectors := []struct { - file string - want []ftypes.Package + tests := []struct { + name string + filePath string + want []ftypes.Package }{ { - file: "testdata/requirements_flask.txt", - want: requirementsFlask, + name: "happy path", + filePath: "testdata/requirements_flask.txt", + want: requirementsFlask, }, { - file: "testdata/requirements_comments.txt", - want: requirementsComments, + name: "happy path with comments", + filePath: "testdata/requirements_comments.txt", + want: requirementsComments, }, { - file: "testdata/requirements_spaces.txt", - want: requirementsSpaces, + name: "happy path with spaces", + filePath: "testdata/requirements_spaces.txt", + want: requirementsSpaces, }, { - file: "testdata/requirements_no_version.txt", - want: requirementsNoVersion, + name: "happy path with dependency without version", + filePath: "testdata/requirements_no_version.txt", + want: requirementsNoVersion, }, { - file: "testdata/requirements_operator.txt", - want: requirementsOperator, + name: "happy path with operator", + filePath: "testdata/requirements_operator.txt", + want: requirementsOperator, }, { - file: "testdata/requirements_hash.txt", - want: requirementsHash, + name: "happy path with hash", + filePath: "testdata/requirements_hash.txt", + want: requirementsHash, }, { - file: "testdata/requirements_hyphens.txt", - want: requirementsHyphens, + name: "happy path with hyphens", + filePath: "testdata/requirements_hyphens.txt", + want: requirementsHyphens, }, { - file: "testdata/requirement_exstras.txt", - want: requirementsExtras, + name: "happy path with exstras", + filePath: "testdata/requirement_exstras.txt", + want: requirementsExtras, }, { - file: "testdata/requirements_utf16le.txt", - want: requirementsUtf16le, + name: "happy path. File uses utf16le", + filePath: "testdata/requirements_utf16le.txt", + want: requirementsUtf16le, + }, + { + name: "happy path with templating engine", + filePath: "testdata/requirements_with_templating_engine.txt", + want: nil, }, } - for _, v := range vectors { - t.Run(path.Base(v.file), func(t *testing.T) { - f, err := os.Open(v.file) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.filePath) require.NoError(t, err) got, _, err := NewParser().Parse(f) require.NoError(t, err) - assert.Equal(t, v.want, got) + assert.Equal(t, tt.want, got) }) } } diff --git a/pkg/dependency/parser/python/pip/testdata/requirements_with_templating_engine.txt b/pkg/dependency/parser/python/pip/testdata/requirements_with_templating_engine.txt new file mode 100644 index 000000000000..c121e2517d9f --- /dev/null +++ b/pkg/dependency/parser/python/pip/testdata/requirements_with_templating_engine.txt @@ -0,0 +1,5 @@ +%ifcookiecutter.command_line_interface|lower=='click'-%} +foo|bar=='1.2.4' +foo{bar}==%% +foo,bar&&foobar==1.2.3 +foo=='invalid-version' \ No newline at end of file From b1e159b7fbb2dad616dfd5d9878e5c3d296b5fae Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 29 May 2024 09:02:00 +0400 Subject: [PATCH 113/352] ci: introduce Release Please for automated release management (#6795) Signed-off-by: knqyf263 Co-authored-by: simar7 <1254783+simar7@users.noreply.github.com> --- .github/workflows/release-please.yaml | 82 +++++++++++++++++++++++ .github/workflows/semantic-pr.yaml | 1 + .release-please-manifest.json | 1 + docs/community/maintainer/release-flow.md | 65 ++++++++++++++++++ mkdocs.yml | 1 + release-please-config.json | 11 +++ 6 files changed, 161 insertions(+) create mode 100644 .github/workflows/release-please.yaml create mode 100644 .release-please-manifest.json create mode 100644 docs/community/maintainer/release-flow.md create mode 100644 release-please-config.json diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml new file mode 100644 index 000000000000..ca7912f9068f --- /dev/null +++ b/.github/workflows/release-please.yaml @@ -0,0 +1,82 @@ +name: Release Please + +on: + push: + branches: + - main + - 'release/v*' + workflow_dispatch: + inputs: + version: + required: true + description: 'Release version without the "v" prefix (e.g., 0.51.0)' + type: string + +jobs: + release-please: + runs-on: ubuntu-latest + if: ${{ !startsWith(github.event.head_commit.message, 'release:') && !github.event.inputs.version }} + steps: + - name: Release Please + id: release + uses: googleapis/release-please-action@v4 + with: + token: ${{ secrets.PAT }} + target-branch: ${{ github.ref_name }} + + manual-release-please: + runs-on: ubuntu-latest + if: ${{ github.event.inputs.version }} + steps: + - name: Install Release Please CLI + run: npm install release-please -g + + - name: Release Please + run: | + release-please release-pr --repo-url=${{ github.server_url }}/${{ github.repository }} \ + --token=${{ secrets.PAT }} \ + --release-as=${{ github.event.inputs.version }} \ + --target-branch=${{ github.ref_name }} + + release-tag: + runs-on: ubuntu-latest + if: ${{ startsWith(github.event.head_commit.message, 'release:') }} + steps: + # Since skip-github-release is specified, the outputs of googleapis/release-please-action cannot be used. + # Therefore, we need to parse the version ourselves. + - name: Extract version and PR number from commit message + id: extract_info + shell: bash + run: | + echo "version=$( echo "${{ github.event.head_commit.message }}" | sed 's/^release: v\([0-9]\+\.[0-9]\+\.[0-9]\+\).*$/\1/' )" >> $GITHUB_OUTPUT + echo "pr_number=$( echo "${{ github.event.head_commit.message }}" | sed 's/.*(\#\([0-9]\+\)).*$/\1/' )" >> $GITHUB_OUTPUT + + - name: Tag release + if: ${{ steps.extract_info.outputs.version }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.PAT }} + script: | + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/tags/v${{ steps.extract_info.outputs.version }}`, + sha: context.sha + }); + + # Since skip-github-release is specified, googleapis/release-please-action doesn't delete the label from PR. + # This label prevents the subsequent PRs from being created. Therefore, we need to delete it ourselves. + # cf. https://github.com/googleapis/release-please?tab=readme-ov-file#release-please-bot-does-not-create-a-release-pr-why + - name: Remove the label from PR + if: ${{ steps.extract_info.outputs.pr_number }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.PAT }} + script: | + const prNumber = parseInt('${{ steps.extract_info.outputs.pr_number }}', 10); + github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + name: 'autorelease: pending' + }); diff --git a/.github/workflows/semantic-pr.yaml b/.github/workflows/semantic-pr.yaml index 5db6bd3a76a8..b136c9ae5ca9 100644 --- a/.github/workflows/semantic-pr.yaml +++ b/.github/workflows/semantic-pr.yaml @@ -28,6 +28,7 @@ jobs: ci chore revert + release BREAKING scopes: | diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 000000000000..c9b8977ea67b --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1 @@ +{".":"0.51.4"} diff --git a/docs/community/maintainer/release-flow.md b/docs/community/maintainer/release-flow.md new file mode 100644 index 000000000000..6a47469f12b8 --- /dev/null +++ b/docs/community/maintainer/release-flow.md @@ -0,0 +1,65 @@ +# Release Flow + +## Overview +Trivy adopts [conventional commit messages][conventional-commits], and [Release Please][release-please] automatically creates a [release PR](https://github.com/googleapis/release-please?tab=readme-ov-file#whats-a-release-pr) based on the messages of the merged commits. +This release PR is automatically updated every time a new commit is added to the release branch. + +If a commit has the prefix `feat:`, a PR is automatically created to increment the minor version, and if a commit has the prefix `fix:`, a PR is created to increment the patch version. +When the PR is merged, GitHub Actions automatically creates a version tag and the release is performed. +For detailed behavior, please refer to [the GitHub Actions configuration][workflows]. + +!!! note + Commits with prefixes like `chore` or `build` are not considered releasable, and no release PR is created. + To include such commits in a release, you need to either include commits with `feat` or `fix` prefixes or perform a manual release as described [below](#manual-release). + +## Flow +The release flow consists of the following main steps: + +1. Creating the release PR (automatically or manually) +1. Drafting the release notes +1. Merging the release PR +1. Updating the release notes + +### Automatic Release PR Creation +When a releasable commit (a commit with `feat` or `fix` prefix) is merged, a release PR is automatically created. +These Release PRs are kept up-to-date as additional work is merged. +When it's ready to tag a release, simply merge the release PR. +See the [Release Please documentation][release-please] for more information. + +The title of the PR will be in the format `release: v${version} [${branch}]` (e.g., `release: v0.51.0 [main]`). +The format of the PR title is important for identifying the release commit, so it should not be changed. + +The `release/vX.Y` release branches are also subject to automatic release PR creation for patch releases. +The PR title will be like `release: v0.51.1 [release/v0.51]`. + +### Manual Release PR Creation +If you want to release commits like `chore`, a release PR is not automatically created, so you need to manually trigger the creation of a release PR. +The [Release Please workflow](https://github.com/aquasecurity/trivy/actions/workflows/release-please.yaml) supports `workflow_dispatch` and can be triggered manually. +Click "Run workflow" in the top right corner and specify the release branch. +In Trivy, the following branches are the release branches. + +- `main` +- `release/vX.Y` (e.g. `release/v0.51`) + +Specify the release version (without the `v` prefix) and click "Run workflow" to create a release PR for the specified version. + +### Drafting the Release Notes +Next, create release notes for this version. +Draft a new post in GitHub Discussions, and maintainers edit these release notes (e.g., https://github.com/aquasecurity/trivy/discussions/6605). +Currently, the creation of this draft is done manually. +For patch version updates, this step can be skipped since they only involve bug fixes. + +### Merging the Release PR +Once the draft of the release notes is complete, merge the release PR. +When the PR is merged, a tag is automatically created, and [GoReleaser][goreleaser] releases binaries, container images, etc. + +### Updating the Release Notes +If the release completes without errors, a page for the release notes is created in GitHub Discussions (e.g., https://github.com/aquasecurity/trivy/discussions/6622). +Copy the draft release notes, adjust the formatting, and finalize the release notes. + +The release is now complete. + +[conventional-commits]: https://www.conventionalcommits.org/en/v1.0.0/ +[release-please]: https://github.com/googleapis/release-please +[goreleaser]: https://goreleaser.com/ +[workflows]: https://github.com/aquasecurity/trivy/tree/main/.github/workflows \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 6b5bd93249d5..664924cab59a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -199,6 +199,7 @@ nav: - Overview: community/contribute/checks/overview.md - Add Service Support: community/contribute/checks/service-support.md - Maintainer: + - Release Flow: community/maintainer/release-flow.md - Help Wanted: community/maintainer/help-wanted.md - Triage: community/maintainer/triage.md theme: diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 000000000000..2d6aaecd71dd --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "go", + "include-component-in-tag": false, + "bump-minor-pre-major": true, + "skip-github-release": true, + "pull-request-title-pattern": "release: v${version} [${branch}]", + "packages": { + ".": {} + } +} From 62de6f3feba6e4c56ad3922441d5b0f150c3d6b7 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 29 May 2024 07:40:05 +0200 Subject: [PATCH 114/352] fix: clean up golangci lint configuration (#6797) Signed-off-by: Matthieu MOREL --- .golangci.yaml | 92 ++++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 51 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 0c051ca4b145..83dae8d06e61 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,35 +1,9 @@ linters-settings: + dupl: + threshold: 100 errcheck: check-type-assertions: true check-blank: true - govet: - check-shadowing: false - gofmt: - simplify: false - rewrite-rules: - - pattern: 'interface{}' - replacement: 'any' - revive: - ignore-generated-header: true - gocyclo: - min-complexity: 20 - dupl: - threshold: 100 - goconst: - min-len: 3 - min-occurrences: 3 - misspell: - locale: US - ignore-words: - - licence - - optimise - gosec: - excludes: - - G101 - - G114 - - G204 - - G304 - - G402 gci: sections: - standard @@ -37,17 +11,9 @@ linters-settings: - prefix(github.com/aquasecurity/) - blank - dot - gomodguard: - blocked: - modules: - - github.com/hashicorp/go-version: - recommendations: - - github.com/aquasecurity/go-version - reason: "`aquasecurity/go-version` is designed for our use-cases" - - github.com/Masterminds/semver: - recommendations: - - github.com/aquasecurity/go-version - reason: "`aquasecurity/go-version` is designed for our use-cases" + goconst: + min-len: 3 + min-occurrences: 3 gocritic: disabled-checks: - appendAssign @@ -70,6 +36,42 @@ linters-settings: ruleguard: failOn: all rules: '${configDir}/misc/lint/rules.go' + gocyclo: + min-complexity: 20 + gofmt: + simplify: false + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' + gomodguard: + blocked: + modules: + - github.com/hashicorp/go-version: + recommendations: + - github.com/aquasecurity/go-version + reason: "`aquasecurity/go-version` is designed for our use-cases" + - github.com/Masterminds/semver: + recommendations: + - github.com/aquasecurity/go-version + reason: "`aquasecurity/go-version` is designed for our use-cases" + gosec: + excludes: + - G101 + - G114 + - G204 + - G304 + - G402 + govet: + check-shadowing: false + misspell: + locale: US + ignore-words: + - behaviour + - licence + - optimise + - simmilar + revive: + ignore-generated-header: true testifylint: enable-all: true disable: @@ -111,7 +113,6 @@ issues: linters: - goconst - gosec - - misspell - unused - path: ".*_test.go$" linters: @@ -129,16 +130,5 @@ issues: linters: - gocritic text: "importShadow:" - - linters: - - errcheck - text: "Close` is not checked" - - linters: - - errcheck - text: "os.*` is not checked" - - linters: - - golint - text: "a blank import should be only in a main or test package" - exclude: - - "should have a package comment, unless it's in another file for this package" exclude-use-default: false max-same-issues: 0 From dca50294e895c1cc7d3d3611727599c81140c65a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 May 2024 09:42:15 +0400 Subject: [PATCH 115/352] chore(deps): bump the common group with 3 updates (#6789) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 11 ++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index e2150c80aab5..48cdc4df5515 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 - github.com/BurntSushi/toml v1.3.2 + github.com/BurntSushi/toml v1.4.0 github.com/CycloneDX/cyclonedx-go v0.8.0 github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible github.com/Masterminds/sprig/v3 v3.2.3 @@ -62,7 +62,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.6 github.com/hashicorp/go-uuid v1.0.3 - github.com/hashicorp/go-version v1.6.0 + github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/hc-install v0.7.0 github.com/hashicorp/hcl/v2 v2.20.1 @@ -128,7 +128,7 @@ require ( golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.15.0 + helm.sh/helm/v3 v3.15.1 k8s.io/api v0.30.1 k8s.io/utils v0.0.0-20231127182322-b307cd553661 modernc.org/sqlite v1.29.10 diff --git a/go.sum b/go.sum index 860e980a9250..74c7721f2ed1 100644 --- a/go.sum +++ b/go.sum @@ -651,8 +651,8 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CycloneDX/cyclonedx-go v0.8.0 h1:FyWVj6x6hoJrui5uRQdYZcSievw3Z32Z88uYzG/0D6M= github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= @@ -1561,8 +1561,9 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -3080,8 +3081,8 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.15.0 h1:gcLxHeFp0Hfo7lYi6KIZ84ZyvlAnfFRSJ8lTL3zvG5U= -helm.sh/helm/v3 v3.15.0/go.mod h1:fvfoRcB8UKRUV5jrIfOTaN/pG1TPhuqSb56fjYdTKXg= +helm.sh/helm/v3 v3.15.1 h1:22ztacHz4gMqhXNqCQ9NAg6BFWoRUryNLvnkz6OVyw0= +helm.sh/helm/v3 v3.15.1/go.mod h1:fvfoRcB8UKRUV5jrIfOTaN/pG1TPhuqSb56fjYdTKXg= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From c4741b021ed76020a6c3872a9fd7b879d220b6b5 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 29 May 2024 11:11:02 +0400 Subject: [PATCH 116/352] ci: replace PAT with ORG_REPO_TOKEN (#6806) Signed-off-by: knqyf263 --- .github/workflows/release-please.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml index ca7912f9068f..290cdce1c4ce 100644 --- a/.github/workflows/release-please.yaml +++ b/.github/workflows/release-please.yaml @@ -21,7 +21,7 @@ jobs: id: release uses: googleapis/release-please-action@v4 with: - token: ${{ secrets.PAT }} + token: ${{ secrets.ORG_REPO_TOKEN }} target-branch: ${{ github.ref_name }} manual-release-please: @@ -34,7 +34,7 @@ jobs: - name: Release Please run: | release-please release-pr --repo-url=${{ github.server_url }}/${{ github.repository }} \ - --token=${{ secrets.PAT }} \ + --token=${{ secrets.ORG_REPO_TOKEN }} \ --release-as=${{ github.event.inputs.version }} \ --target-branch=${{ github.ref_name }} @@ -55,7 +55,7 @@ jobs: if: ${{ steps.extract_info.outputs.version }} uses: actions/github-script@v7 with: - github-token: ${{ secrets.PAT }} + github-token: ${{ secrets.ORG_REPO_TOKEN }} script: | await github.rest.git.createRef({ owner: context.repo.owner, @@ -71,7 +71,7 @@ jobs: if: ${{ steps.extract_info.outputs.pr_number }} uses: actions/github-script@v7 with: - github-token: ${{ secrets.PAT }} + github-token: ${{ secrets.ORG_REPO_TOKEN }} script: | const prNumber = parseInt('${{ steps.extract_info.outputs.pr_number }}', 10); github.rest.issues.removeLabel({ From fb3c7560333a02bf832772302769ca1cfe9641be Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 29 May 2024 13:39:55 +0400 Subject: [PATCH 117/352] ci: set initial version to v0.51.1 (#6810) Signed-off-by: knqyf263 --- .release-please-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c9b8977ea67b..74ac33a5e6e7 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{".":"0.51.4"} +{".":"0.51.1"} From aa59489fa8558e1b6b7e043d07a9193058b444da Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 29 May 2024 12:35:09 +0200 Subject: [PATCH 118/352] ci(deps): use modules instead of incompatible version (#6805) Signed-off-by: Matthieu MOREL --- go.mod | 6 ++---- go.sum | 4 ---- pkg/fanal/image/image_test.go | 2 +- pkg/rpc/retry.go | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 48cdc4df5515..a0275a82c98e 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/aws/smithy-go v1.20.2 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 - github.com/cenkalti/backoff v2.2.1+incompatible + github.com/cenkalti/backoff/v4 v4.3.0 github.com/cheggaaa/pb/v3 v3.1.5 github.com/containerd/containerd v1.7.17 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 @@ -53,7 +53,7 @@ require ( github.com/go-openapi/runtime v0.28.0 github.com/go-openapi/strfmt v0.23.0 github.com/go-redis/redis/v8 v8.11.5 - github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/go-containerregistry v0.19.1 github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/uuid v1.6.0 @@ -220,7 +220,6 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/briandowns/spinner v1.23.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.7 // indirect @@ -275,7 +274,6 @@ require ( github.com/gofrs/uuid v4.3.1+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect diff --git a/go.sum b/go.sum index 74c7721f2ed1..f5e2ad6aadfa 100644 --- a/go.sum +++ b/go.sum @@ -939,8 +939,6 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXe github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -1350,8 +1348,6 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= diff --git a/pkg/fanal/image/image_test.go b/pkg/fanal/image/image_test.go index 283a4ee83ad7..afcb2b978dbf 100644 --- a/pkg/fanal/image/image_test.go +++ b/pkg/fanal/image/image_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/golang-jwt/jwt" + "github.com/golang-jwt/jwt/v5" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/rpc/retry.go b/pkg/rpc/retry.go index 31a7760cb770..01fe13788ef4 100644 --- a/pkg/rpc/retry.go +++ b/pkg/rpc/retry.go @@ -3,7 +3,7 @@ package rpc import ( "time" - "github.com/cenkalti/backoff" + "github.com/cenkalti/backoff/v4" "github.com/twitchtv/twirp" "github.com/aquasecurity/trivy/pkg/log" From f92ea096856c7c262b05bd4d31c62689ebafac82 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 30 May 2024 11:36:57 +0600 Subject: [PATCH 119/352] fix(sbom): fix panic for `convert` mode when scanning json file derived from sbom file (#6808) --- pkg/sbom/io/encode.go | 11 ++- pkg/sbom/io/encode_test.go | 154 +++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 2 deletions(-) diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index db9052f033dd..9672f1648dc6 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -83,8 +83,15 @@ func (e *Encoder) rootComponent(r types.Report) (*core.Component, error) { root.Type = core.TypeFilesystem case artifact.TypeRepository: root.Type = core.TypeRepository - case artifact.TypeCycloneDX: - return r.BOM.Root(), nil + case artifact.TypeCycloneDX, artifact.TypeSPDX: + // When we scan SBOM file + if r.BOM != nil { + return r.BOM.Root(), nil + } + // When we scan a `json` file (meaning a file in `json` format) which was created from the SBOM file. + // e.g. for use in `convert` mode. + // See https://github.com/aquasecurity/trivy/issues/6780 + root.Type = core.TypeFilesystem } if r.Metadata.Size != 0 { diff --git a/pkg/sbom/io/encode_test.go b/pkg/sbom/io/encode_test.go index c6cc450da832..d165b64c3e80 100644 --- a/pkg/sbom/io/encode_test.go +++ b/pkg/sbom/io/encode_test.go @@ -535,6 +535,97 @@ func TestEncoder_Encode(t *testing.T) { }, wantVulns: make(map[uuid.UUID][]core.Vulnerability), }, + { + name: "SBOM file", + report: types.Report{ + SchemaVersion: 2, + ArtifactName: "report.cdx.json", + ArtifactType: artifact.TypeCycloneDX, + Results: []types.Result{ + { + Target: "Java", + Type: ftypes.Jar, + Class: types.ClassLangPkg, + Packages: []ftypes.Package{ + { + ID: "org.apache.logging.log4j:log4j-core:2.23.1", + Name: "org.apache.logging.log4j:log4j-core", + Version: "2.23.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.logging.log4j", + Name: "log4j-core", + Version: "2.23.1", + }, + }, + FilePath: "log4j-core-2.23.1.jar", + }, + }, + }, + }, + BOM: newTestBOM(t), + }, + wantComponents: map[uuid.UUID]*core.Component{ + uuid.MustParse("2ff14136-e09f-4df9-80ea-000000000001"): appComponent, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): libComponent, + }, + wantRels: map[uuid.UUID][]core.Relationship{ + uuid.MustParse("2ff14136-e09f-4df9-80ea-000000000001"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"), + Type: core.RelationshipContains, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): nil, + }, + wantVulns: make(map[uuid.UUID][]core.Vulnerability), + }, + { + name: "json file created from SBOM file (BOM is empty)", + report: types.Report{ + SchemaVersion: 2, + ArtifactName: "report.cdx.json", + ArtifactType: artifact.TypeCycloneDX, + Results: []types.Result{ + { + Target: "Java", + Type: ftypes.Jar, + Class: types.ClassLangPkg, + Packages: []ftypes.Package{ + { + ID: "org.apache.logging.log4j:log4j-core:2.23.1", + Name: "org.apache.logging.log4j:log4j-core", + Version: "2.23.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.logging.log4j", + Name: "log4j-core", + Version: "2.23.1", + }, + }, + FilePath: "log4j-core-2.23.1.jar", + }, + }, + }, + }, + }, + wantComponents: map[uuid.UUID]*core.Component{ + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): fsComponent, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): libComponent, + }, + wantRels: map[uuid.UUID][]core.Relationship{ + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"), + Type: core.RelationshipContains, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): nil, + }, + wantVulns: make(map[uuid.UUID][]core.Vulnerability), + }, { name: "invalid digest", report: types.Report{ @@ -580,3 +671,66 @@ func TestEncoder_Encode(t *testing.T) { }) } } + +var ( + appComponent = &core.Component{ + Root: true, + Type: core.TypeApplication, + Name: "log4j-core-2.23.1.jar", + } + fsComponent = &core.Component{ + Root: true, + Type: core.TypeFilesystem, + Name: "report.cdx.json", + PkgIdentifier: ftypes.PkgIdentifier{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", + }, + Properties: core.Properties{ + { + Name: "SchemaVersion", + Value: "2", + }, + }, + } + libComponent = &core.Component{ + Type: core.TypeLibrary, + Name: "log4j-core", + Group: "org.apache.logging.log4j", + Version: "2.23.1", + PkgIdentifier: ftypes.PkgIdentifier{ + BOMRef: "pkg:maven/org.apache.logging.log4j/log4j-core@2.23.1", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.logging.log4j", + Name: "log4j-core", + Version: "2.23.1", + }, + }, + Files: []core.File{ + { + Path: "log4j-core-2.23.1.jar", + }, + }, + Properties: core.Properties{ + { + Name: "FilePath", + Value: "log4j-core-2.23.1.jar", + }, + { + Name: "PkgID", + Value: "org.apache.logging.log4j:log4j-core:2.23.1", + }, + { + Name: "PkgType", + Value: "jar", + }, + }, + } +) + +func newTestBOM(t *testing.T) *core.BOM { + uuid.SetFakeUUID(t, "2ff14136-e09f-4df9-80ea-%012d") + bom := core.NewBOM(core.Options{}) + bom.AddComponent(appComponent) + return bom +} From 5b0bc5823e82c511f6d9f3cce1abbe3d900a51a8 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 30 May 2024 11:11:13 +0400 Subject: [PATCH 120/352] chore: improve error message for image not found (#6822) Signed-off-by: knqyf263 --- pkg/fanal/image/image.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fanal/image/image.go b/pkg/fanal/image/image.go index 2a8862a89ab5..ea2df2c9ddc0 100644 --- a/pkg/fanal/image/image.go +++ b/pkg/fanal/image/image.go @@ -55,7 +55,7 @@ func NewContainerImage(ctx context.Context, imageName string, opt types.ImageOpt errs = multierror.Append(errs, err) } - return nil, func() {}, errs + return nil, func() {}, xerrors.Errorf("unable to find the specified image %q in %q: %w", imageName, opt.ImageSources, errs) } func ID(img v1.Image) (string, error) { From 2f05418e60a729c8b1b53ac39c72e2b4ed462aa2 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 30 May 2024 11:43:26 +0400 Subject: [PATCH 121/352] docs: add more workarounds for out-of-disk (#6821) Signed-off-by: knqyf263 --- docs/docs/references/troubleshooting.md | 32 +++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/docs/references/troubleshooting.md b/docs/docs/references/troubleshooting.md index fcdb97bb8a61..7d2c3258aa2c 100644 --- a/docs/docs/references/troubleshooting.md +++ b/docs/docs/references/troubleshooting.md @@ -154,14 +154,42 @@ $ TMPDIR=/my/custom/path trivy repo ... write /tmp/fanal-3323732142: no space left on device ``` -Trivy uses the `/tmp` directory during image scan, if the image is large or `/tmp` is of insufficient size then the scan fails You can set the `TMPDIR` environment variable to use redirect trivy to use a directory with adequate storage. +Trivy uses a temporary directory during image scans. +The directory path would be determined as follows: -Try: +- On Unix systems: Use `$TMPDIR` if non-empty, else `/tmp`. +- On Windows: Uses GetTempPath, returning the first non-empty value from `%TMP%`, `%TEMP%`, `%USERPROFILE%`, or the Windows directory. + +See [this documentation](https://golang.org/pkg/os/#TempDir) for more details. + +If the image is large or the temporary directory has insufficient space, the scan will fail. +You can configure the directory path to redirect Trivy to a directory with adequate storage. +On Unix systems, you can set the `$TMPDIR` environment variable. ``` $ TMPDIR=/my/custom/path trivy image ... ``` +When scanning images from a container registry, Trivy processes each layer by streaming, loading only the necessary files for the scan into memory and discarding unnecessary files. +If a layer contains large files that are necessary for the scan (such as JAR files or binary files), Trivy saves them to a temporary directory (e.g. $TMPDIR) on local storage to avoid increased memory consumption. +Although these files are deleted after the scan is complete, they can temporarily increase disk consumption and potentially exhaust storage. +In such cases, there are currently three workarounds: + +1. Use a temporary directory with sufficient capacity + + This is the same as explained above. + +2. Specify a small value for `--parallel` + + By default, multiple layers are processed in parallel. + If each layer contains large files, disk space may be consumed rapidly. + By specifying a small value such as `--parallel 1`, parallelism is reduced, which can mitigate the issue. + +3. Specify `--skip-files` or `--skip-dirs` + + If the container image contains large files that do not need to be scanned, you can skip their processing by specifying --skip-files or --skip-dirs. + For more details, please refer to [this documentation](../configuration/skipping.md). + ## DB ### Old DB schema From 29615be85e8bfeaf5a0cd51829b1898c55fa4274 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 30 May 2024 14:29:29 +0600 Subject: [PATCH 122/352] feat(python): add license support for `requirement.txt` files (#6782) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- docs/docs/coverage/language/python.md | 35 ++- pkg/fanal/analyzer/language/python/pip/pip.go | 198 +++++++++++++++- .../analyzer/language/python/pip/pip_test.go | 224 ++++++++++++++++-- .../requirements.txt} | 0 .../pip/testdata/{ => happy}/requirements.txt | 0 .../Flask-2.0.0.dist-info/METADATA | 124 ++++++++++ .../click-8.0.0.dist-info/METADATA | 109 +++++++++ .../Flask-2.0.0.dist-info/METADATA | 124 ++++++++++ .../click-8.0.0.dist-info/METADATA | 109 +++++++++ pkg/fanal/artifact/local/fs_test.go | 128 +++++----- 10 files changed, 948 insertions(+), 103 deletions(-) rename pkg/fanal/analyzer/language/python/pip/testdata/{not-related.txt => empty/requirements.txt} (100%) rename pkg/fanal/analyzer/language/python/pip/testdata/{ => happy}/requirements.txt (100%) create mode 100644 pkg/fanal/analyzer/language/python/pip/testdata/libs/common-dir/lib/site-packages/Flask-2.0.0.dist-info/METADATA create mode 100644 pkg/fanal/analyzer/language/python/pip/testdata/libs/common-dir/lib/site-packages/click-8.0.0.dist-info/METADATA create mode 100644 pkg/fanal/analyzer/language/python/pip/testdata/libs/python-dir/lib/python3.10/site-packages/Flask-2.0.0.dist-info/METADATA create mode 100644 pkg/fanal/analyzer/language/python/pip/testdata/libs/python-dir/lib/python3.10/site-packages/click-8.0.0.dist-info/METADATA diff --git a/docs/docs/coverage/language/python.md b/docs/docs/coverage/language/python.md index ce792842b978..c4f6b6d83e86 100644 --- a/docs/docs/coverage/language/python.md +++ b/docs/docs/coverage/language/python.md @@ -3,20 +3,20 @@ Trivy supports three types of Python package managers: `pip`, `Pipenv` and `Poetry`. The following scanners are supported for package managers. -| Package manager | SBOM | Vulnerability | License | -| --------------- | :---: | :-----------: | :-----: | -| pip | ✓ | ✓ | - | -| Pipenv | ✓ | ✓ | - | -| Poetry | ✓ | ✓ | - | +| Package manager | SBOM | Vulnerability | License | +|-----------------|:----:|:-------------:|:-------:| +| pip | ✓ | ✓ | ✓ | +| Pipenv | ✓ | ✓ | - | +| Poetry | ✓ | ✓ | - | In addition, Trivy supports three formats of Python packages: `egg`, `wheel` and `conda`. The following scanners are supported for Python packages. -| Packaging | SBOM | Vulnerability | License | -| --------- | :---: | :-----------: | :-----: | -| Egg | ✓ | ✓ | ✓ | -| Wheel | ✓ | ✓ | ✓ | -| Conda | ✓ | - | - | +| Packaging | SBOM | Vulnerability | License | +|-----------|:----:|:-------------:|:-------:| +| Egg | ✓ | ✓ | ✓ | +| Wheel | ✓ | ✓ | ✓ | +| Conda | ✓ | - | - | The following table provides an outline of the features Trivy offers. @@ -40,6 +40,8 @@ See [here](./index.md) for the detail. Trivy parses your files generated by package managers in filesystem/repository scanning. ### pip + +#### Dependency detection Trivy only parses [version specifiers](https://packaging.python.org/en/latest/specifications/version-specifiers/#id4) with `==` comparison operator and without `.*`. To convert unsupported version specifiers - use the `pip freeze` command. @@ -91,7 +93,16 @@ urllib3==1.26.15 `requirements.txt` files don't contain information about dependencies used for development. Trivy could detect vulnerabilities on the development packages, which not affect your production environment. -License detection is not supported for `pip`. +#### License detection + +`requirements.txt` files don't contain information about licenses. +Therefore, Trivy checks `METADATA` files from `lib/site-packages` directory. + +Trivy uses 3 ways to detect `site-packages` directory: + +- Checks `VIRTUAL_ENV` environment variable. +- Detects path to `python`[^1] binary and checks `../lib/pythonX.Y/site-packages` directory. +- Detects path to `python`[^1] binary and checks `../../lib/site-packages` directory. ### Pipenv Trivy parses `Pipfile.lock`. @@ -116,4 +127,6 @@ Trivy looks for `*.egg-info`, `*.egg-info/PKG-INFO`, `*.egg` and `EGG-INFO/PKG-I ### Wheel Trivy looks for `.dist-info/META-DATA` to identify Python packages. +[^1]: Trivy checks `python`, `python3`, `python2` and `python.exe` file names. + [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies diff --git a/pkg/fanal/analyzer/language/python/pip/pip.go b/pkg/fanal/analyzer/language/python/pip/pip.go index 380fcbf4936c..85b20a79465c 100644 --- a/pkg/fanal/analyzer/language/python/pip/pip.go +++ b/pkg/fanal/analyzer/language/python/pip/pip.go @@ -2,31 +2,92 @@ package pip import ( "context" + "fmt" + "io" + "io/fs" "os" + "os/exec" "path/filepath" + "sort" + "strings" + "github.com/samber/lo" "golang.org/x/xerrors" + goversion "github.com/aquasecurity/go-version/pkg/version" + "github.com/aquasecurity/trivy/pkg/dependency/parser/python/packaging" "github.com/aquasecurity/trivy/pkg/dependency/parser/python/pip" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) func init() { - analyzer.RegisterAnalyzer(&pipLibraryAnalyzer{}) + analyzer.RegisterPostAnalyzer(analyzer.TypePip, newPipLibraryAnalyzer) } const version = 1 -type pipLibraryAnalyzer struct{} +var pythonExecNames = []string{ + "python3", + "python", + "python2", + "python.exe", +} + +type pipLibraryAnalyzer struct { + logger *log.Logger + metadataParser packaging.Parser +} + +func newPipLibraryAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return pipLibraryAnalyzer{ + logger: log.WithPrefix("pip"), + metadataParser: *packaging.NewParser(), + }, nil +} + +func (a pipLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application -func (a pipLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { - res, err := language.Analyze(types.Pip, input.FilePath, input.Content, pip.NewParser()) + sitePackagesDir, err := a.pythonSitePackagesDir() if err != nil { - return nil, xerrors.Errorf("unable to parse requirements.txt: %w", err) + a.logger.Warn("Unable to find python `site-packages` directory. License detection is skipped.", log.Err(err)) + } + + // We only saved the `requirements.txt` files + required := func(_ string, _ fs.DirEntry) bool { + return true + } + + if err = fsutils.WalkDir(input.FS, ".", required, func(pathPath string, d fs.DirEntry, r io.Reader) error { + app, err := language.Parse(types.Pip, pathPath, r, pip.NewParser()) + if err != nil { + return xerrors.Errorf("unable to parse requirements.txt: %w", err) + } + + if app == nil { + return nil + } + + // Fill licenses + if sitePackagesDir != "" { + for i := range app.Packages { + app.Packages[i].Licenses = a.pkgLicense(app.Packages[i].Name, app.Packages[i].Version, sitePackagesDir) + } + } + + apps = append(apps, *app) + return nil + }); err != nil { + return nil, xerrors.Errorf("pip walt error: %w", err) } - return res, nil + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil } func (a pipLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { @@ -41,3 +102,128 @@ func (a pipLibraryAnalyzer) Type() analyzer.Type { func (a pipLibraryAnalyzer) Version() int { return version } + +// pkgLicense parses `METADATA` pkg file to look for licenses +func (a pipLibraryAnalyzer) pkgLicense(pkgName, pkgVer, spDir string) []string { + // METADATA path is `**/site-packages/-.dist-info/METADATA` + pkgDir := fmt.Sprintf("%s-%s.dist-info", pkgName, pkgVer) + metadataPath := filepath.Join(spDir, pkgDir, "METADATA") + metadataFile, err := os.Open(metadataPath) + if os.IsNotExist(err) { + a.logger.Debug("No package metadata found", log.String("site-packages", pkgDir), + log.String("name", pkgName), log.String("version", pkgVer)) + return nil + } + + metadataPkg, _, err := a.metadataParser.Parse(metadataFile) + if err != nil { + a.logger.Warn("Unable to parse METADATA file", log.String("path", metadataPath), log.Err(err)) + return nil + } + + // METADATA file contains info about only 1 package + // cf. https://github.com/aquasecurity/trivy/blob/e66dbb935764908f0b2b9a55cbfe6c107f101a31/pkg/dependency/parser/python/packaging/parse.go#L86-L92 + return metadataPkg[0].Licenses +} + +// pythonSitePackagesDir returns path to site-packages dir +func (a pipLibraryAnalyzer) pythonSitePackagesDir() (string, error) { + // check VIRTUAL_ENV first + if venv := os.Getenv("VIRTUAL_ENV"); venv != "" { + libDir := filepath.Join(venv, "lib") + if _, err := os.Stat(libDir); os.IsNotExist(err) { + return "", xerrors.Errorf("unable to detect `lib` dir for %q venv: %w", venv, err) + } + + spDir, err := a.findSitePackagesDir(libDir) + if err != nil { + return "", xerrors.Errorf("unable to detect `site-packages` dir for %q venv: %w", spDir, err) + } else if spDir != "" { + return spDir, nil + } + } + + // Find path to Python executable + pythonExecPath, err := pythonExecutablePath() + if err != nil { + return "", err + } + pythonExecDir := filepath.Dir(pythonExecPath) + + // Search for a directory starting with "python" in the lib directory + libDir := filepath.Join(pythonExecDir, "..", "lib") + spDir, err := a.findSitePackagesDir(libDir) + if err != nil { + return "", xerrors.Errorf("unable to detect `site-packages` dir for %q: %w", pythonExecPath, err) + } else if spDir != "" { + return spDir, nil + } + + // Try another common pattern if the Python library directory is not found + spDir = filepath.Join(pythonExecDir, "..", "..", "lib", "site-packages") + if fsutils.DirExists(spDir) { + return spDir, nil + } + + return "", xerrors.Errorf("site-packages directory not found") +} + +// pythonExecutablePath returns path to Python executable +func pythonExecutablePath() (string, error) { + for _, execName := range pythonExecNames { + // Get the absolute path of the python command + pythonPath, err := exec.LookPath(execName) + if err != nil { + continue + } + return pythonPath, nil + } + return "", xerrors.Errorf("unable to find path to Python executable") +} + +// findSitePackagesDir finds `site-packages` dir in `lib` dir +func (a pipLibraryAnalyzer) findSitePackagesDir(libDir string) (string, error) { + entries, err := os.ReadDir(libDir) + if err != nil { + if !os.IsNotExist(err) { + return "", xerrors.Errorf("failed to read lib directory: %w", err) + } + return "", nil + } + + // Find python dir which contains `site-packages` dir + // First check for newer versions + pythonDirs := a.sortPythonDirs(entries) + for i := len(pythonDirs) - 1; i >= 0; i-- { + dir := filepath.Join(libDir, pythonDirs[i], "site-packages") + if fsutils.DirExists(dir) { + return dir, nil + } + } + return "", nil +} + +// sortPythonDirs finds dirs starting with `python` and sorts them +// e.g. python2.7 => python3.9 => python3.11 +func (a pipLibraryAnalyzer) sortPythonDirs(entries []os.DirEntry) []string { + var pythonVers []goversion.Version + for _, entry := range entries { + // Found a directory starting with "python", assume it's the Python library directory + if entry.IsDir() && strings.HasPrefix(entry.Name(), "python") { + ver := strings.TrimPrefix(entry.Name(), "python") + v, err := goversion.Parse(ver) + if err != nil { + a.logger.Debug("Unable to parse version from Python dir name", log.String("dir", entry.Name()), log.Err(err)) + continue + } + pythonVers = append(pythonVers, v) + } + } + + // Sort Python version + sort.Sort(goversion.Collection(pythonVers)) + + return lo.Map(pythonVers, func(v goversion.Version, _ int) string { + return "python" + v.String() + }) +} diff --git a/pkg/fanal/analyzer/language/python/pip/pip_test.go b/pkg/fanal/analyzer/language/python/pip/pip_test.go index 62fd13953f9b..e3041d0079c1 100644 --- a/pkg/fanal/analyzer/language/python/pip/pip_test.go +++ b/pkg/fanal/analyzer/language/python/pip/pip_test.go @@ -3,6 +3,9 @@ package pip import ( "context" "os" + "path/filepath" + "runtime" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -10,23 +13,92 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" ) func Test_pipAnalyzer_Analyze(t *testing.T) { + resultWithLicenses := &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Pip, + FilePath: "requirements.txt", + Packages: types.Packages{ + { + Name: "click", + Version: "8.0.0", + Locations: []types.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, + Licenses: []string{ + "BSD License", + }, + }, + { + Name: "Flask", + Version: "2.0.0", + Locations: []types.Location{ + { + StartLine: 2, + EndLine: 2, + }, + }, + Licenses: []string{ + "BSD License", + }, + }, + { + Name: "itsdangerous", + Version: "2.0.0", + Locations: []types.Location{ + { + StartLine: 3, + EndLine: 3, + }, + }, + }, + }, + }, + }, + } + tests := []struct { - name string - inputFile string - want *analyzer.AnalysisResult - wantErr string + name string + dir string + venv string + pythonExecDir string + want *analyzer.AnalysisResult + wantErr string }{ { - name: "happy path", - inputFile: "testdata/requirements.txt", + name: "happy path with licenses from venv", + dir: filepath.Join("testdata", "happy"), + venv: filepath.Join("testdata", "libs", "python-dir"), + pythonExecDir: filepath.Join("testdata", "libs", "python-dir", "bin"), + want: resultWithLicenses, + }, + { + name: "happy path with licenses from python dir", + dir: filepath.Join("testdata", "happy"), + pythonExecDir: filepath.Join("testdata", "libs", "python-dir", "bin"), + want: resultWithLicenses, + }, + { + name: "happy path with licenses from common dir", + dir: filepath.Join("testdata", "happy"), + pythonExecDir: filepath.Join("testdata", "libs", "common-dir", "foo", "bar"), + want: resultWithLicenses, + }, + { + name: "happy path without licenses", + dir: filepath.Join("testdata", "happy"), want: &analyzer.AnalysisResult{ Applications: []types.Application{ { Type: types.Pip, - FilePath: "testdata/requirements.txt", + FilePath: "requirements.txt", Packages: types.Packages{ { Name: "click", @@ -64,29 +136,50 @@ func Test_pipAnalyzer_Analyze(t *testing.T) { }, }, { - name: "happy path with not related filename", - inputFile: "testdata/not-related.txt", - want: nil, + name: "happy path with not related filename", + dir: "testdata/empty", + want: &analyzer.AnalysisResult{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - f, err := os.Open(tt.inputFile) + if tt.venv != "" { + t.Setenv("VIRTUAL_ENV", tt.venv) + } + + var newPATH string + if tt.pythonExecDir != "" { + err := os.MkdirAll(tt.pythonExecDir, os.ModePerm) + require.NoError(t, err) + defer func() { + if strings.HasSuffix(tt.pythonExecDir, "bar") { // for `happy path with licenses from common dir` test + tt.pythonExecDir = filepath.Dir(tt.pythonExecDir) + } + err = os.RemoveAll(tt.pythonExecDir) + require.NoError(t, err) + }() + + pythonExecFileName := "python" + if runtime.GOOS == "windows" { + pythonExecFileName = "python.exe" + } + // create temp python3 Executable + err = os.WriteFile(filepath.Join(tt.pythonExecDir, pythonExecFileName), nil, 0755) + require.NoError(t, err) + + newPATH, err = filepath.Abs(tt.pythonExecDir) + require.NoError(t, err) + + } + t.Setenv("PATH", newPATH) + + a, err := newPipLibraryAnalyzer(analyzer.AnalyzerOptions{}) require.NoError(t, err) - defer f.Close() - a := pipLibraryAnalyzer{} - ctx := context.Background() - got, err := a.Analyze(ctx, analyzer.AnalysisInput{ - FilePath: tt.inputFile, - Content: f, + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), }) - if tt.wantErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr) - return - } require.NoError(t, err) assert.Equal(t, tt.want, got) }) @@ -118,3 +211,90 @@ func Test_pipAnalyzer_Required(t *testing.T) { }) } } + +func Test_pythonExecutablePath(t *testing.T) { + tests := []struct { + name string + execName string + wantErr string + }{ + { + name: "happy path with `python` filename", + execName: "python", + }, + { + name: "happy path with `python3` filename", + execName: "python3", + }, + { + name: "happy path with `python2` filename", + execName: "python2", + }, + { + name: "sad path. Python executable not found", + execName: "python-wrong", + wantErr: "unable to find path to Python executable", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := t.TempDir() + binDir := filepath.Join(tmpDir, "bin") + err := os.MkdirAll(binDir, os.ModePerm) + require.NoError(t, err) + + if runtime.GOOS == "windows" { + tt.execName += ".exe" + } + err = os.WriteFile(filepath.Join(binDir, tt.execName), nil, 0755) + require.NoError(t, err) + + t.Setenv("PATH", binDir) + + path, err := pythonExecutablePath() + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + require.Equal(t, tt.execName, filepath.Base(path)) + }) + } +} + +func Test_sortPythonDirs(t *testing.T) { + dirs := []string{ + "wrong", + "wrong2.7", + "python3.11", + "python3.10", + "python2.7", + "python3.9", + "python3", + "python2", + "pythonBadVer", + } + wantDirs := []string{ + "python2", + "python2.7", + "python3", + "python3.9", + "python3.10", + "python3.11", + } + + tmp := t.TempDir() + for _, dir := range dirs { + err := os.Mkdir(filepath.Join(tmp, dir), os.ModePerm) + require.NoError(t, err) + } + + tmpDir, err := os.ReadDir(tmp) + require.NoError(t, err) + + a := pipLibraryAnalyzer{ + logger: log.WithPrefix("pip"), + } + got := a.sortPythonDirs(tmpDir) + require.Equal(t, wantDirs, got) +} diff --git a/pkg/fanal/analyzer/language/python/pip/testdata/not-related.txt b/pkg/fanal/analyzer/language/python/pip/testdata/empty/requirements.txt similarity index 100% rename from pkg/fanal/analyzer/language/python/pip/testdata/not-related.txt rename to pkg/fanal/analyzer/language/python/pip/testdata/empty/requirements.txt diff --git a/pkg/fanal/analyzer/language/python/pip/testdata/requirements.txt b/pkg/fanal/analyzer/language/python/pip/testdata/happy/requirements.txt similarity index 100% rename from pkg/fanal/analyzer/language/python/pip/testdata/requirements.txt rename to pkg/fanal/analyzer/language/python/pip/testdata/happy/requirements.txt diff --git a/pkg/fanal/analyzer/language/python/pip/testdata/libs/common-dir/lib/site-packages/Flask-2.0.0.dist-info/METADATA b/pkg/fanal/analyzer/language/python/pip/testdata/libs/common-dir/lib/site-packages/Flask-2.0.0.dist-info/METADATA new file mode 100644 index 000000000000..5df0132dde56 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/pip/testdata/libs/common-dir/lib/site-packages/Flask-2.0.0.dist-info/METADATA @@ -0,0 +1,124 @@ +Metadata-Version: 2.1 +Name: Flask +Version: 2.0.0 +Summary: A simple framework for building complex web applications. +Home-page: https://palletsprojects.com/p/flask +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://flask.palletsprojects.com/ +Project-URL: Changes, https://flask.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/flask/ +Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/ +Project-URL: Twitter, https://twitter.com/PalletsTeam +Project-URL: Chat, https://discord.gg/pallets +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Framework :: Flask +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst +Requires-Dist: Werkzeug (>=2.0) +Requires-Dist: Jinja2 (>=3.0) +Requires-Dist: itsdangerous (>=2.0) +Requires-Dist: click (>=7.1.2) +Provides-Extra: async +Requires-Dist: asgiref (>=3.2) ; extra == 'async' +Provides-Extra: dotenv +Requires-Dist: python-dotenv ; extra == 'dotenv' + +Flask +===== + +Flask is a lightweight `WSGI`_ web application framework. It is designed +to make getting started quick and easy, with the ability to scale up to +complex applications. It began as a simple wrapper around `Werkzeug`_ +and `Jinja`_ and has become one of the most popular Python web +application frameworks. + +Flask offers suggestions, but doesn't enforce any dependencies or +project layout. It is up to the developer to choose the tools and +libraries they want to use. There are many extensions provided by the +community that make adding new functionality easy. + +.. _WSGI: https://wsgi.readthedocs.io/ +.. _Werkzeug: https://werkzeug.palletsprojects.com/ +.. _Jinja: https://jinja.palletsprojects.com/ + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U Flask + +.. _pip: https://pip.pypa.io/en/stable/quickstart/ + + +A Simple Example +---------------- + +.. code-block:: python + + # save this as app.py + from flask import Flask + + app = Flask(__name__) + + @app.route("/") + def hello(): + return "Hello, World!" + +.. code-block:: text + + $ flask run + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + + +Contributing +------------ + +For guidance on setting up a development environment and how to make a +contribution to Flask, see the `contributing guidelines`_. + +.. _contributing guidelines: https://github.com/pallets/flask/blob/master/CONTRIBUTING.rst + + +Donate +------ + +The Pallets organization develops and supports Flask and the libraries +it uses. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://flask.palletsprojects.com/ +- Changes: https://flask.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/Flask/ +- Source Code: https://github.com/pallets/flask/ +- Issue Tracker: https://github.com/pallets/flask/issues/ +- Website: https://palletsprojects.com/p/flask/ +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets + + diff --git a/pkg/fanal/analyzer/language/python/pip/testdata/libs/common-dir/lib/site-packages/click-8.0.0.dist-info/METADATA b/pkg/fanal/analyzer/language/python/pip/testdata/libs/common-dir/lib/site-packages/click-8.0.0.dist-info/METADATA new file mode 100644 index 000000000000..5e67f0446795 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/pip/testdata/libs/common-dir/lib/site-packages/click-8.0.0.dist-info/METADATA @@ -0,0 +1,109 @@ +Metadata-Version: 2.1 +Name: click +Version: 8.0.0 +Summary: Composable command line interface toolkit +Home-page: https://palletsprojects.com/p/click/ +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://click.palletsprojects.com/ +Project-URL: Changes, https://click.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/click/ +Project-URL: Issue Tracker, https://github.com/pallets/click/issues/ +Project-URL: Twitter, https://twitter.com/PalletsTeam +Project-URL: Chat, https://discord.gg/pallets +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst +Requires-Dist: colorama ; platform_system == "Windows" + +\$ click\_ +========== + +Click is a Python package for creating beautiful command line interfaces +in a composable way with as little code as necessary. It's the "Command +Line Interface Creation Kit". It's highly configurable but comes with +sensible defaults out of the box. + +It aims to make the process of writing command line tools quick and fun +while also preventing any frustration caused by the inability to +implement an intended CLI API. + +Click in three points: + +- Arbitrary nesting of commands +- Automatic help page generation +- Supports lazy loading of subcommands at runtime + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U click + +.. _pip: https://pip.pypa.io/en/stable/quickstart/ + + +A Simple Example +---------------- + +.. code-block:: python + + import click + + @click.command() + @click.option("--count", default=1, help="Number of greetings.") + @click.option("--name", prompt="Your name", help="The person to greet.") + def hello(count, name): + """Simple program that greets NAME for a total of COUNT times.""" + for _ in range(count): + click.echo(f"Hello, {name}!") + + if __name__ == '__main__': + hello() + +.. code-block:: text + + $ python hello.py --count=3 + Your name: Click + Hello, Click! + Hello, Click! + Hello, Click! + + +Donate +------ + +The Pallets organization develops and supports Click and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://click.palletsprojects.com/ +- Changes: https://click.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/click/ +- Source Code: https://github.com/pallets/click +- Issue Tracker: https://github.com/pallets/click/issues +- Website: https://palletsprojects.com/p/click +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets + + diff --git a/pkg/fanal/analyzer/language/python/pip/testdata/libs/python-dir/lib/python3.10/site-packages/Flask-2.0.0.dist-info/METADATA b/pkg/fanal/analyzer/language/python/pip/testdata/libs/python-dir/lib/python3.10/site-packages/Flask-2.0.0.dist-info/METADATA new file mode 100644 index 000000000000..5df0132dde56 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/pip/testdata/libs/python-dir/lib/python3.10/site-packages/Flask-2.0.0.dist-info/METADATA @@ -0,0 +1,124 @@ +Metadata-Version: 2.1 +Name: Flask +Version: 2.0.0 +Summary: A simple framework for building complex web applications. +Home-page: https://palletsprojects.com/p/flask +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://flask.palletsprojects.com/ +Project-URL: Changes, https://flask.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/flask/ +Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/ +Project-URL: Twitter, https://twitter.com/PalletsTeam +Project-URL: Chat, https://discord.gg/pallets +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Framework :: Flask +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst +Requires-Dist: Werkzeug (>=2.0) +Requires-Dist: Jinja2 (>=3.0) +Requires-Dist: itsdangerous (>=2.0) +Requires-Dist: click (>=7.1.2) +Provides-Extra: async +Requires-Dist: asgiref (>=3.2) ; extra == 'async' +Provides-Extra: dotenv +Requires-Dist: python-dotenv ; extra == 'dotenv' + +Flask +===== + +Flask is a lightweight `WSGI`_ web application framework. It is designed +to make getting started quick and easy, with the ability to scale up to +complex applications. It began as a simple wrapper around `Werkzeug`_ +and `Jinja`_ and has become one of the most popular Python web +application frameworks. + +Flask offers suggestions, but doesn't enforce any dependencies or +project layout. It is up to the developer to choose the tools and +libraries they want to use. There are many extensions provided by the +community that make adding new functionality easy. + +.. _WSGI: https://wsgi.readthedocs.io/ +.. _Werkzeug: https://werkzeug.palletsprojects.com/ +.. _Jinja: https://jinja.palletsprojects.com/ + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U Flask + +.. _pip: https://pip.pypa.io/en/stable/quickstart/ + + +A Simple Example +---------------- + +.. code-block:: python + + # save this as app.py + from flask import Flask + + app = Flask(__name__) + + @app.route("/") + def hello(): + return "Hello, World!" + +.. code-block:: text + + $ flask run + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + + +Contributing +------------ + +For guidance on setting up a development environment and how to make a +contribution to Flask, see the `contributing guidelines`_. + +.. _contributing guidelines: https://github.com/pallets/flask/blob/master/CONTRIBUTING.rst + + +Donate +------ + +The Pallets organization develops and supports Flask and the libraries +it uses. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://flask.palletsprojects.com/ +- Changes: https://flask.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/Flask/ +- Source Code: https://github.com/pallets/flask/ +- Issue Tracker: https://github.com/pallets/flask/issues/ +- Website: https://palletsprojects.com/p/flask/ +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets + + diff --git a/pkg/fanal/analyzer/language/python/pip/testdata/libs/python-dir/lib/python3.10/site-packages/click-8.0.0.dist-info/METADATA b/pkg/fanal/analyzer/language/python/pip/testdata/libs/python-dir/lib/python3.10/site-packages/click-8.0.0.dist-info/METADATA new file mode 100644 index 000000000000..5e67f0446795 --- /dev/null +++ b/pkg/fanal/analyzer/language/python/pip/testdata/libs/python-dir/lib/python3.10/site-packages/click-8.0.0.dist-info/METADATA @@ -0,0 +1,109 @@ +Metadata-Version: 2.1 +Name: click +Version: 8.0.0 +Summary: Composable command line interface toolkit +Home-page: https://palletsprojects.com/p/click/ +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://click.palletsprojects.com/ +Project-URL: Changes, https://click.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/click/ +Project-URL: Issue Tracker, https://github.com/pallets/click/issues/ +Project-URL: Twitter, https://twitter.com/PalletsTeam +Project-URL: Chat, https://discord.gg/pallets +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst +Requires-Dist: colorama ; platform_system == "Windows" + +\$ click\_ +========== + +Click is a Python package for creating beautiful command line interfaces +in a composable way with as little code as necessary. It's the "Command +Line Interface Creation Kit". It's highly configurable but comes with +sensible defaults out of the box. + +It aims to make the process of writing command line tools quick and fun +while also preventing any frustration caused by the inability to +implement an intended CLI API. + +Click in three points: + +- Arbitrary nesting of commands +- Automatic help page generation +- Supports lazy loading of subcommands at runtime + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U click + +.. _pip: https://pip.pypa.io/en/stable/quickstart/ + + +A Simple Example +---------------- + +.. code-block:: python + + import click + + @click.command() + @click.option("--count", default=1, help="Number of greetings.") + @click.option("--name", prompt="Your name", help="The person to greet.") + def hello(count, name): + """Simple program that greets NAME for a total of COUNT times.""" + for _ in range(count): + click.echo(f"Hello, {name}!") + + if __name__ == '__main__': + hello() + +.. code-block:: text + + $ python hello.py --count=3 + Your name: Click + Hello, Click! + Hello, Click! + Hello, Click! + + +Donate +------ + +The Pallets organization develops and supports Click and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://click.palletsprojects.com/ +- Changes: https://click.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/click/ +- Source Code: https://github.com/pallets/click +- Issue Tracker: https://github.com/pallets/click/issues +- Website: https://palletsprojects.com/p/click +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets + + diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index 1d4029578ca9..2cee794c85b2 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -47,7 +47,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", + BlobID: "sha256:e480047f53bccb8a9107a424fab43452dc59df641022a300d34326639254a0cf", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -82,9 +82,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "host", Type: artifact.TypeFilesystem, - ID: "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", + ID: "sha256:e480047f53bccb8a9107a424fab43452dc59df641022a300d34326639254a0cf", BlobIDs: []string{ - "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", + "sha256:e480047f53bccb8a9107a424fab43452dc59df641022a300d34326639254a0cf", }, }, }, @@ -125,7 +125,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:afc2bc421aac8c61d89d4dd1c1865efb5441e3877c8a4c919232729d7c574dab", + BlobID: "sha256:e480047f53bccb8a9107a424fab43452dc59df641022a300d34326639254a0cf", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -175,7 +175,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:0c41376dbbc0dbf18e9dbd36c5de85627007dcf9357fd98f191864d48dd35537", + BlobID: "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -203,9 +203,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "testdata/requirements.txt", Type: artifact.TypeFilesystem, - ID: "sha256:0c41376dbbc0dbf18e9dbd36c5de85627007dcf9357fd98f191864d48dd35537", + ID: "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", BlobIDs: []string{ - "sha256:0c41376dbbc0dbf18e9dbd36c5de85627007dcf9357fd98f191864d48dd35537", + "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", }, }, }, @@ -216,7 +216,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:0c41376dbbc0dbf18e9dbd36c5de85627007dcf9357fd98f191864d48dd35537", + BlobID: "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -244,9 +244,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "testdata/requirements.txt", Type: artifact.TypeFilesystem, - ID: "sha256:0c41376dbbc0dbf18e9dbd36c5de85627007dcf9357fd98f191864d48dd35537", + ID: "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", BlobIDs: []string{ - "sha256:0c41376dbbc0dbf18e9dbd36c5de85627007dcf9357fd98f191864d48dd35537", + "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", }, }, }, @@ -341,9 +341,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/single-failure", Type: artifact.TypeFilesystem, - ID: "sha256:1a6ce0acc3b57eb6c830c96fcd868fec1eb4d3b57ad51e481c76d85f22870a65", + ID: "sha256:5e4ca8ffaa89f49c69acfbac6d7cebb0a372b07d4c4c9876f9a58043c8ee56e9", BlobIDs: []string{ - "sha256:1a6ce0acc3b57eb6c830c96fcd868fec1eb4d3b57ad51e481c76d85f22870a65", + "sha256:5e4ca8ffaa89f49c69acfbac6d7cebb0a372b07d4c4c9876f9a58043c8ee56e9", }, }, }, @@ -426,9 +426,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/multiple-failures", Type: artifact.TypeFilesystem, - ID: "sha256:afc20cf0fc99c62bbc79b00cb9fbc70ba7ee76c946a6d560639ba9279344787d", + ID: "sha256:1832cedd0d8baeb172c7297acb56417c0dec454c280a79ee2a8f413e1ffca192", BlobIDs: []string{ - "sha256:afc20cf0fc99c62bbc79b00cb9fbc70ba7ee76c946a6d560639ba9279344787d", + "sha256:1832cedd0d8baeb172c7297acb56417c0dec454c280a79ee2a8f413e1ffca192", }, }, }, @@ -456,9 +456,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/no-results", Type: artifact.TypeFilesystem, - ID: "sha256:1827b6a0b0a17e0d623a2045e9d9c331ef613390eda2fed823969ee0dd730257", + ID: "sha256:00e01cf7bf052dbf8f02739d281675eff16f5ddf004256472cdddb55cf974fd6", BlobIDs: []string{ - "sha256:1827b6a0b0a17e0d623a2045e9d9c331ef613390eda2fed823969ee0dd730257", + "sha256:00e01cf7bf052dbf8f02739d281675eff16f5ddf004256472cdddb55cf974fd6", }, }, }, @@ -505,9 +505,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/passed", Type: artifact.TypeFilesystem, - ID: "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", + ID: "sha256:9ee9fe14c3fcff202bc8c72f9e20a3035442ae394ec5cd201e1cb29d2111b4e1", BlobIDs: []string{ - "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", + "sha256:9ee9fe14c3fcff202bc8c72f9e20a3035442ae394ec5cd201e1cb29d2111b4e1", }, }, }, @@ -571,9 +571,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/busted-relative-paths/child/main.tf", Type: artifact.TypeFilesystem, - ID: "sha256:8db34d644bfb98077180616caeab0b41a26d9029a47a23d4b36e1d6e45584919", + ID: "sha256:c3e6c9e68cd7a9900cd8aa690e1d5af174ddcc00ddb9438a858c3e329b9ea8f4", BlobIDs: []string{ - "sha256:8db34d644bfb98077180616caeab0b41a26d9029a47a23d4b36e1d6e45584919", + "sha256:c3e6c9e68cd7a9900cd8aa690e1d5af174ddcc00ddb9438a858c3e329b9ea8f4", }, }, }, @@ -621,9 +621,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/tfvar-outside/tf", Type: artifact.TypeFilesystem, - ID: "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", + ID: "sha256:9ee9fe14c3fcff202bc8c72f9e20a3035442ae394ec5cd201e1cb29d2111b4e1", BlobIDs: []string{ - "sha256:eec58ef10d1b04df4af76b2472e615f8c27e253a16f90d7542670a7001d88915", + "sha256:9ee9fe14c3fcff202bc8c72f9e20a3035442ae394ec5cd201e1cb29d2111b4e1", }, }, }, @@ -711,9 +711,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/relative-paths/child", Type: artifact.TypeFilesystem, - ID: "sha256:f13b89447db61be1c1e4099ef18aec7272091f8f2d3581643a9d1fabc74eda83", + ID: "sha256:44d28d3fc115f1f8dd3f8978c7e9432ba255dd82661c3810178276897e2d8fb7", BlobIDs: []string{ - "sha256:f13b89447db61be1c1e4099ef18aec7272091f8f2d3581643a9d1fabc74eda83", + "sha256:44d28d3fc115f1f8dd3f8978c7e9432ba255dd82661c3810178276897e2d8fb7", }, }, }, @@ -830,9 +830,9 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraformplan/snapshots/single-failure", Type: artifact.TypeFilesystem, - ID: "sha256:732c38451bde877a94d1ff5b6f2019655bed04fd24b1169de195eeee1199045e", + ID: "sha256:39babdcb854331c58dc1e20c20dc67c2b4bda23895bf2861cc72d187de2bc716", BlobIDs: []string{ - "sha256:732c38451bde877a94d1ff5b6f2019655bed04fd24b1169de195eeee1199045e", + "sha256:39babdcb854331c58dc1e20c20dc67c2b4bda23895bf2861cc72d187de2bc716", }, }, }, @@ -906,9 +906,9 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraformplan/snapshots/multiple-failures", Type: artifact.TypeFilesystem, - ID: "sha256:752c0b470adfcfe7e21892cf4c6fc3bc28dba873c9c1696f40b71b7a51ad7231", + ID: "sha256:32debc3c2857404ceeec978a938609a4b20f1c53c304603815955885377f286c", BlobIDs: []string{ - "sha256:752c0b470adfcfe7e21892cf4c6fc3bc28dba873c9c1696f40b71b7a51ad7231", + "sha256:32debc3c2857404ceeec978a938609a4b20f1c53c304603815955885377f286c", }, }, }, @@ -946,9 +946,9 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraformplan/snapshots/passed", Type: artifact.TypeFilesystem, - ID: "sha256:8947704a08f54ab1df32cd905d6bca72edf1785a42702968bafa331172da7176", + ID: "sha256:2432b64e4583676ec1e94903d31c4faf79a1e0f4ed49bf24aef2bf9b44517ca2", BlobIDs: []string{ - "sha256:8947704a08f54ab1df32cd905d6bca72edf1785a42702968bafa331172da7176", + "sha256:2432b64e4583676ec1e94903d31c4faf79a1e0f4ed49bf24aef2bf9b44517ca2", }, }, }, @@ -1061,9 +1061,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/single-failure/src", Type: artifact.TypeFilesystem, - ID: "sha256:889a94522970c6e55f1f7543914b2f0131f79f9c4526445fb95309f64a9947d7", + ID: "sha256:43bb7ce5253686cf96a68e6d4bd30c33e163019ae2893968602da9e5e86b1aab", BlobIDs: []string{ - "sha256:889a94522970c6e55f1f7543914b2f0131f79f9c4526445fb95309f64a9947d7", + "sha256:43bb7ce5253686cf96a68e6d4bd30c33e163019ae2893968602da9e5e86b1aab", }, }, }, @@ -1145,9 +1145,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/multiple-failures/src", Type: artifact.TypeFilesystem, - ID: "sha256:17c9c72a759856445e6d3847b2d5ed90c3bad3e4ee50cea0c812ef53c179f8ca", + ID: "sha256:4609d64f57187099483e76a979d2f74862a092a5f42a9945e6f1242c372a811c", BlobIDs: []string{ - "sha256:17c9c72a759856445e6d3847b2d5ed90c3bad3e4ee50cea0c812ef53c179f8ca", + "sha256:4609d64f57187099483e76a979d2f74862a092a5f42a9945e6f1242c372a811c", }, }, }, @@ -1177,9 +1177,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/no-results/src", Type: artifact.TypeFilesystem, - ID: "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", + ID: "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", BlobIDs: []string{ - "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", + "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", }, }, }, @@ -1235,9 +1235,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/params/code/src", Type: artifact.TypeFilesystem, - ID: "sha256:267b572211115db6a2a4484a02317fbb6d4f050da0e95b1db4243d49889483de", + ID: "sha256:abaeb2f443a59940afd63e015d0bb737127ebde06306840f529dd40d65390703", BlobIDs: []string{ - "sha256:267b572211115db6a2a4484a02317fbb6d4f050da0e95b1db4243d49889483de", + "sha256:abaeb2f443a59940afd63e015d0bb737127ebde06306840f529dd40d65390703", }, }, }, @@ -1293,9 +1293,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/passed/src", Type: artifact.TypeFilesystem, - ID: "sha256:8ca92725ce2f47b7ffb1b0a9e0359d59ac2b3b3f517ba42f66a859436057e54a", + ID: "sha256:73bb13d7c722bfcdf6c00f51f368befda453e1acba31ba62aad31b5b04b235e8", BlobIDs: []string{ - "sha256:8ca92725ce2f47b7ffb1b0a9e0359d59ac2b3b3f517ba42f66a859436057e54a", + "sha256:73bb13d7c722bfcdf6c00f51f368befda453e1acba31ba62aad31b5b04b235e8", }, }, }, @@ -1381,9 +1381,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/single-failure/src", Type: artifact.TypeFilesystem, - ID: "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", + ID: "sha256:059f081288e7ea418286bddcee78167e8ebf33b47e365c20754b6cfa180d997d", BlobIDs: []string{ - "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", + "sha256:059f081288e7ea418286bddcee78167e8ebf33b47e365c20754b6cfa180d997d", }, }, }, @@ -1439,9 +1439,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/multiple-failures/src", Type: artifact.TypeFilesystem, - ID: "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", + ID: "sha256:059f081288e7ea418286bddcee78167e8ebf33b47e365c20754b6cfa180d997d", BlobIDs: []string{ - "sha256:627cbf451ec7929dfe5151dfe0e2305ed855906bf79136f198528a0cc3f6e4f9", + "sha256:059f081288e7ea418286bddcee78167e8ebf33b47e365c20754b6cfa180d997d", }, }, }, @@ -1469,9 +1469,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/no-results/src", Type: artifact.TypeFilesystem, - ID: "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", + ID: "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", BlobIDs: []string{ - "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", + "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", }, }, }, @@ -1529,9 +1529,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/passed/src", Type: artifact.TypeFilesystem, - ID: "sha256:4cc7f6bba417cc65c5391bc9c07fd1e205e21bdec87b271889433af18be1e454", + ID: "sha256:5f6504b01b68ef1418d59210c0c7b3604fe63f8575a72721d1172d9d2fdd2e23", BlobIDs: []string{ - "sha256:4cc7f6bba417cc65c5391bc9c07fd1e205e21bdec87b271889433af18be1e454", + "sha256:5f6504b01b68ef1418d59210c0c7b3604fe63f8575a72721d1172d9d2fdd2e23", }, }, }, @@ -1621,9 +1621,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/single-failure/src", Type: artifact.TypeFilesystem, - ID: "sha256:d5ca0b4e96aaaeafa424a2250db6297a5182cb6ca5db49bc1ff11790f6cdbee9", + ID: "sha256:14768f3c82388c43e0af6579632535b5ecd6bd4ac802d063d74b636725191fe9", BlobIDs: []string{ - "sha256:d5ca0b4e96aaaeafa424a2250db6297a5182cb6ca5db49bc1ff11790f6cdbee9", + "sha256:14768f3c82388c43e0af6579632535b5ecd6bd4ac802d063d74b636725191fe9", }, }, }, @@ -1707,9 +1707,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/multiple-failures/src", Type: artifact.TypeFilesystem, - ID: "sha256:eef9fff2fe8f5c4a123c018b4f91db25d9676e7d171a3a683c2fbfbbbe82fa54", + ID: "sha256:d7b5fe7fb0e8d51cb6a06b5f2027b1e976e25a2462ff40241f86e13e69ff210c", BlobIDs: []string{ - "sha256:eef9fff2fe8f5c4a123c018b4f91db25d9676e7d171a3a683c2fbfbbbe82fa54", + "sha256:d7b5fe7fb0e8d51cb6a06b5f2027b1e976e25a2462ff40241f86e13e69ff210c", }, }, }, @@ -1737,9 +1737,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/no-results/src", Type: artifact.TypeFilesystem, - ID: "sha256:2b54cf33feaa1fe1f5bf223f873ca6c3f7c3693b0bb3b0ce9e2e7fd79cd37b5a", + ID: "sha256:ddd0e363b0ebab71250f9d36de65d485ca2faa9510ab6ddea940de7af8267b67", BlobIDs: []string{ - "sha256:2b54cf33feaa1fe1f5bf223f873ca6c3f7c3693b0bb3b0ce9e2e7fd79cd37b5a", + "sha256:ddd0e363b0ebab71250f9d36de65d485ca2faa9510ab6ddea940de7af8267b67", }, }, }, @@ -1797,9 +1797,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/passed/src", Type: artifact.TypeFilesystem, - ID: "sha256:dc7a0fd3ea2f13b0ea05f4e517a16e602b0fc17fbd72aa5e34107ef12a91a30b", + ID: "sha256:af77d733739a4366b6e03e95605f5282262d45905efed501d83ec7ecc97a7574", BlobIDs: []string{ - "sha256:dc7a0fd3ea2f13b0ea05f4e517a16e602b0fc17fbd72aa5e34107ef12a91a30b", + "sha256:af77d733739a4366b6e03e95605f5282262d45905efed501d83ec7ecc97a7574", }, }, }, @@ -1886,9 +1886,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/azurearm/single-failure/src", Type: artifact.TypeFilesystem, - ID: "sha256:c1a8bfd544b9041ad194382cc42b54289f70966d061ef501b267aec8fd07c5df", + ID: "sha256:ff25ae7d346f724faf4d2653868bc249bf221460bcf6d364cf50372c303342d2", BlobIDs: []string{ - "sha256:c1a8bfd544b9041ad194382cc42b54289f70966d061ef501b267aec8fd07c5df", + "sha256:ff25ae7d346f724faf4d2653868bc249bf221460bcf6d364cf50372c303342d2", }, }, }, @@ -1968,9 +1968,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/azurearm/multiple-failures/src", Type: artifact.TypeFilesystem, - ID: "sha256:75bf0e88f8d2857be90fb8d10a350c04c1532ba7f510e1eb404a8bae30ce97d8", + ID: "sha256:e5c5ad752c6ff8d26f1b6764a9fb6dec345157df7c816b86d491b0af9e9f4ae6", BlobIDs: []string{ - "sha256:75bf0e88f8d2857be90fb8d10a350c04c1532ba7f510e1eb404a8bae30ce97d8", + "sha256:e5c5ad752c6ff8d26f1b6764a9fb6dec345157df7c816b86d491b0af9e9f4ae6", }, }, }, @@ -1998,9 +1998,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/azurearm/no-results/src", Type: artifact.TypeFilesystem, - ID: "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", + ID: "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", BlobIDs: []string{ - "sha256:26c76a2cb55cb0ef2c3a2dd79e237bddb508ca2c4cefdb103698a1972c8a9c2d", + "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", }, }, }, @@ -2054,9 +2054,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/azurearm/passed/src", Type: artifact.TypeFilesystem, - ID: "sha256:b9ba7c4eafec405c8b6998dbb98ee1c7f7830caf8487fd1461433ff82d8779e9", + ID: "sha256:0702eea6f699984f98c788c737f3adf74ae00db2b24b7d44577c7eedc31b1eb1", BlobIDs: []string{ - "sha256:b9ba7c4eafec405c8b6998dbb98ee1c7f7830caf8487fd1461433ff82d8779e9", + "sha256:0702eea6f699984f98c788c737f3adf74ae00db2b24b7d44577c7eedc31b1eb1", }, }, }, From 1c49ae957e7d6b273288032b7a04392b81b46abe Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 30 May 2024 14:32:12 +0400 Subject: [PATCH 123/352] docs(julia): add scanner table (#6826) Signed-off-by: knqyf263 --- docs/docs/coverage/language/julia.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/docs/coverage/language/julia.md b/docs/docs/coverage/language/julia.md index 3817dacfeb1b..3c4446c12e7a 100644 --- a/docs/docs/coverage/language/julia.md +++ b/docs/docs/coverage/language/julia.md @@ -3,6 +3,12 @@ ## Features Trivy supports [Pkg.jl](https://pkgdocs.julialang.org/v1/), which is the Julia package manager. +The following scanners are supported. + +| Package manager | SBOM | Vulnerability | License | +|-----------------|:----:|:-------------:|:-------:| +| Pkg.jl | ✓ | - | - | + The following table provides an outline of the features Trivy offers. | Package manager | File | Transitive dependencies | Dev dependencies | License | Dependency graph | Position | From aa0c413814e8915b38d2285c6a8ba5bc3f0705b4 Mon Sep 17 00:00:00 2001 From: guoguangwu Date: Sat, 1 Jun 2024 14:58:20 +0800 Subject: [PATCH 124/352] fix: close testfile (#6830) Signed-off-by: guoguangwu --- pkg/fanal/test/integration/docker/docker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/fanal/test/integration/docker/docker.go b/pkg/fanal/test/integration/docker/docker.go index 13ba250d4444..772aef0778f2 100644 --- a/pkg/fanal/test/integration/docker/docker.go +++ b/pkg/fanal/test/integration/docker/docker.go @@ -81,6 +81,7 @@ func (d Docker) ReplicateImage(ctx context.Context, imageRef, imagePath string, if err != nil { return err } + defer testfile.Close() // load image into docker engine resp, err := d.cli.ImageLoad(ctx, testfile, true) From c2b9132a7e933a68df4cc0eb86aab23719ded1b5 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Sun, 2 Jun 2024 15:41:55 +0400 Subject: [PATCH 125/352] fix(cli): always output fatal errors to stderr (#6827) Signed-off-by: knqyf263 --- pkg/log/logger.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/log/logger.go b/pkg/log/logger.go index efee7ef8800d..f46eb46fc87f 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -67,7 +67,8 @@ func Errorf(format string, args ...any) { slog.Default().Error(fmt.Sprintf(forma // Fatal for logging fatal errors func Fatal(msg string, args ...any) { - slog.Default().Log(context.Background(), LevelFatal, msg, args...) + // Fatal errors should be logged to stderr even if the logger is disabled. + New(NewHandler(os.Stderr, &Options{})).Log(context.Background(), LevelFatal, msg, args...) os.Exit(1) } From 83fc6e7ec2b791c2a28326cc38eb54f44bb876df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:57:31 +0400 Subject: [PATCH 126/352] chore(deps): bump alpine from 3.19.1 to 3.20.0 in the docker group (#6835) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- Dockerfile.canary | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9c7b2d83f9c4..9e4dfe3b1dbe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.19.1 +FROM alpine:3.20.0 RUN apk --no-cache add ca-certificates git COPY trivy /usr/local/bin/trivy COPY contrib/*.tpl contrib/ diff --git a/Dockerfile.canary b/Dockerfile.canary index f42b34519499..bd65bc46195d 100644 --- a/Dockerfile.canary +++ b/Dockerfile.canary @@ -1,4 +1,4 @@ -FROM alpine:3.19.1 +FROM alpine:3.20.0 RUN apk --no-cache add ca-certificates git # binaries were created with GoReleaser From 728e77a7261dc3fcda1e61e79be066c789bbba0c Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Mon, 3 Jun 2024 09:31:18 +0400 Subject: [PATCH 127/352] fix(plugin): initialize logger (#6836) Signed-off-by: knqyf263 --- cmd/trivy/main.go | 3 ++- pkg/commands/app.go | 2 +- pkg/plugin/manager.go | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cmd/trivy/main.go b/cmd/trivy/main.go index 9e4fe4f5d639..6cbda13b9cf3 100644 --- a/cmd/trivy/main.go +++ b/cmd/trivy/main.go @@ -28,7 +28,8 @@ func main() { func run() error { // Trivy behaves as the specified plugin. if runAsPlugin := os.Getenv("TRIVY_RUN_AS_PLUGIN"); runAsPlugin != "" { - if err := plugin.RunWithURL(context.Background(), runAsPlugin, plugin.Options{Args: os.Args[1:]}); err != nil { + log.InitLogger(false, false) + if err := plugin.Run(context.Background(), runAsPlugin, plugin.Options{Args: os.Args[1:]}); err != nil { return xerrors.Errorf("plugin error: %w", err) } return nil diff --git a/pkg/commands/app.go b/pkg/commands/app.go index cac211fa1484..4a8331a71053 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -799,7 +799,7 @@ func NewPluginCommand() *cobra.Command { Short: "Run a plugin on the fly", Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return plugin.RunWithURL(cmd.Context(), args[0], plugin.Options{Args: args[1:]}) + return plugin.Run(cmd.Context(), args[0], plugin.Options{Args: args[1:]}) }, }, &cobra.Command{ diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 741d245d6b62..91dd30d368c3 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -84,8 +84,8 @@ func Install(ctx context.Context, name string, opts Options) (Plugin, error) { func Start(ctx context.Context, name string, opts Options) (Wait, error) { return defaultManager().Start(ctx, name, opts) } -func RunWithURL(ctx context.Context, name string, opts Options) error { - return defaultManager().RunWithURL(ctx, name, opts) +func Run(ctx context.Context, name string, opts Options) error { + return defaultManager().Run(ctx, name, opts) } func Upgrade(ctx context.Context, names []string) error { return defaultManager().Upgrade(ctx, names) } func Uninstall(ctx context.Context, name string) error { return defaultManager().Uninstall(ctx, name) } @@ -291,8 +291,8 @@ func (m *Manager) Start(ctx context.Context, name string, opts Options) (Wait, e return wait, nil } -// RunWithURL runs the plugin -func (m *Manager) RunWithURL(ctx context.Context, name string, opts Options) error { +// Run installs and runs the plugin +func (m *Manager) Run(ctx context.Context, name string, opts Options) error { plugin, err := m.Install(ctx, name, opts) if err != nil { return xerrors.Errorf("plugin install error: %w", err) From c24dfbab68056a42aff9589b024c6f2d067f9f52 Mon Sep 17 00:00:00 2001 From: Aqua Security automated builds <54269356+aqua-bot@users.noreply.github.com> Date: Mon, 3 Jun 2024 09:45:19 +0300 Subject: [PATCH 128/352] release: v0.52.0 [main] (#6809) --- .release-please-manifest.json | 2 +- CHANGELOG.md | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 74ac33a5e6e7..be3d2812d7fe 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{".":"0.51.1"} +{".":"0.52.0"} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000000..721b9a11baa7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,50 @@ +# Changelog + +## [0.52.0](https://github.com/aquasecurity/trivy/compare/v0.51.1...v0.52.0) (2024-06-03) + + +### Features + +* Add Julia language analyzer support ([#5635](https://github.com/aquasecurity/trivy/issues/5635)) ([fecafb1](https://github.com/aquasecurity/trivy/commit/fecafb1fc5bb129c7485342a0775f0dd8bedd28e)) +* add support for plugin index ([#6674](https://github.com/aquasecurity/trivy/issues/6674)) ([26faf8f](https://github.com/aquasecurity/trivy/commit/26faf8f3f04b1c5f9f81c03ffc6b2008732207e2)) +* **misconf:** Add support for deprecating a check ([#6664](https://github.com/aquasecurity/trivy/issues/6664)) ([88702cf](https://github.com/aquasecurity/trivy/commit/88702cfd5918b093defc5b5580f7cbf16f5f2417)) +* **misconf:** add Terraform 'removed' block to schema ([#6640](https://github.com/aquasecurity/trivy/issues/6640)) ([b7a0a13](https://github.com/aquasecurity/trivy/commit/b7a0a131a03ed49c08d3b0d481bc9284934fd6e1)) +* **misconf:** register builtin Rego funcs from trivy-checks ([#6616](https://github.com/aquasecurity/trivy/issues/6616)) ([7c22ee3](https://github.com/aquasecurity/trivy/commit/7c22ee3df5ee51beb90e44428a99541b3d19ab98)) +* **misconf:** resolve tf module from OpenTofu compatible registry ([#6743](https://github.com/aquasecurity/trivy/issues/6743)) ([ac74520](https://github.com/aquasecurity/trivy/commit/ac7452009bf7ca0fa8ee1de8807c792eabad405a)) +* **misconf:** support for VPC resources for inbound/outbound rules ([#6779](https://github.com/aquasecurity/trivy/issues/6779)) ([349caf9](https://github.com/aquasecurity/trivy/commit/349caf96bc3dd81551d488044f1adfdb947f39fb)) +* **misconf:** support symlinks inside of Helm archives ([#6621](https://github.com/aquasecurity/trivy/issues/6621)) ([4eae37c](https://github.com/aquasecurity/trivy/commit/4eae37c52b035b3576361c12f70d3d9517d0a73c)) +* **nodejs:** add v9 pnpm lock file support ([#6617](https://github.com/aquasecurity/trivy/issues/6617)) ([1e08648](https://github.com/aquasecurity/trivy/commit/1e0864842e32a709941d4b4e8f521602bcee684d)) +* **plugin:** specify plugin version ([#6683](https://github.com/aquasecurity/trivy/issues/6683)) ([d6dc567](https://github.com/aquasecurity/trivy/commit/d6dc56732babbc9d7f788c280a768d8648aa093d)) +* **python:** add license support for `requirement.txt` files ([#6782](https://github.com/aquasecurity/trivy/issues/6782)) ([29615be](https://github.com/aquasecurity/trivy/commit/29615be85e8bfeaf5a0cd51829b1898c55fa4274)) +* **python:** add line number support for `requirement.txt` files ([#6729](https://github.com/aquasecurity/trivy/issues/6729)) ([2bc54ad](https://github.com/aquasecurity/trivy/commit/2bc54ad2752aba5de4380cb92c13b09c0abefd73)) +* **report:** Include licenses and secrets filtered by rego to ModifiedFindings ([#6483](https://github.com/aquasecurity/trivy/issues/6483)) ([fa3cf99](https://github.com/aquasecurity/trivy/commit/fa3cf993eace4be793f85907b42365269c597b91)) +* **vex:** improve relationship support in CSAF VEX ([#6735](https://github.com/aquasecurity/trivy/issues/6735)) ([a447f6b](https://github.com/aquasecurity/trivy/commit/a447f6ba94b6f8b14177dc5e4369a788e2020d90)) +* **vex:** support non-root components for products in OpenVEX ([#6728](https://github.com/aquasecurity/trivy/issues/6728)) ([9515695](https://github.com/aquasecurity/trivy/commit/9515695d45e9b5c20890e27e21e3ab45bfd4ce5f)) + + +### Bug Fixes + +* clean up golangci lint configuration ([#6797](https://github.com/aquasecurity/trivy/issues/6797)) ([62de6f3](https://github.com/aquasecurity/trivy/commit/62de6f3feba6e4c56ad3922441d5b0f150c3d6b7)) +* **cli:** always output fatal errors to stderr ([#6827](https://github.com/aquasecurity/trivy/issues/6827)) ([c2b9132](https://github.com/aquasecurity/trivy/commit/c2b9132a7e933a68df4cc0eb86aab23719ded1b5)) +* close APKINDEX archive file ([#6672](https://github.com/aquasecurity/trivy/issues/6672)) ([5caf437](https://github.com/aquasecurity/trivy/commit/5caf4377f3a7fcb1f6e1a84c67136ae62d100be3)) +* close settings.xml ([#6768](https://github.com/aquasecurity/trivy/issues/6768)) ([9c3e895](https://github.com/aquasecurity/trivy/commit/9c3e895fcb0852c00ac03ed21338768f76b5273b)) +* close testfile ([#6830](https://github.com/aquasecurity/trivy/issues/6830)) ([aa0c413](https://github.com/aquasecurity/trivy/commit/aa0c413814e8915b38d2285c6a8ba5bc3f0705b4)) +* **conda:** add support `pip` deps for `environment.yml` files ([#6675](https://github.com/aquasecurity/trivy/issues/6675)) ([150a773](https://github.com/aquasecurity/trivy/commit/150a77313e980cd63797a89a03afcbc97b285f38)) +* **go:** add only non-empty root modules for `gobinaries` ([#6710](https://github.com/aquasecurity/trivy/issues/6710)) ([c96f2a5](https://github.com/aquasecurity/trivy/commit/c96f2a5b3de820da37e14594dd537c3b0949ae9c)) +* **go:** include only `.version`|`.ver` (no prefixes) ldflags for `gobinaries` ([#6705](https://github.com/aquasecurity/trivy/issues/6705)) ([afb4f9d](https://github.com/aquasecurity/trivy/commit/afb4f9dc4730671ba004e1734fa66422c4c86dad)) +* Golang version parsing from binaries w/GOEXPERIMENT ([#6696](https://github.com/aquasecurity/trivy/issues/6696)) ([696f2ae](https://github.com/aquasecurity/trivy/commit/696f2ae0ecdd4f90303f41249924a09ace70dd78)) +* include packages unless it is not needed ([#6765](https://github.com/aquasecurity/trivy/issues/6765)) ([56dbe1f](https://github.com/aquasecurity/trivy/commit/56dbe1f6768fe67fbc1153b74fde0f83eaa1b281)) +* **misconf:** don't shift ignore rule related to code ([#6708](https://github.com/aquasecurity/trivy/issues/6708)) ([39a746c](https://github.com/aquasecurity/trivy/commit/39a746c77837f873e87b81be40676818030f44c5)) +* **misconf:** skip Rego errors with a nil location ([#6638](https://github.com/aquasecurity/trivy/issues/6638)) ([a2c522d](https://github.com/aquasecurity/trivy/commit/a2c522ddb229f049999c4ce74ef75a0e0f9fdc62)) +* **misconf:** skip Rego errors with a nil location ([#6666](https://github.com/aquasecurity/trivy/issues/6666)) ([a126e10](https://github.com/aquasecurity/trivy/commit/a126e1075a44ef0e40c0dc1e214d1c5955f80242)) +* node-collector high and critical cves ([#6707](https://github.com/aquasecurity/trivy/issues/6707)) ([ff32deb](https://github.com/aquasecurity/trivy/commit/ff32deb7bf9163c06963f557228260b3b8c161ed)) +* **plugin:** initialize logger ([#6836](https://github.com/aquasecurity/trivy/issues/6836)) ([728e77a](https://github.com/aquasecurity/trivy/commit/728e77a7261dc3fcda1e61e79be066c789bbba0c)) +* **python:** add package name and version validation for `requirements.txt` files. ([#6804](https://github.com/aquasecurity/trivy/issues/6804)) ([ea3a124](https://github.com/aquasecurity/trivy/commit/ea3a124fc7162c30c7f1a59bdb28db0b3c8bb86d)) +* **report:** hide empty tables if all vulns has been filtered ([#6352](https://github.com/aquasecurity/trivy/issues/6352)) ([3d388d8](https://github.com/aquasecurity/trivy/commit/3d388d8552ef42d4d54176309a38c1879008527b)) +* **sbom:** fix panic for `convert` mode when scanning json file derived from sbom file ([#6808](https://github.com/aquasecurity/trivy/issues/6808)) ([f92ea09](https://github.com/aquasecurity/trivy/commit/f92ea096856c7c262b05bd4d31c62689ebafac82)) +* use of specified context to obtain cluster name ([#6645](https://github.com/aquasecurity/trivy/issues/6645)) ([39ebed4](https://github.com/aquasecurity/trivy/commit/39ebed45f8c218509d264bd3f3ca548fc33d2b3a)) + + +### Performance Improvements + +* **misconf:** parse rego input once ([#6615](https://github.com/aquasecurity/trivy/issues/6615)) ([67c6b1d](https://github.com/aquasecurity/trivy/commit/67c6b1d473999003d682bdb42657bbf3a4a69a9c)) From b7b8cdc9e96abec35757295582f5deaa43bd3181 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Mon, 3 Jun 2024 11:34:28 +0400 Subject: [PATCH 129/352] test: replace embedded Git repository with dynamically created repository (#6824) Signed-off-by: knqyf263 --- internal/gittest/server.go | 132 ++++++++++++++++++ internal/testutil/fs.go | 36 +++++ pkg/fanal/artifact/repo/git_test.go | 57 ++++---- .../repo/testdata/test-repo/anothertest.txt | 1 + .../artifact/repo/testdata/test-repo/test.txt | 1 + .../artifact/repo/testdata/test.git/HEAD | 1 - .../artifact/repo/testdata/test.git/config | 6 - .../0d/8bf3f07c3970b3e38c2cfb1c619cb86fae76d2 | 2 - .../1c/1d7deed649fbecd66fab423ccd9d001bf9ff91 | Bin 59 -> 0 bytes .../27/aaec53f92314d9438a53c703f169d2cbf5001a | Bin 86 -> 0 bytes .../5e/fb9bc29c482e023e40e0a2b3b7e49cec842034 | Bin 53 -> 0 bytes .../6a/c152fe2b87cb5e243414df71790a32912e778d | 3 - .../c0/42cd14d2b999cade090785af47e9f8b8e342ff | Bin 34 -> 0 bytes .../c9/06fc4a94762f8a2c77c718947143d16e4e9ec7 | Bin 122 -> 0 bytes .../d7/937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b | Bin 165 -> 0 bytes .../e2/4866d1d31ddffdb27fbcf583d5deb4386d5145 | Bin 53 -> 0 bytes .../e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 | Bin 15 -> 0 bytes .../f4/836be6497e83e13dc0cfbce7e6b973b1ea511d | Bin 40 -> 0 bytes .../repo/testdata/test.git/refs/heads/master | 1 - .../testdata/test.git/refs/heads/valid-branch | 1 - .../repo/testdata/test.git/refs/tags/v1.0.0 | 1 - pkg/plugin/manager_unix_test.go | 60 +++++--- pkg/plugin/testdata/test_plugin.git/HEAD | 1 - pkg/plugin/testdata/test_plugin.git/config | 8 -- .../testdata/test_plugin.git/description | 1 - .../testdata/test_plugin.git/info/exclude | 6 - .../08/6aefb548a1150b765d1e163a5e542fc80bd660 | Bin 147 -> 0 bytes .../0a/e1413e3807e024dbc7de4129d12bdcae7dea61 | 3 - .../49/0535e047e795a5c95306ce281c9f08cdb35b7c | Bin 37 -> 0 bytes .../92/9b4718db99b64a38b4e8c3ec8e673976821c08 | Bin 344 -> 0 bytes .../a0/82cf7b16998b8f048e7d2bf8207d9525688a9f | Bin 90 -> 0 bytes .../d7/8abde66b1d35bdac65402f0e2cddf3a96cd377 | Bin 380 -> 0 bytes .../dc/135ebfc7f680300c981029184a492bbdfa6db3 | Bin 91 -> 0 bytes .../testdata/test_plugin.git/packed-refs | 4 - .../test_plugin.git/refs/heads/.gitkeep | 0 .../test_plugin.git/refs/tags/.gitkeep | 0 36 files changed, 240 insertions(+), 85 deletions(-) create mode 100644 internal/gittest/server.go create mode 100644 internal/testutil/fs.go create mode 100644 pkg/fanal/artifact/repo/testdata/test-repo/anothertest.txt create mode 100644 pkg/fanal/artifact/repo/testdata/test-repo/test.txt delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/HEAD delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/config delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/objects/0d/8bf3f07c3970b3e38c2cfb1c619cb86fae76d2 delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/objects/1c/1d7deed649fbecd66fab423ccd9d001bf9ff91 delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/objects/27/aaec53f92314d9438a53c703f169d2cbf5001a delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/objects/5e/fb9bc29c482e023e40e0a2b3b7e49cec842034 delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/objects/6a/c152fe2b87cb5e243414df71790a32912e778d delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/objects/c0/42cd14d2b999cade090785af47e9f8b8e342ff delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/objects/c9/06fc4a94762f8a2c77c718947143d16e4e9ec7 delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/objects/d7/937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/objects/e2/4866d1d31ddffdb27fbcf583d5deb4386d5145 delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/objects/f4/836be6497e83e13dc0cfbce7e6b973b1ea511d delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/refs/heads/master delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/refs/heads/valid-branch delete mode 100644 pkg/fanal/artifact/repo/testdata/test.git/refs/tags/v1.0.0 delete mode 100644 pkg/plugin/testdata/test_plugin.git/HEAD delete mode 100644 pkg/plugin/testdata/test_plugin.git/config delete mode 100644 pkg/plugin/testdata/test_plugin.git/description delete mode 100644 pkg/plugin/testdata/test_plugin.git/info/exclude delete mode 100644 pkg/plugin/testdata/test_plugin.git/objects/08/6aefb548a1150b765d1e163a5e542fc80bd660 delete mode 100644 pkg/plugin/testdata/test_plugin.git/objects/0a/e1413e3807e024dbc7de4129d12bdcae7dea61 delete mode 100644 pkg/plugin/testdata/test_plugin.git/objects/49/0535e047e795a5c95306ce281c9f08cdb35b7c delete mode 100644 pkg/plugin/testdata/test_plugin.git/objects/92/9b4718db99b64a38b4e8c3ec8e673976821c08 delete mode 100644 pkg/plugin/testdata/test_plugin.git/objects/a0/82cf7b16998b8f048e7d2bf8207d9525688a9f delete mode 100644 pkg/plugin/testdata/test_plugin.git/objects/d7/8abde66b1d35bdac65402f0e2cddf3a96cd377 delete mode 100644 pkg/plugin/testdata/test_plugin.git/objects/dc/135ebfc7f680300c981029184a492bbdfa6db3 delete mode 100644 pkg/plugin/testdata/test_plugin.git/packed-refs delete mode 100644 pkg/plugin/testdata/test_plugin.git/refs/heads/.gitkeep delete mode 100644 pkg/plugin/testdata/test_plugin.git/refs/tags/.gitkeep diff --git a/internal/gittest/server.go b/internal/gittest/server.go new file mode 100644 index 000000000000..277d645de241 --- /dev/null +++ b/internal/gittest/server.go @@ -0,0 +1,132 @@ +//go:build unix + +package gittest + +import ( + "errors" + "net/http/httptest" + "path/filepath" + "testing" + "time" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/sosedoff/gitkit" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/internal/testutil" +) + +var signature = &object.Signature{ + Name: "Test", + Email: "test@example.com", + When: time.Now(), +} + +func NewServer(t *testing.T, repo, dir string) *httptest.Server { + wtDir := t.TempDir() + + // git init + r, err := git.PlainInit(wtDir, false) + require.NoError(t, err) + + wt, err := r.Worktree() + require.NoError(t, err) + + testutil.CopyDir(t, dir, wtDir) + + _, err = wt.Add(".") + require.NoError(t, err) + + _, err = wt.Commit("initial commit", &git.CommitOptions{ + Author: signature, + }) + require.NoError(t, err) + + // Create a bare repository + bareDir := t.TempDir() + gitDir := filepath.Join(bareDir, repo+".git") + _, err = git.PlainClone(gitDir, true, &git.CloneOptions{URL: wtDir}) + require.NoError(t, err) + + // Set up a git server + service := gitkit.New(gitkit.Config{Dir: bareDir}) + err = service.Setup() + require.NoError(t, err) + + return httptest.NewServer(service) +} + +func Clone(t *testing.T, ts *httptest.Server, repo, worktree string) *git.Repository { + cloneOptions := git.CloneOptions{ + URL: ts.URL + "/" + repo + ".git", + } + + r, err := git.PlainClone(worktree, false, &cloneOptions) + require.NoError(t, err) + + return r +} + +func CommitAll(t *testing.T, r *git.Repository, msg string) { + w, err := r.Worktree() + require.NoError(t, err) + + _, err = w.Add(".") + require.NoError(t, err) + + _, err = w.Commit(msg, &git.CommitOptions{ + Author: signature, + }) + require.NoError(t, err) +} + +func SetTag(t *testing.T, r *git.Repository, tag string) { + h, err := r.Head() + require.NoError(t, err) + + t.Logf("git tag -a %s %s -m \"%s\"", tag, h.Hash(), tag) + _, err = r.CreateTag(tag, h.Hash(), &git.CreateTagOptions{ + Tagger: signature, + Message: tag, + }) + require.NoError(t, err) +} + +func PushTags(t *testing.T, r *git.Repository) { + t.Log("git push --tags") + err := r.Push(&git.PushOptions{ + RemoteName: "origin", + RefSpecs: []config.RefSpec{"refs/tags/*:refs/tags/*"}, + }) + + if err != nil { + if errors.Is(err, git.NoErrAlreadyUpToDate) { + return + } + require.NoError(t, err) + } +} + +func CreateRemoteBranch(t *testing.T, r *git.Repository, branchName string) { + wt, err := r.Worktree() + require.NoError(t, err) + + ref := plumbing.NewBranchReferenceName(branchName) + err = wt.Checkout(&git.CheckoutOptions{ + Branch: ref, + Create: true, + }) + require.NoError(t, err) + defer func() { + require.NoError(t, wt.Checkout(&git.CheckoutOptions{})) + }() + + err = r.Push(&git.PushOptions{ + RemoteName: "origin", + RefSpecs: []config.RefSpec{config.RefSpec(ref + ":" + ref)}, + }) + require.NoError(t, err) +} diff --git a/internal/testutil/fs.go b/internal/testutil/fs.go new file mode 100644 index 000000000000..4a1162aa1bab --- /dev/null +++ b/internal/testutil/fs.go @@ -0,0 +1,36 @@ +package testutil + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +// CopyDir copies the directory content from src to dst. +// It supports only simple cases for testing. +func CopyDir(t *testing.T, src, dst string) { + srcInfo, err := os.Stat(src) + require.NoError(t, err) + + err = os.MkdirAll(dst, srcInfo.Mode()) + require.NoError(t, err) + + entries, err := os.ReadDir(src) + require.NoError(t, err) + + for _, entry := range entries { + srcPath := filepath.Join(src, entry.Name()) + dstPath := filepath.Join(dst, entry.Name()) + + if entry.IsDir() { + CopyDir(t, srcPath, dstPath) + } else { + _, err = fsutils.CopyFile(srcPath, dstPath) + require.NoError(t, err) + } + } +} diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go index 00ea53ef58d3..0e4c8ee39d4b 100644 --- a/pkg/fanal/artifact/repo/git_test.go +++ b/pkg/fanal/artifact/repo/git_test.go @@ -7,10 +7,11 @@ import ( "net/http/httptest" "testing" - "github.com/sosedoff/gitkit" + "github.com/go-git/go-git/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/gittest" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/walker" @@ -19,26 +20,29 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret" ) -func setupGitServer() (*httptest.Server, error) { - service := gitkit.New(gitkit.Config{ - Dir: "./testdata", - AutoCreate: false, - }) +func setupGitRepository(t *testing.T, repo, dir string) (*httptest.Server, *git.Repository) { + gs := gittest.NewServer(t, repo, dir) - if err := service.Setup(); err != nil { - return nil, err - } + worktree := t.TempDir() + r := gittest.Clone(t, gs, repo, worktree) + + // Branch + gittest.CreateRemoteBranch(t, r, "valid-branch") - ts := httptest.NewServer(service) + // Tag + gittest.SetTag(t, r, "v1.0.0") + gittest.PushTags(t, r) - return ts, nil + return gs, r } func TestNewArtifact(t *testing.T) { - ts, err := setupGitServer() - require.NoError(t, err) + ts, repo := setupGitRepository(t, "test-repo", "testdata/test-repo") defer ts.Close() + head, err := repo.Head() + require.NoError(t, err) + type args struct { target string c cache.ArtifactCache @@ -55,7 +59,7 @@ func TestNewArtifact(t *testing.T) { { name: "remote repo", args: args{ - target: ts.URL + "/test.git", + target: ts.URL + "/test-repo.git", c: nil, noProgress: false, }, @@ -71,9 +75,9 @@ func TestNewArtifact(t *testing.T) { assertion: assert.NoError, }, { - name: "happy noProgress", + name: "no progress", args: args{ - target: ts.URL + "/test.git", + target: ts.URL + "/test-repo.git", c: nil, noProgress: true, }, @@ -82,7 +86,7 @@ func TestNewArtifact(t *testing.T) { { name: "branch", args: args{ - target: ts.URL + "/test.git", + target: ts.URL + "/test-repo.git", c: nil, repoBranch: "valid-branch", }, @@ -91,7 +95,7 @@ func TestNewArtifact(t *testing.T) { { name: "tag", args: args{ - target: ts.URL + "/test.git", + target: ts.URL + "/test-repo.git", c: nil, repoTag: "v1.0.0", }, @@ -100,9 +104,9 @@ func TestNewArtifact(t *testing.T) { { name: "commit", args: args{ - target: ts.URL + "/test.git", + target: ts.URL + "/test-repo.git", c: nil, - repoCommit: "6ac152fe2b87cb5e243414df71790a32912e778d", + repoCommit: head.String(), }, assertion: assert.NoError, }, @@ -131,7 +135,7 @@ func TestNewArtifact(t *testing.T) { { name: "invalid branch", args: args{ - target: ts.URL + "/test.git", + target: ts.URL + "/test-repo.git", c: nil, repoBranch: "invalid-branch", }, @@ -142,7 +146,7 @@ func TestNewArtifact(t *testing.T) { { name: "invalid tag", args: args{ - target: ts.URL + "/test.git", + target: ts.URL + "/test-repo.git", c: nil, repoTag: "v1.0.9", }, @@ -153,7 +157,7 @@ func TestNewArtifact(t *testing.T) { { name: "invalid commit", args: args{ - target: ts.URL + "/test.git", + target: ts.URL + "/test-repo.git", c: nil, repoCommit: "6ac152fe2b87cb5e243414df71790a32912e778e", }, @@ -178,8 +182,7 @@ func TestNewArtifact(t *testing.T) { } func TestArtifact_Inspect(t *testing.T) { - ts, err := setupGitServer() - require.NoError(t, err) + ts, _ := setupGitRepository(t, "test-repo", "testdata/test-repo") defer ts.Close() tests := []struct { @@ -190,9 +193,9 @@ func TestArtifact_Inspect(t *testing.T) { }{ { name: "happy path", - rawurl: ts.URL + "/test.git", + rawurl: ts.URL + "/test-repo.git", want: artifact.Reference{ - Name: ts.URL + "/test.git", + Name: ts.URL + "/test-repo.git", Type: artifact.TypeRepository, ID: "sha256:6a89d4fcd50f840a79da64523c255da80171acd3d286df2acc60056c778d9304", BlobIDs: []string{ diff --git a/pkg/fanal/artifact/repo/testdata/test-repo/anothertest.txt b/pkg/fanal/artifact/repo/testdata/test-repo/anothertest.txt new file mode 100644 index 000000000000..f4836be6497e --- /dev/null +++ b/pkg/fanal/artifact/repo/testdata/test-repo/anothertest.txt @@ -0,0 +1 @@ +this is another text file. \ No newline at end of file diff --git a/pkg/fanal/artifact/repo/testdata/test-repo/test.txt b/pkg/fanal/artifact/repo/testdata/test-repo/test.txt new file mode 100644 index 000000000000..c042cd14d2b9 --- /dev/null +++ b/pkg/fanal/artifact/repo/testdata/test-repo/test.txt @@ -0,0 +1 @@ +this is a text file. \ No newline at end of file diff --git a/pkg/fanal/artifact/repo/testdata/test.git/HEAD b/pkg/fanal/artifact/repo/testdata/test.git/HEAD deleted file mode 100644 index cb089cd89a7d..000000000000 --- a/pkg/fanal/artifact/repo/testdata/test.git/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/master diff --git a/pkg/fanal/artifact/repo/testdata/test.git/config b/pkg/fanal/artifact/repo/testdata/test.git/config deleted file mode 100644 index e6da231579bc..000000000000 --- a/pkg/fanal/artifact/repo/testdata/test.git/config +++ /dev/null @@ -1,6 +0,0 @@ -[core] - repositoryformatversion = 0 - filemode = true - bare = true - ignorecase = true - precomposeunicode = true diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/0d/8bf3f07c3970b3e38c2cfb1c619cb86fae76d2 b/pkg/fanal/artifact/repo/testdata/test.git/objects/0d/8bf3f07c3970b3e38c2cfb1c619cb86fae76d2 deleted file mode 100644 index 9baa2c164c9e..000000000000 --- a/pkg/fanal/artifact/repo/testdata/test.git/objects/0d/8bf3f07c3970b3e38c2cfb1c619cb86fae76d2 +++ /dev/null @@ -1,2 +0,0 @@ -xAj0s+X˲ `,"^cG Ї{MGxb$nZӄ3dVul|]!rvLy 'WQ>9/DjԺYϥrh߼1\~un[ \ 4[f'}:*? -mb~T \ No newline at end of file diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/1c/1d7deed649fbecd66fab423ccd9d001bf9ff91 b/pkg/fanal/artifact/repo/testdata/test.git/objects/1c/1d7deed649fbecd66fab423ccd9d001bf9ff91 deleted file mode 100644 index 1c1c8f2a4f50c6b83a6645b026b6f2c722d2e748..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59 zcmb8#W%j<>n*Fxv)5_&?<#!Z>Hon+ P#njD0l#xMWkMec^NU0R} diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/27/aaec53f92314d9438a53c703f169d2cbf5001a b/pkg/fanal/artifact/repo/testdata/test.git/objects/27/aaec53f92314d9438a53c703f169d2cbf5001a deleted file mode 100644 index 1e1970b9b3e25d56678d5ce984f5ed0d98cac0f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86 zcmV-c0IC0Y0V^p=O;s?rWH2-^Ff%bxNX*MG$w)0KNi8nXE2$`9_|lyH%(Jffq3wb5 sd!9erS-kO8pe$4=T+IQevm%#v&OCLGlf8Al`^z6Y9y|R90QS@-;8*}CxBvhE diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/5e/fb9bc29c482e023e40e0a2b3b7e49cec842034 b/pkg/fanal/artifact/repo/testdata/test.git/objects/5e/fb9bc29c482e023e40e0a2b3b7e49cec842034 deleted file mode 100644 index d615c02a6b36669ca11cf0c4d92dde442130dcbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53 zcmb8#W%j<>n*Fxv)5_&?<#!Z>Hoon JAu&woJ^;~d6Yu~4 diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/6a/c152fe2b87cb5e243414df71790a32912e778d b/pkg/fanal/artifact/repo/testdata/test.git/objects/6a/c152fe2b87cb5e243414df71790a32912e778d deleted file mode 100644 index f40c82e685b2..000000000000 --- a/pkg/fanal/artifact/repo/testdata/test.git/objects/6a/c152fe2b87cb5e243414df71790a32912e778d +++ /dev/null @@ -1,3 +0,0 @@ -xK0DgS>Br5Xqp$mr@-Qym -~dc6.@&*&˔OFD6}qsz6%@;FGVXRs3o cW(<~Kj}FmncH0g8J2biU_keG2K7S-O2)8#l4FCWD diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/d7/937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b b/pkg/fanal/artifact/repo/testdata/test.git/objects/d7/937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b deleted file mode 100644 index c948dc6ea8d0cd5ae18cfce1442ceac08a49b581..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165 zcmV;W09yZe0i}*j3c@fDMqTF=*$a|CGmVIN1i=$bXC}7L*pjAreY^1l?!NaHkB{b3 z#-^Q5hgLz5XYFY56%;DVqHdGKZmU!usm?8;|C|y*LC^r?fxn_PA8hnP1|^e&VYlUkLK)++U@?d T$v+K+%op8cZBBgweIiasKjBa- diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/e2/4866d1d31ddffdb27fbcf583d5deb4386d5145 b/pkg/fanal/artifact/repo/testdata/test.git/objects/e2/4866d1d31ddffdb27fbcf583d5deb4386d5145 deleted file mode 100644 index 5f1213f2ea4c4104e96cda98384a0f3eb001a3c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53 zcmV-50LuS(0V^p=O;s>9V=y!@Ff%bxC`m0Y(JQGaVL0G)R^-yonWye?vbU~xfB9p_ LW2gTBQz8=1`p*`L diff --git a/pkg/fanal/artifact/repo/testdata/test.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/pkg/fanal/artifact/repo/testdata/test.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 deleted file mode 100644 index 711223894375fe1186ac5bfffdc48fb1fa1e65cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15 Wcmb4V<^eUELH%b#Jv2HjMO59lGKV4g|y6^R6PL4{0qQ%+7ogB diff --git a/pkg/fanal/artifact/repo/testdata/test.git/refs/heads/master b/pkg/fanal/artifact/repo/testdata/test.git/refs/heads/master deleted file mode 100644 index 21969947cb30..000000000000 --- a/pkg/fanal/artifact/repo/testdata/test.git/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0d8bf3f07c3970b3e38c2cfb1c619cb86fae76d2 diff --git a/pkg/fanal/artifact/repo/testdata/test.git/refs/heads/valid-branch b/pkg/fanal/artifact/repo/testdata/test.git/refs/heads/valid-branch deleted file mode 100644 index 22301d650ca1..000000000000 --- a/pkg/fanal/artifact/repo/testdata/test.git/refs/heads/valid-branch +++ /dev/null @@ -1 +0,0 @@ -d7937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b diff --git a/pkg/fanal/artifact/repo/testdata/test.git/refs/tags/v1.0.0 b/pkg/fanal/artifact/repo/testdata/test.git/refs/tags/v1.0.0 deleted file mode 100644 index bff68b69688e..000000000000 --- a/pkg/fanal/artifact/repo/testdata/test.git/refs/tags/v1.0.0 +++ /dev/null @@ -1 +0,0 @@ -c906fc4a94762f8a2c77c718947143d16e4e9ec7 diff --git a/pkg/plugin/manager_unix_test.go b/pkg/plugin/manager_unix_test.go index 0fd024ff3246..fca1c8fac2ad 100644 --- a/pkg/plugin/manager_unix_test.go +++ b/pkg/plugin/manager_unix_test.go @@ -14,11 +14,12 @@ import ( "testing" "time" + "github.com/go-git/go-git/v5" "github.com/google/go-containerregistry/pkg/v1" - "github.com/sosedoff/gitkit" // Not work on Windows "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/gittest" "github.com/aquasecurity/trivy/pkg/clock" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" @@ -26,24 +27,43 @@ import ( "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) -func setupGitServer() (*httptest.Server, error) { - service := gitkit.New(gitkit.Config{ - Dir: "./testdata", - AutoCreate: false, - }) +func setupGitRepository(t *testing.T, repo, dir string) *httptest.Server { + gs := gittest.NewServer(t, repo, dir) - if err := service.Setup(); err != nil { - return nil, err - } + worktree := t.TempDir() + r := gittest.Clone(t, gs, repo, worktree) + + // git tag + gittest.SetTag(t, r, "v0.2.0") + + // git commit + modifyManifest(t, worktree, "0.3.0") + gittest.CommitAll(t, r, "bump up to 0.3.0") + + err := r.Push(&git.PushOptions{}) + require.NoError(t, err) + + // git tag + gittest.SetTag(t, r, "v0.3.0") - ts := httptest.NewServer(service) + // git push --tags + gittest.PushTags(t, r) - return ts, nil + return gs } -func TestManager_Install(t *testing.T) { - gs, err := setupGitServer() +func modifyManifest(t *testing.T, worktree, version string) { + manifestPath := filepath.Join(worktree, "plugin.yaml") + b, err := os.ReadFile(manifestPath) + require.NoError(t, err) + + b = bytes.ReplaceAll(b, []byte("0.2.0"), []byte(version)) + err = os.WriteFile(manifestPath, b, 0644) require.NoError(t, err) +} + +func TestManager_Install(t *testing.T) { + gs := setupGitRepository(t, "test_plugin", "testdata/test_plugin") t.Cleanup(gs.Close) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -76,8 +96,8 @@ func TestManager_Install(t *testing.T) { }, }, } - wantPluginWithVersion := wantPlugin - wantPluginWithVersion.Version = "0.1.0" + wantPluginWithGit := wantPlugin + wantPluginWithGit.Version = "0.3.0" wantLogs := `2021-08-25T12:20:30Z INFO Installing the plugin... src="%s" 2021-08-25T12:20:30Z INFO Plugin successfully installed name="test_plugin" version="%s" @@ -109,16 +129,16 @@ func TestManager_Install(t *testing.T) { { name: "git", pluginName: "git::" + gs.URL + "/test_plugin.git", - want: wantPlugin, + want: wantPluginWithGit, wantFile: ".trivy/plugins/test_plugin/test.sh", - wantLogs: fmt.Sprintf(wantLogs, "git::"+gs.URL+"/test_plugin.git", "0.2.0"), + wantLogs: fmt.Sprintf(wantLogs, "git::"+gs.URL+"/test_plugin.git", "0.3.0"), }, { name: "with version", - pluginName: "git::" + gs.URL + "/test_plugin.git@v0.1.0", - want: wantPluginWithVersion, + pluginName: "git::" + gs.URL + "/test_plugin.git@v0.2.0", + want: wantPlugin, wantFile: ".trivy/plugins/test_plugin/test.sh", - wantLogs: fmt.Sprintf(wantLogs, "git::"+gs.URL+"/test_plugin.git", "0.1.0"), + wantLogs: fmt.Sprintf(wantLogs, "git::"+gs.URL+"/test_plugin.git", "0.2.0"), }, { name: "via index", diff --git a/pkg/plugin/testdata/test_plugin.git/HEAD b/pkg/plugin/testdata/test_plugin.git/HEAD deleted file mode 100644 index b870d82622c1..000000000000 --- a/pkg/plugin/testdata/test_plugin.git/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/main diff --git a/pkg/plugin/testdata/test_plugin.git/config b/pkg/plugin/testdata/test_plugin.git/config deleted file mode 100644 index 9512a718938b..000000000000 --- a/pkg/plugin/testdata/test_plugin.git/config +++ /dev/null @@ -1,8 +0,0 @@ -[core] - repositoryformatversion = 0 - filemode = true - bare = true - ignorecase = true - precomposeunicode = true -[remote "origin"] - url = /Users/teppei/src/github.com/aquasecurity/trivy/pkg/plugin/testdata/test_plugin diff --git a/pkg/plugin/testdata/test_plugin.git/description b/pkg/plugin/testdata/test_plugin.git/description deleted file mode 100644 index 498b267a8c78..000000000000 --- a/pkg/plugin/testdata/test_plugin.git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/pkg/plugin/testdata/test_plugin.git/info/exclude b/pkg/plugin/testdata/test_plugin.git/info/exclude deleted file mode 100644 index a5196d1be8fb..000000000000 --- a/pkg/plugin/testdata/test_plugin.git/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/pkg/plugin/testdata/test_plugin.git/objects/08/6aefb548a1150b765d1e163a5e542fc80bd660 b/pkg/plugin/testdata/test_plugin.git/objects/08/6aefb548a1150b765d1e163a5e542fc80bd660 deleted file mode 100644 index e7d696256b86d9c3323ac0740015d918dae20479..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 147 zcmV;E0Brww0Zooe4#F@DL|Nw)R`-O05K?m)}bpc2I(oI`pUXp0py9{vdRpd=i8&5in`$3aPP4!^isNtVPhcckT1 zw3u%pIWY=g4?>ofdcUbQK@3>-<@wQ=mdyaV^>Xe`No+v(7qo2Mnz`2htT&%uLTVfd BMbrQQ diff --git a/pkg/plugin/testdata/test_plugin.git/objects/0a/e1413e3807e024dbc7de4129d12bdcae7dea61 b/pkg/plugin/testdata/test_plugin.git/objects/0a/e1413e3807e024dbc7de4129d12bdcae7dea61 deleted file mode 100644 index b16c882bc30f..000000000000 --- a/pkg/plugin/testdata/test_plugin.git/objects/0a/e1413e3807e024dbc7de4129d12bdcae7dea61 +++ /dev/null @@ -1,3 +0,0 @@ -xM -0 S[7a}ɺ -#iE޵(s,>-0!b C!)A1$$~h  AI ZI,\:r*{,A8'oMhd^ݩ [Bi \ No newline at end of file diff --git a/pkg/plugin/testdata/test_plugin.git/objects/49/0535e047e795a5c95306ce281c9f08cdb35b7c b/pkg/plugin/testdata/test_plugin.git/objects/49/0535e047e795a5c95306ce281c9f08cdb35b7c deleted file mode 100644 index 964cbf82f49bbc0825b9a3b0ce88f1ab88927e94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37 tcmbGR=C3>B9+OaM$w5k~+3 diff --git a/pkg/plugin/testdata/test_plugin.git/objects/92/9b4718db99b64a38b4e8c3ec8e673976821c08 b/pkg/plugin/testdata/test_plugin.git/objects/92/9b4718db99b64a38b4e8c3ec8e673976821c08 deleted file mode 100644 index ba6cfbad6f8c1f126ac73ae05fca5053271761db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 344 zcmV-e0jK_W0hN$TZ-PJ+g}a_#F?&;;VL&ixo1#!4h4BFt!wyCm1r!nLgI{0Jbk|K! za+7nv4oJIIf`-f{TcYpNR- zg|2Ht*D|Q;`78a-0rWAod{mffuVpuy(>t3HTH3)^?qoi8yu4?8lsR@wKDEr`m+hg> zCh3y#RMAWh;A#C)In)UyZ*h$Vh}{!fC={rVJ`}c*p?BA;Zk>8Pn-SVZGhBq4g{h}Y z0QPjLYn4r;g>g!Pfhs$*twD<0Jhs~?3#!%munJUDR7&Z1sa4fa$*pXH+%j(SIX#Ex q^)&`?vz$+hQ8sEAVK6i>Ff%bxD99;I&&<=SOw7$;;K+Kv)nlP3cUi2Qm{nYe w{t51D2|#7$rltxdsl_FF#Tg8qtfmj#pHE$SGMMe0hRl49vzw!906R(?UD|Xgng9R* diff --git a/pkg/plugin/testdata/test_plugin.git/objects/d7/8abde66b1d35bdac65402f0e2cddf3a96cd377 b/pkg/plugin/testdata/test_plugin.git/objects/d7/8abde66b1d35bdac65402f0e2cddf3a96cd377 deleted file mode 100644 index 78b94e8996dd4a8bb2cdcb6c052a4c599e3a7813..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 380 zcmV-?0fYW{0hN$TZ<|0Ag}a_#G5e_YJb27VZB=8aO>L&Nfi`6~49pk|h7h8}{`G-G z-F4F=UFqCUI#*Xmb!}UX(8lb;IFu!nh!LgT96Ow|oM~jS(k)1?89{ErZDKo&Nv=UJ zhw?H)PIzv)m=!taxnoJna;s#DmWq{*OS$VXf)!#wK8>etfalA@RTGE8Pk-H4)5@w@ zsIL72v5Rd+DP|PjAqyce(doUh{O_RY&0}TY$5~#m2?dPfEsVpBSS9H(c;f)3ScEBR zwyRl(Y5Zt*dqqXJ|NZM6)hUjW^UTb$j`laY^Xsj7K3yJbt`Ev+=5Gyv!~M61NJN>P z{CtsjNWCTej5KJDzAL-QO)y;xr`1blwx;4H*SN|25Q`+R0i5aKyC~wsJId24wf_`- z!lWguq3HLPcR!APt$neJ&25DXS(c2h} acGpAn8NAmK2>)MY0JIAVK6i>Ff%bxD99;I&&<=SOw7$;;Ckq2XTknJ<@WJ=j+z&> x@2snRl?YU3ZfdGfl3HA%SDeA%$!hw*{rS|TCxh9}X~@jyIJ-Hz1^}iS9{935F311? diff --git a/pkg/plugin/testdata/test_plugin.git/packed-refs b/pkg/plugin/testdata/test_plugin.git/packed-refs deleted file mode 100644 index 00a4f957a23f..000000000000 --- a/pkg/plugin/testdata/test_plugin.git/packed-refs +++ /dev/null @@ -1,4 +0,0 @@ -# pack-refs with: peeled fully-peeled sorted -d78abde66b1d35bdac65402f0e2cddf3a96cd377 refs/heads/main -929b4718db99b64a38b4e8c3ec8e673976821c08 refs/tags/v0.1.0 -d78abde66b1d35bdac65402f0e2cddf3a96cd377 refs/tags/v0.2.0 diff --git a/pkg/plugin/testdata/test_plugin.git/refs/heads/.gitkeep b/pkg/plugin/testdata/test_plugin.git/refs/heads/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/pkg/plugin/testdata/test_plugin.git/refs/tags/.gitkeep b/pkg/plugin/testdata/test_plugin.git/refs/tags/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 From bab16b88adc5f635df8aaaf1e449bf71c75d3070 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 08:11:13 +0400 Subject: [PATCH 130/352] chore(deps): bump the common group with 5 updates (#6842) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 25 ++++++++++++------------ go.sum | 60 +++++++++++++++++++++++++++------------------------------- 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index a0275a82c98e..8fe91fdb969e 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/Masterminds/sprig/v3 v3.2.3 github.com/NYTimes/gziphandler v1.1.1 github.com/alecthomas/chroma v0.10.0 - github.com/alicebob/miniredis/v2 v2.32.1 + github.com/alicebob/miniredis/v2 v2.33.0 github.com/antchfx/htmlquery v1.3.1 github.com/apparentlymart/go-cidr v1.1.0 github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 @@ -60,7 +60,7 @@ require ( github.com/google/wire v0.6.0 github.com/hashicorp/go-getter v1.7.4 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-retryablehttp v0.7.6 + github.com/hashicorp/go-retryablehttp v0.7.7 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/golang-lru/v2 v2.0.7 @@ -90,7 +90,7 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 github.com/moby/buildkit v0.13.2 - github.com/open-policy-agent/opa v0.64.1 + github.com/open-policy-agent/opa v0.65.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/openvex/go-vex v0.2.5 @@ -107,7 +107,7 @@ require ( github.com/spf13/cast v1.6.0 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.18.2 + github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.31.0 github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0 @@ -131,14 +131,13 @@ require ( helm.sh/helm/v3 v3.15.1 k8s.io/api v0.30.1 k8s.io/utils v0.0.0-20231127182322-b307cd553661 - modernc.org/sqlite v1.29.10 + modernc.org/sqlite v1.30.0 sigs.k8s.io/yaml v1.4.0 ) require ( cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/compute v1.25.1 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/storage v1.39.1 // indirect dario.cat/mergo v1.0.0 // indirect @@ -257,7 +256,7 @@ require ( github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/analysis v0.23.0 // indirect @@ -339,14 +338,14 @@ require ( github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/prometheus/client_golang v1.19.0 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect @@ -387,15 +386,15 @@ require ( go.opentelemetry.io/otel/metric v1.27.0 // indirect go.opentelemetry.io/otel/sdk v1.27.0 // indirect go.opentelemetry.io/otel/trace v1.27.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.19.0 // indirect google.golang.org/api v0.172.0 // indirect - google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect @@ -415,7 +414,7 @@ require ( k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/kubectl v0.30.0 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect - modernc.org/libc v1.49.3 // indirect + modernc.org/libc v1.50.9 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect modernc.org/strutil v1.2.0 // indirect diff --git a/go.sum b/go.sum index f5e2ad6aadfa..5f07e4b9c178 100644 --- a/go.sum +++ b/go.sum @@ -175,13 +175,12 @@ cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63 cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= -cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= -cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= @@ -730,8 +729,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= -github.com/alicebob/miniredis/v2 v2.32.1 h1:Bz7CciDnYSaa0mX5xODh6GUITRSx+cVhjNoOR4JssBo= -github.com/alicebob/miniredis/v2 v2.32.1/go.mod h1:AqkLNAfUm0K07J28hnAyyQKf/x0YkCY/g5DCtuL01Mw= +github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA= +github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= @@ -1266,8 +1265,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -1546,8 +1545,8 @@ github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1: github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.6 h1:TwRYfx2z2C4cLbXmT8I5PgP/xmuqASDyiVuGYfs9GZM= -github.com/hashicorp/go-retryablehttp v0.7.6/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= @@ -1859,8 +1858,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= -github.com/open-policy-agent/opa v0.64.1 h1:n8IJTYlFWzqiOYx+JiawbErVxiqAyXohovcZxYbskxQ= -github.com/open-policy-agent/opa v0.64.1/go.mod h1:j4VeLorVpKipnkQ2TDjWshEuV3cvP/rHzQhYaraUXZY= +github.com/open-policy-agent/opa v0.65.0 h1:wnEU0pEk80YjFi3yoDbFTMluyNssgPI4VJNJetD9a4U= +github.com/open-policy-agent/opa v0.65.0/go.mod h1:CNoLL44LuCH1Yot/zoeZXRKFylQtCJV+oGFiP2TeeEc= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -1904,8 +1903,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.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/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 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/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= @@ -1942,8 +1941,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -2080,8 +2079,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -2238,8 +2237,8 @@ go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39S go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -2454,8 +2453,8 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2483,7 +2482,6 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2813,8 +2811,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -3139,16 +3135,16 @@ lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk= -modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk= +modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA= -modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI= +modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs= +modernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= @@ -3164,8 +3160,8 @@ modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= -modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg= -modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo= +modernc.org/libc v1.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk= +modernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= @@ -3182,8 +3178,8 @@ modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/sqlite v1.29.10 h1:3u93dz83myFnMilBGCOLbr+HjklS6+5rJLx4q86RDAg= -modernc.org/sqlite v1.29.10/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA= +modernc.org/sqlite v1.30.0 h1:8YhPUs/HTnlEgErn/jSYQTwHN/ex8CjHHjg+K9iG7LM= +modernc.org/sqlite v1.30.0/go.mod h1:cgkTARJ9ugeXSNaLBPK3CqbOe7Ec7ZhWPoMFGldEYEw= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= From 8dd076a768d868599787dc644bde9380285e16c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 09:09:00 +0400 Subject: [PATCH 131/352] chore(deps): bump the aws group across 1 directory with 7 updates (#6837) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 18 +++++++++--------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 8fe91fdb969e..fd6a361f2c13 100644 --- a/go.mod +++ b/go.mod @@ -32,13 +32,13 @@ require ( github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7 github.com/aws/aws-sdk-go-v2 v1.27.0 - github.com/aws/aws-sdk-go-v2/config v1.27.15 - github.com/aws/aws-sdk-go-v2/credentials v1.17.15 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.20 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.161.3 - github.com/aws/aws-sdk-go-v2/service/ecr v1.28.2 - github.com/aws/aws-sdk-go-v2/service/s3 v1.54.2 - github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 + github.com/aws/aws-sdk-go-v2/config v1.27.16 + github.com/aws/aws-sdk-go-v2/credentials v1.17.16 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.21 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.162.0 + github.com/aws/aws-sdk-go-v2/service/ecr v1.28.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3 + github.com/aws/aws-sdk-go-v2/service/sts v1.28.10 github.com/aws/smithy-go v1.20.2 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 @@ -213,8 +213,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/service/sns v1.26.6 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.9 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3 // indirect github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect diff --git a/go.sum b/go.sum index 5f07e4b9c178..4ddad3871334 100644 --- a/go.sum +++ b/go.sum @@ -799,14 +799,14 @@ github.com/aws/aws-sdk-go-v2 v1.27.0 h1:7bZWKoXhzI+mMR/HjdMx8ZCC5+6fY0lS5tr0bbgi github.com/aws/aws-sdk-go-v2 v1.27.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= -github.com/aws/aws-sdk-go-v2/config v1.27.15 h1:uNnGLZ+DutuNEkuPh6fwqK7LpEiPmzb7MIMA1mNWEUc= -github.com/aws/aws-sdk-go-v2/config v1.27.15/go.mod h1:7j7Kxx9/7kTmL7z4LlhwQe63MYEE5vkVV6nWg4ZAI8M= -github.com/aws/aws-sdk-go-v2/credentials v1.17.15 h1:YDexlvDRCA8ems2T5IP1xkMtOZ1uLJOCJdTr0igs5zo= -github.com/aws/aws-sdk-go-v2/credentials v1.17.15/go.mod h1:vxHggqW6hFNaeNC0WyXS3VdyjcV0a4KMUY4dKJ96buU= +github.com/aws/aws-sdk-go-v2/config v1.27.16 h1:knpCuH7laFVGYTNd99Ns5t+8PuRjDn4HnnZK48csipM= +github.com/aws/aws-sdk-go-v2/config v1.27.16/go.mod h1:vutqgRhDUktwSge3hrC3nkuirzkJ4E/mLj5GvI0BQas= +github.com/aws/aws-sdk-go-v2/credentials v1.17.16 h1:7d2QxY83uYl0l58ceyiSpxg9bSbStqBC6BeEeHEchwo= +github.com/aws/aws-sdk-go-v2/credentials v1.17.16/go.mod h1:Ae6li/6Yc6eMzysRL2BXlPYvnrLLBg3D11/AmOjw50k= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 h1:dQLK4TjtnlRGb0czOht2CevZ5l6RSyRWAnKeGd7VAFE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3/go.mod h1:TL79f2P6+8Q7dTsILpiVST+AL9lkF6PPGI167Ny0Cjw= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.20 h1:NCM9wYaJCmlIWZSO/JwUEveKf0NCvsSgo9V9BwOAolo= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.20/go.mod h1:dmxIx3qriuepxqZgFeFMitFuftWPB94+MZv/6Btpth4= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.21 h1:1v8Ii0MRVGYB/sdhkbxrtolCA7Tp+lGh+5OJTs5vmZ8= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.21/go.mod h1:cxdd1rc8yxCjKz28hi30XN1jDXr2DxZvD44vLxTz/bg= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 h1:lf/8VTF2cM+N4SLzaYJERKEWAXq8MOMpZfU6wEPWsPk= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7/go.mod h1:4SjkU7QiqK2M9oozyMzfZ/23LmUY+h3oFqhdeP5OMiI= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 h1:4OYVp0705xu8yjdyoWix0r9wPIRXnIzzOoUpQVHIJ/g= @@ -839,10 +839,10 @@ github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 h1:XKO0BswTDeZMLDBd/b5pCEZ github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8/go.mod h1:N5tqZcYMM0N1PN7UQYJNWuGyO886OfnMhf/3MAbqMcI= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.161.3 h1:l0mvKOGm25yo/Fy+Y/08Cm4aTA4XmnIuq4ppy+shfMI= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.161.3/go.mod h1:iJ2sQeUTkjNp3nL7kE/Bav0xXYhtiRCRP5ZXk4jFhCQ= -github.com/aws/aws-sdk-go-v2/service/ecr v1.28.2 h1:xUpMnRZonKfrHaNLC77IMpWZSUMRRXIi6IU5EhAPsrM= -github.com/aws/aws-sdk-go-v2/service/ecr v1.28.2/go.mod h1:X52zjAVRaXklEU1TE/wO8kyyJSr9cJx9ZsqliWbyRys= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.162.0 h1:A1YMX7uMzXhfIEL9zc5049oQgSaH4ZeXx/sOth0dk/I= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.162.0/go.mod h1:iJ2sQeUTkjNp3nL7kE/Bav0xXYhtiRCRP5ZXk4jFhCQ= +github.com/aws/aws-sdk-go-v2/service/ecr v1.28.3 h1:NsP8PA4Kw1sA6UKl3ZFRIcA9dWomePbmoRIvfOl+HKs= +github.com/aws/aws-sdk-go-v2/service/ecr v1.28.3/go.mod h1:X52zjAVRaXklEU1TE/wO8kyyJSr9cJx9ZsqliWbyRys= github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6 h1:Sc2mLjyA1R8z2l705AN7Wr7QOlnUxVnGPJeDIVyUSrs= github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6/go.mod h1:LzHcyOEvaLjbc5e+fP/KmPWBr+h/Ef+EHvnf1Pzo368= github.com/aws/aws-sdk-go-v2/service/efs v1.28.1 h1:dKtJBzCIew4/VDsYgrx6v140cIpQVoe93kCNniYATtE= @@ -885,20 +885,20 @@ github.com/aws/aws-sdk-go-v2/service/rds v1.66.1 h1:TafjIpDW/+l7s+f3EIONaFsNvNfw github.com/aws/aws-sdk-go-v2/service/rds v1.66.1/go.mod h1:MYzRMSdY70kcS8AFg0aHmk/xj6VAe0UfaCCoLrBWPow= github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7 h1:k4WaqQ7LHSGrSftCRXTRLv7WaozXu+fZ1jdisQSR2eU= github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7/go.mod h1:8hU0Ax6q6QA+jrMcWTE0A4YH594MQoWP3EzGO3GH5Dw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.54.2 h1:gYSJhNiOF6J9xaYxu2NFNstoiNELwt0T9w29FxSfN+Y= -github.com/aws/aws-sdk-go-v2/service/s3 v1.54.2/go.mod h1:739CllldowZiPPsDFcJHNF4FXrVxaSGVnZ9Ez9Iz9hc= +github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3 h1:57NtjG+WLims0TxIQbjTqebZUKDM03DfM11ANAekW0s= +github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3/go.mod h1:739CllldowZiPPsDFcJHNF4FXrVxaSGVnZ9Ez9Iz9hc= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 h1:dPCRgAL4WD9tSMaDglRNGOiAtSTjkwNiUW5GDpWFfHA= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0/go.mod h1:4Ae1NCLK6ghmjzd45Tc33GgCKhUWD2ORAlULtMO1Cbs= github.com/aws/aws-sdk-go-v2/service/sns v1.26.6 h1:w2YwF8889ardGU3Y0qZbJ4Zzh+Q/QqKZ4kwkK7JFvnI= github.com/aws/aws-sdk-go-v2/service/sns v1.26.6/go.mod h1:IrcbquqMupzndZ20BXxDxjM7XenTRhbwBOetk4+Z5oc= github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6 h1:UdbDTllc7cmusTTMy1dcTrYKRl4utDEsmKh9ZjvhJCc= github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6/go.mod h1:mCUv04gd/7g+/HNzDB4X6dzJuygji0ckvB3Lg/TdG5Y= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 h1:Kv1hwNG6jHC/sxMTe5saMjH6t6ZLkgfvVxyEjfWL1ks= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.8/go.mod h1:c1qtZUWtygI6ZdvKppzCSXsDOq5I4luJPZ0Ud3juFCA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 h1:nWBZ1xHCF+A7vv9sDzJOq4NWIdzFYm0kH7Pr4OjHYsQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2/go.mod h1:9lmoVDVLz/yUZwLaQ676TK02fhCu4+PgRSmMaKR1ozk= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 h1:Qp6Boy0cGDloOE3zI6XhNLNZgjNS8YmiFQFHe71SaW0= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.9/go.mod h1:0Aqn1MnEuitqfsCNyKsdKLhDUOr4txD/g19EfiUqgws= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.9 h1:aD7AGQhvPuAxlSUfo0CWU7s6FpkbyykMhGYMvlqTjVs= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.9/go.mod h1:c1qtZUWtygI6ZdvKppzCSXsDOq5I4luJPZ0Ud3juFCA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3 h1:Pav5q3cA260Zqez42T9UhIlsd9QeypszRPwC9LdSSsQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3/go.mod h1:9lmoVDVLz/yUZwLaQ676TK02fhCu4+PgRSmMaKR1ozk= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.10 h1:69tpbPED7jKPyzMcrwSvhWcJ9bPnZsZs18NT40JwM0g= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.10/go.mod h1:0Aqn1MnEuitqfsCNyKsdKLhDUOr4txD/g19EfiUqgws= github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1 h1:pqxn3fcZDgWmo8GMUjlxVBdakcGo0AeUb7mjX33pJIQ= github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1/go.mod h1:kP5rUlnqfno/obflnKX4KMBWkoVHLDI8oCka9U0opRo= github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= From 02d540478d495416b50d7e8b187ff9f5bba41f45 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 5 Jun 2024 10:06:38 +0700 Subject: [PATCH 132/352] feat(misconf): add metadata to Cloud schema (#6831) --- pkg/iac/rego/convert/struct.go | 13 +- pkg/iac/rego/convert/struct_test.go | 59 +- pkg/iac/rego/schemas/builder.go | 12 +- pkg/iac/rego/schemas/cloud.json | 1195 ++++++++++++++++++++++++++- 4 files changed, 1253 insertions(+), 26 deletions(-) diff --git a/pkg/iac/rego/convert/struct.go b/pkg/iac/rego/convert/struct.go index 0ca5cffae97b..306f4d85a464 100644 --- a/pkg/iac/rego/convert/struct.go +++ b/pkg/iac/rego/convert/struct.go @@ -32,20 +32,21 @@ func StructToRego(inputValue reflect.Value) map[string]any { field := inputValue.Field(i) typ := inputValue.Type().Field(i) name := typ.Name - if !typ.IsExported() { + + if !typ.IsExported() || field.Interface() == nil { continue } - if field.Interface() == nil { + + if _, ok := field.Interface().(types.Metadata); ok && name == "Metadata" { continue } + val := anonymousToRego(reflect.ValueOf(field.Interface())) + if val == nil { continue } - key := strings.ToLower(name) - if _, ok := field.Interface().(types.Metadata); key == "metadata" && ok { - continue - } + output[strings.ToLower(name)] = val } diff --git a/pkg/iac/rego/convert/struct_test.go b/pkg/iac/rego/convert/struct_test.go index 2147110b4a26..0970b00c26e6 100644 --- a/pkg/iac/rego/convert/struct_test.go +++ b/pkg/iac/rego/convert/struct_test.go @@ -5,17 +5,56 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_StructConversion(t *testing.T) { - input := struct { - X string - Y int - Z struct { - A float64 - } - }{} - input.Z.A = 123 - converted := StructToRego(reflect.ValueOf(input)) - assert.Equal(t, map[string]any{"z": make(map[string]any)}, converted) + tests := []struct { + name string + inp any + expected any + }{ + { + name: "struct with nested struct", + inp: struct { + X string + Y int + Z struct { + A float64 + } + }{ + X: "test", + Z: struct { + A float64 + }{ + A: 123, + }, + }, + expected: map[string]any{"z": make(map[string]any)}, + }, + { + name: "struct with metadata", + inp: struct { + X string + Metadata types.Metadata + }{ + X: "test", + Metadata: types.NewTestMetadata(), + }, + expected: map[string]any{ + "__defsec_metadata": func() any { + meta := types.NewTestMetadata().GetMetadata() + return meta.ToRego() + }(), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + converted := StructToRego(reflect.ValueOf(tt.inp)) + assert.Equal(t, tt.expected, converted) + }) + } } diff --git a/pkg/iac/rego/schemas/builder.go b/pkg/iac/rego/schemas/builder.go index 5ae395423624..649bf0a1aacf 100644 --- a/pkg/iac/rego/schemas/builder.go +++ b/pkg/iac/rego/schemas/builder.go @@ -75,13 +75,12 @@ func sanitize(s string) string { } func (b *builder) readProperty(name string, parent, inputType reflect.Type, indent int) (*Property, error) { - if inputType.Kind() == reflect.Ptr { inputType = inputType.Elem() } switch inputType.String() { - case "types.Metadata", "types.Range", "types.Reference": + case "types.Range", "types.Reference": return nil, nil } @@ -181,7 +180,8 @@ func (b *builder) readStruct(name string, parent, inputType reflect.Type, indent b.schema.Defs[refName(name, parent, inputType)] = def } - if inputType.Implements(converterInterface) { + if inputType.Implements(converterInterface) || + inputType.String() == "types.Metadata" { if inputType.Kind() == reflect.Ptr { inputType = inputType.Elem() } @@ -192,6 +192,7 @@ func (b *builder) readStruct(name string, parent, inputType reflect.Type, indent } else { for i := 0; i < inputType.NumField(); i++ { + field := inputType.Field(i) prop, err := b.readProperty(field.Name, inputType, field.Type, indent+1) if err != nil { @@ -201,9 +202,12 @@ func (b *builder) readStruct(name string, parent, inputType reflect.Type, indent continue } key := strings.ToLower(field.Name) + + // metadata exported as "__defsec_metadata" if key == "metadata" { - continue + key = "__defsec_metadata" } + def.Properties[key] = *prop } } diff --git a/pkg/iac/rego/schemas/cloud.json b/pkg/iac/rego/schemas/cloud.json index 2ac3594e98c5..e5b3ec740196 100644 --- a/pkg/iac/rego/schemas/cloud.json +++ b/pkg/iac/rego/schemas/cloud.json @@ -191,6 +191,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.AssumeRole": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "duration": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -238,6 +242,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.AssumeRoleWithWebIdentity": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "duration": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -274,6 +282,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.DefaultTags": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "tags": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.MapValue" @@ -283,6 +295,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.IgnoreTags": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "keyprefixes": { "type": "array", "items": { @@ -314,6 +330,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.TerraformProvider": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "accesskey": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -471,6 +491,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.accessanalyzer.Analyzer": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "active": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -493,7 +517,13 @@ } }, "github.com.aquasecurity.trivy.pkg.iac.providers.aws.accessanalyzer.Findings": { - "type": "object" + "type": "object", + "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + } + } }, "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.APIGateway": { "type": "object", @@ -511,6 +541,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.API": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "name": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -553,6 +587,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.AccessLogging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cloudwatchloggrouparn": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -562,6 +600,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.DomainName": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "name": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -575,6 +617,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.Method": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "apikeyrequired": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -592,6 +638,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.RESTMethodSettings": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cachedataencrypted": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -609,6 +659,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.Resource": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "methods": { "type": "array", "items": { @@ -621,6 +675,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.Stage": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "accesslogging": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v1.AccessLogging" @@ -645,6 +703,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.API": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "name": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -684,6 +746,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.AccessLogging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cloudwatchloggrouparn": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -693,6 +759,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.DomainName": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "name": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -706,6 +776,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.Stage": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "accesslogging": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.apigateway.v2.AccessLogging" @@ -738,6 +812,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.Database": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryption": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.EncryptionConfiguration" @@ -751,6 +829,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.EncryptionConfiguration": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "type": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -760,6 +842,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.Workgroup": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryption": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.athena.EncryptionConfiguration" @@ -777,6 +863,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.CacheBehaviour": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "viewerprotocolpolicy": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -798,6 +888,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.Distribution": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "defaultcachebehaviour": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.CacheBehaviour" @@ -826,6 +920,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.Logging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "bucket": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -835,6 +933,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudfront.ViewerCertificate": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cloudfrontdefaultcertificate": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -864,6 +966,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudtrail.DataResource": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "type": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -880,6 +986,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudtrail.EventSelector": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "dataresources": { "type": "array", "items": { @@ -896,6 +1006,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudtrail.Trail": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "bucketname": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -936,6 +1050,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.Alarm": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "alarmname": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -963,6 +1081,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.AlarmDimension": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "name": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -995,6 +1117,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.LogGroup": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "arn": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1023,6 +1149,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.MetricDataQuery": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "expression": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1036,6 +1166,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.cloudwatch.MetricFilter": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "filtername": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1049,6 +1183,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.codebuild.ArtifactSettings": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryptionenabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1070,6 +1208,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.codebuild.Project": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "artifactsettings": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.codebuild.ArtifactSettings" @@ -1095,6 +1237,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.config.ConfigurationAggregrator": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "sourceallregions": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1104,6 +1250,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.documentdb.Cluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "backupretentionperiod": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" @@ -1151,6 +1301,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.documentdb.Instance": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "kmskeyid": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1160,6 +1314,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.dynamodb.DAXCluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "pointintimerecovery": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1192,6 +1350,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.dynamodb.ServerSideEncryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1205,6 +1367,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.dynamodb.Table": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "pointintimerecovery": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1218,6 +1384,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.BlockDevice": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encrypted": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1288,6 +1458,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Encryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1301,6 +1475,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Instance": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "ebsblockdevices": { "type": "array", "items": { @@ -1332,6 +1510,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.LaunchConfiguration": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "associatepublicip": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1364,6 +1546,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.LaunchTemplate": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "instance": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Instance" @@ -1377,6 +1563,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.MetadataOptions": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "httpendpoint": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1390,6 +1580,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.NetworkACL": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "isdefaultrule": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1406,6 +1600,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.NetworkACLRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "action": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1430,6 +1628,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.SecurityGroup": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "description": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1461,6 +1663,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.SecurityGroupRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cidrs": { "type": "array", "items": { @@ -1477,6 +1683,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Subnet": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "mappubliciponlaunch": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1486,6 +1696,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.VPC": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "flowlogsenabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1510,6 +1724,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Volume": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryption": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ec2.Encryption" @@ -1531,6 +1749,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecr.Encryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "kmskeyid": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1544,6 +1766,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecr.ImageScanning": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "scanonpush": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1553,6 +1779,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecr.Repository": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryption": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecr.Encryption" @@ -1577,6 +1807,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.Cluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "settings": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.ClusterSettings" @@ -1586,6 +1820,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.ClusterSettings": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "containerinsightsenabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1595,6 +1833,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.ContainerDefinition": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cpu": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" @@ -1657,6 +1899,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.EFSVolumeConfiguration": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "transitencryptionenabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1690,6 +1936,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.TaskDefinition": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "containerdefinitions": { "type": "array", "items": { @@ -1709,6 +1959,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.Volume": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "efsvolumeconfiguration": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.ecs.EFSVolumeConfiguration" @@ -1730,6 +1984,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.efs.FileSystem": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encrypted": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1739,6 +1997,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.eks.Cluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryption": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.eks.Encryption" @@ -1775,6 +2037,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.eks.Encryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "kmskeyid": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1788,6 +2054,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.eks.Logging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "api": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1813,6 +2083,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticache.Cluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "engine": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1856,6 +2130,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticache.ReplicationGroup": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "atrestencryptionenabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1869,6 +2147,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticache.SecurityGroup": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "description": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1878,6 +2160,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.AtRestEncryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1891,6 +2177,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.Domain": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "accesspolicies": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1944,6 +2234,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.Endpoint": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enforcehttps": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1957,6 +2251,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.LogPublishing": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "auditenabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -1970,6 +2268,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.ServiceSoftwareOptions": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "currentversion": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -1991,6 +2293,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elasticsearch.TransitEncryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2000,6 +2306,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elb.Action": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "type": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2021,6 +2331,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elb.Listener": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "defaultactions": { "type": "array", "items": { @@ -2041,6 +2355,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.elb.LoadBalancer": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "dropinvalidheaderfields": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2065,6 +2383,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.emr.Cluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "settings": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.emr.ClusterSettings" @@ -2074,6 +2396,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.emr.ClusterSettings": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "name": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2110,6 +2436,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.emr.SecurityConfiguration": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "configuration": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2123,6 +2453,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.AccessKey": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "accesskeyid": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2176,6 +2510,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Group": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "name": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2243,6 +2581,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.MFADevice": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "isvirtual": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2252,6 +2594,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.PasswordPolicy": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "maxagedays": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" @@ -2285,6 +2631,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Policy": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "builtin": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2302,6 +2652,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Role": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "name": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2318,6 +2672,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.ServerCertificate": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "expiration": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" @@ -2327,6 +2685,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.User": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "accesskeys": { "type": "array", "items": { @@ -2368,6 +2730,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.kinesis.Encryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "kmskeyid": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2393,6 +2759,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.kinesis.Stream": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryption": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.kinesis.Encryption" @@ -2414,6 +2784,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.kms.Key": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "rotationenabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2427,6 +2801,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.lambda.Function": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "permissions": { "type": "array", "items": { @@ -2455,6 +2833,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.lambda.Permission": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "principal": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2468,6 +2850,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.lambda.Tracing": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "mode": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2477,6 +2863,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.mq.Broker": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "logging": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.mq.Logging" @@ -2490,6 +2880,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.mq.Logging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "audit": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2515,6 +2909,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.BrokerLogging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cloudwatch": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.CloudwatchLogging" @@ -2532,6 +2930,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.CloudwatchLogging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2541,6 +2943,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.Cluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryptionatrest": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.EncryptionAtRest" @@ -2558,6 +2964,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.EncryptionAtRest": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2571,6 +2981,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.EncryptionInTransit": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "clientbroker": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2580,6 +2994,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.FirehoseLogging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2589,6 +3007,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.Logging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "broker": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.BrokerLogging" @@ -2610,6 +3032,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.msk.S3Logging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2619,6 +3045,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.neptune.Cluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "kmskeyid": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2636,6 +3066,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.neptune.Logging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "audit": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2669,6 +3103,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Cluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "availabilityzones": { "type": "array", "items": { @@ -2737,6 +3175,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.DBParameterGroupsList": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "dbparametergroupname": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2748,11 +3190,21 @@ } }, "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.DBSecurityGroup": { - "type": "object" + "type": "object", + "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + } + } }, "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.DBSnapshotAttributes": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "attributevalues": { "type": "array", "items": { @@ -2765,6 +3217,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Encryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryptstorage": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2778,6 +3234,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Instance": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "autominorversionupgrade": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2875,6 +3335,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.ParameterGroups": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "dbparametergroupfamily": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2895,6 +3359,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Parameters": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "parametername": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2908,6 +3376,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.PerformanceInsights": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -2958,6 +3430,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.Snapshots": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "dbsnapshotarn": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -2984,11 +3460,21 @@ } }, "github.com.aquasecurity.trivy.pkg.iac.providers.aws.rds.TagList": { - "type": "object" + "type": "object", + "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + } + } }, "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.Cluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "allowversionupgrade": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3042,6 +3528,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.ClusterParameter": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "parametername": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3055,6 +3545,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.Encryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3068,6 +3562,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.EndPoint": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "port": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" @@ -3110,7 +3608,11 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.ReservedNode": { "type": "object", "properties": { - "nodetype": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, + "nodetype": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" } @@ -3119,6 +3621,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.redshift.SecurityGroup": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "description": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3128,6 +3634,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Bucket": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "accelerateconfigurationstatus": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3188,11 +3698,21 @@ } }, "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Contents": { - "type": "object" + "type": "object", + "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + } + } }, "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Encryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "algorithm": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3210,6 +3730,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Logging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3223,6 +3747,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.PublicAccessBlock": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "blockpublicacls": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3244,6 +3772,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Rules": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "status": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3265,6 +3797,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Versioning": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3276,11 +3812,21 @@ } }, "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Website": { - "type": "object" + "type": "object", + "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + } + } }, "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.API": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "accesslogging": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.AccessLogging" @@ -3306,6 +3852,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.AccessLogging": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cloudwatchloggrouparn": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3315,6 +3865,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.Application": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "location": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.Location" @@ -3328,6 +3882,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.DomainConfiguration": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "name": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3341,6 +3899,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.Function": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "functionname": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3368,6 +3930,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.HttpAPI": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "accesslogging": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.AccessLogging" @@ -3389,6 +3955,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.Location": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "applicationid": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3402,6 +3972,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.LoggingConfiguration": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "loggingenabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3411,6 +3985,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.RESTMethodSettings": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cachedataencrypted": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3432,6 +4010,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.RouteSettings": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "datatraceenabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3496,6 +4078,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.SSESpecification": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3509,6 +4095,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.SimpleTable": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "ssespecification": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.SSESpecification" @@ -3522,6 +4112,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.StateMachine": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "loggingconfiguration": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.LoggingConfiguration" @@ -3553,6 +4147,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sam.TracingConfiguration": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3562,6 +4160,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sns.Encryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "kmskeyid": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3583,6 +4185,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sns.Topic": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "arn": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3596,6 +4202,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sqs.Encryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "kmskeyid": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3609,6 +4219,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.sqs.Queue": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryption": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.sqs.Encryption" @@ -3653,6 +4267,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.ssm.Secret": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "kmskeyid": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3662,6 +4280,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.Encryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3671,6 +4293,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.Volume": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryption": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.Encryption" @@ -3680,6 +4306,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.WorkSpace": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "rootvolume": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.workspaces.Volume" @@ -3781,6 +4411,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.FunctionApp": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "httpsonly": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3790,6 +4424,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.Service": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "authentication": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.Service.Authentication" @@ -3854,6 +4492,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.authorization.Permission": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "actions": { "type": "array", "items": { @@ -3866,6 +4508,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.authorization.RoleDefinition": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "assignablescopes": { "type": "array", "items": { @@ -3911,6 +4557,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.Encryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3920,6 +4570,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.LinuxVirtualMachine": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "osprofilelinuxconfig": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.OSProfileLinuxConfig" @@ -3933,6 +4587,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.ManagedDisk": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryption": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.Encryption" @@ -3942,6 +4600,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.OSProfileLinuxConfig": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "disablepasswordauthentication": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -3951,6 +4613,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.VirtualMachine": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "customdata": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -3960,6 +4626,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.WindowsVirtualMachine": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "virtualmachine": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.compute.VirtualMachine" @@ -3969,6 +4639,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.AddonProfile": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "omsagent": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.OMSAgent" @@ -3990,6 +4664,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.KubernetesCluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "addonprofile": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.AddonProfile" @@ -4018,6 +4696,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.NetworkProfile": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "networkpolicy": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -4027,6 +4709,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.OMSAgent": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4036,6 +4722,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.container.RoleBasedAccessControl": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4078,6 +4768,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.ExtendedAuditingPolicy": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "retentionindays": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" @@ -4087,6 +4781,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.FirewallRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "endip": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -4100,6 +4798,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.MSSQLServer": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "extendedauditingpolicies": { "type": "array", "items": { @@ -4123,6 +4825,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.MariaDBServer": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "server": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.Server" @@ -4132,6 +4838,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.MySQLServer": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "server": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.Server" @@ -4141,6 +4851,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.PostgreSQLServer": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "config": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.PostgresSQLConfig" @@ -4154,6 +4868,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.PostgresSQLConfig": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "connectionthrottling": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4171,6 +4889,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.SecurityAlertPolicy": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "disabledalerts": { "type": "array", "items": { @@ -4194,6 +4916,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.database.Server": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enablepublicnetworkaccess": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4230,6 +4956,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.datafactory.Factory": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enablepublicnetwork": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4251,6 +4981,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.datalake.Store": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enableencryption": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4260,6 +4994,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.Key": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "expirydate": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" @@ -4281,6 +5019,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.NetworkACLs": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "defaultaction": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -4290,6 +5032,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.Secret": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "contenttype": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -4303,6 +5049,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.keyvault.Vault": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enablepurgeprotection": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4334,6 +5084,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.monitor.LogProfile": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "categories": { "type": "array", "items": { @@ -4369,6 +5123,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.monitor.RetentionPolicy": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "days": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" @@ -4401,6 +5159,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.NetworkWatcherFlowLog": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "retentionpolicy": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.RetentionPolicy" @@ -4410,6 +5172,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.PortRange": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "end": { "type": "integer" }, @@ -4421,6 +5187,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.RetentionPolicy": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "days": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" @@ -4434,6 +5204,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.SecurityGroup": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "rules": { "type": "array", "items": { @@ -4446,6 +5220,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.network.SecurityGroupRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "allow": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4491,6 +5269,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.securitycenter.Contact": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enablealertnotifications": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4523,6 +5305,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.securitycenter.SubscriptionPricing": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "tier": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -4532,6 +5318,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.Account": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "containers": { "type": "array", "items": { @@ -4570,6 +5360,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.Container": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "publicaccess": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -4579,6 +5373,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.NetworkRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "allowbydefault": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4595,6 +5393,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.Queue": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "name": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -4604,6 +5406,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.storage.QueueProperties": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enablelogging": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4637,6 +5443,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.azure.synapse.Workspace": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enablemanagedvirtualnetwork": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4667,6 +5477,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.cloudstack.compute.Instance": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "userdata": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -4722,6 +5536,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.Droplet": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "sshkeys": { "type": "array", "items": { @@ -4734,6 +5552,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.Firewall": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "inboundrules": { "type": "array", "items": { @@ -4753,6 +5575,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.ForwardingRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "entryprotocol": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -4762,6 +5588,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.InboundFirewallRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "sourceaddresses": { "type": "array", "items": { @@ -4774,6 +5604,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.KubernetesCluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "autoupgrade": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4787,6 +5621,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.LoadBalancer": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "forwardingrules": { "type": "array", "items": { @@ -4803,6 +5641,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.compute.OutboundFirewallRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "destinationaddresses": { "type": "array", "items": { @@ -4815,6 +5657,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.spaces.Bucket": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "acl": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -4843,6 +5689,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.spaces.Object": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "acl": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -4864,6 +5714,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.digitalocean.spaces.Versioning": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4873,6 +5727,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.github.BranchProtection": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "requiresignedcommits": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4882,6 +5740,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.github.EnvironmentSecret": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryptedvalue": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -4933,6 +5795,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.github.Repository": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "archived": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -4987,6 +5853,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.bigquery.AccessGrant": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "domain": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -5016,6 +5886,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.bigquery.Dataset": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "accessgrants": { "type": "array", "items": { @@ -5069,6 +5943,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Disk": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "encryption": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.DiskEncryption" @@ -5082,6 +5960,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.DiskEncryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "kmskeylink": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -5095,6 +5977,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.EgressRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "destinationranges": { "type": "array", "items": { @@ -5111,6 +5997,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Firewall": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "egressrules": { "type": "array", "items": { @@ -5148,6 +6038,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.FirewallRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enforced": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5172,6 +6066,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.IngressRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "firewallrule": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.FirewallRule" @@ -5188,6 +6086,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Instance": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "attacheddisks": { "type": "array", "items": { @@ -5242,6 +6144,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Network": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "firewall": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.Firewall" @@ -5258,6 +6164,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.NetworkInterface": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "haspublicip": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5279,6 +6189,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.ProjectMetadata": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enableoslogin": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5288,6 +6202,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.SSLPolicy": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "minimumtlsversion": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -5305,6 +6223,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.ServiceAccount": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "email": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -5325,6 +6247,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.ShieldedVMConfig": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "integritymonitoringenabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5342,6 +6268,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.compute.SubNetwork": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enableflowlogs": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5371,6 +6301,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.dns.DNSSec": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "defaultkeyspecs": { "type": "array", "items": { @@ -5387,6 +6321,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.dns.KeySpecs": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "algorithm": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -5400,6 +6338,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.dns.ManagedZone": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "dnssec": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.dns.DNSSec" @@ -5413,6 +6355,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.ClientCertificate": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "issuecertificate": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5422,6 +6368,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.Cluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "datapathprovider": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -5502,6 +6452,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.IPAllocationPolicy": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5511,6 +6465,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.Management": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enableautorepair": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5524,6 +6482,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.MasterAuth": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "clientcertificate": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.ClientCertificate" @@ -5541,6 +6503,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.MasterAuthorizedNetworks": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cidrs": { "type": "array", "items": { @@ -5557,6 +6523,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.NetworkPolicy": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5566,6 +6536,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.NodeConfig": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enablelegacyendpoints": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5587,6 +6561,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.NodePool": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "management": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.Management" @@ -5600,6 +6578,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.PrivateCluster": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enableprivatenodes": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5609,6 +6591,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.gke.WorkloadMetadataConfig": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "nodemetadata": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -5618,6 +6604,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Binding": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "includesdefaultserviceaccount": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5638,6 +6628,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Folder": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "bindings": { "type": "array", "items": { @@ -5690,6 +6684,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Member": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "defaultserviceaccount": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5707,6 +6705,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Organization": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "bindings": { "type": "array", "items": { @@ -5740,6 +6742,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Project": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "autocreatenetwork": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5763,6 +6769,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.WorkloadIdentityPoolProvider": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "attributecondition": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -5792,6 +6802,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.kms.Key": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "rotationperiodseconds": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" @@ -5801,6 +6815,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.kms.KeyRing": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "keys": { "type": "array", "items": { @@ -5813,6 +6831,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.Backups": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "enabled": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5822,6 +6844,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.DatabaseInstance": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "databaseversion": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -5839,6 +6865,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.Flags": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "containeddatabaseauthentication": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -5884,6 +6914,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.IPConfiguration": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "authorizednetworks": { "type": "array", "items": { @@ -5929,6 +6963,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.Settings": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "backups": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.sql.Backups" @@ -5946,6 +6984,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.storage.Bucket": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "bindings": { "type": "array", "items": { @@ -5981,6 +7023,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.google.storage.BucketEncryption": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "defaultkmskeyname": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6002,6 +7048,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Egress": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "destinationcidrs": { "type": "array", "items": { @@ -6021,6 +7071,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Ingress": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "ports": { "type": "array", "items": { @@ -6052,6 +7106,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.NetworkPolicy": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "spec": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.NetworkPolicySpec" @@ -6061,6 +7119,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.NetworkPolicySpec": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "egress": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Egress" @@ -6074,6 +7136,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.kubernetes.Port": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "number": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6135,6 +7201,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.Instance": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "networkinterfaces": { "type": "array", "items": { @@ -6151,6 +7221,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.NetworkInterface": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "networkid": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6160,6 +7234,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.SecurityGroup": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "description": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6183,6 +7261,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.computing.SecurityGroupRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cidr": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6208,6 +7290,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.dns.Record": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "record": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6240,6 +7326,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.nas.NASInstance": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "networkid": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6249,6 +7339,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.nas.NASSecurityGroup": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cidrs": { "type": "array", "items": { @@ -6265,6 +7359,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.ElasticLoadBalancer": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "listeners": { "type": "array", "items": { @@ -6284,6 +7382,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.ElasticLoadBalancerListener": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "protocol": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6293,6 +7395,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.LoadBalancer": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "listeners": { "type": "array", "items": { @@ -6305,6 +7411,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.LoadBalancerListener": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "protocol": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6351,6 +7461,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.NetworkInterface": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "isvipnetwork": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue" @@ -6364,6 +7478,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.Router": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "networkinterfaces": { "type": "array", "items": { @@ -6380,6 +7498,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.network.VpnGateway": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "securitygroup": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6389,6 +7511,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.rdb.DBInstance": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "backupretentionperioddays": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" @@ -6414,6 +7540,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.rdb.DBSecurityGroup": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cidrs": { "type": "array", "items": { @@ -6461,6 +7591,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.nifcloud.sslcertificate.ServerCertificate": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "expiration": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" @@ -6505,6 +7639,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.openstack.FirewallRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "destination": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6530,6 +7668,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.openstack.Instance": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "adminpassword": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6564,6 +7706,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.openstack.SecurityGroup": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "description": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6584,6 +7730,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.openstack.SecurityGroupRule": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "cidr": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6613,6 +7763,10 @@ "github.com.aquasecurity.trivy.pkg.iac.providers.oracle.AddressReservation": { "type": "object", "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, "pool": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" @@ -6768,6 +7922,35 @@ } } }, + "github.com.aquasecurity.trivy.pkg.iac.types.Metadata": { + "type": "object", + "properties": { + "endline": { + "type": "integer" + }, + "explicit": { + "type": "boolean" + }, + "filepath": { + "type": "string" + }, + "fskey": { + "type": "string" + }, + "managed": { + "type": "boolean" + }, + "resource": { + "type": "string" + }, + "sourceprefix": { + "type": "string" + }, + "startline": { + "type": "integer" + } + } + }, "github.com.aquasecurity.trivy.pkg.iac.types.StringValue": { "type": "object", "properties": { From 0bcfedbcaa9bbe30ee5ecade5b98e9ce3cc54c9b Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 5 Jun 2024 10:20:07 +0700 Subject: [PATCH 133/352] fix(misconf): fix caching of modules in subdirectories (#6814) --- .../terraform/parser/module_retrieval.go | 2 +- pkg/iac/scanners/terraform/parser/parser.go | 10 +- .../parser/parser_integration_test.go | 49 +++++--- .../terraform/parser/resolvers/cache.go | 31 +++-- .../resolvers/cache_integration_test.go | 114 ++++++++++++++++++ .../terraform/parser/resolvers/options.go | 1 + .../terraform/parser/resolvers/registry.go | 4 +- .../terraform/parser/resolvers/remote.go | 5 +- .../terraform/parser/resolvers/source.go | 36 ++++++ .../terraform/parser/resolvers/source_test.go | 44 +++++++ 10 files changed, 263 insertions(+), 33 deletions(-) create mode 100644 pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go create mode 100644 pkg/iac/scanners/terraform/parser/resolvers/source.go create mode 100644 pkg/iac/scanners/terraform/parser/resolvers/source_test.go diff --git a/pkg/iac/scanners/terraform/parser/module_retrieval.go b/pkg/iac/scanners/terraform/parser/module_retrieval.go index 2ae6221afc73..165f64eef1cb 100644 --- a/pkg/iac/scanners/terraform/parser/module_retrieval.go +++ b/pkg/iac/scanners/terraform/parser/module_retrieval.go @@ -13,8 +13,8 @@ type ModuleResolver interface { } var defaultResolvers = []ModuleResolver{ - resolvers.Cache, resolvers.Local, + resolvers.Cache, resolvers.Remote, resolvers.Registry, } diff --git a/pkg/iac/scanners/terraform/parser/parser.go b/pkg/iac/scanners/terraform/parser/parser.go index b7de6dd4ba08..049be0e02c10 100644 --- a/pkg/iac/scanners/terraform/parser/parser.go +++ b/pkg/iac/scanners/terraform/parser/parser.go @@ -232,17 +232,19 @@ func (p *Parser) Load(ctx context.Context) (*evaluator, error) { } modulesMetadata, metadataPath, err := loadModuleMetadata(p.moduleFS, p.projectRoot) - if err != nil { + + if err != nil && !errors.Is(err, os.ErrNotExist) { p.debug.Log("Error loading module metadata: %s.", err) - } else { - p.debug.Log("Loaded module metadata for %d module(s) from '%s'.", len(modulesMetadata.Modules), metadataPath) + } else if err == nil { + p.debug.Log("Loaded module metadata for %d module(s) from %q.", len(modulesMetadata.Modules), metadataPath) } workingDir, err := os.Getwd() if err != nil { return nil, err } - p.debug.Log("Working directory for module evaluation is '%s'", workingDir) + + p.debug.Log("Working directory for module evaluation is %q", workingDir) return newEvaluator( p.moduleFS, p, diff --git a/pkg/iac/scanners/terraform/parser/parser_integration_test.go b/pkg/iac/scanners/terraform/parser/parser_integration_test.go index f73e9b85539c..5b301c0e41dd 100644 --- a/pkg/iac/scanners/terraform/parser/parser_integration_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_integration_test.go @@ -13,7 +13,8 @@ func Test_DefaultRegistry(t *testing.T) { if testing.Short() { t.Skip("skipping integration test in short mode") } - fs := testutil.CreateFS(t, map[string]string{ + + fsys := testutil.CreateFS(t, map[string]string{ "code/test.tf": ` module "registry" { source = "terraform-aws-modules/vpc/aws" @@ -21,10 +22,9 @@ module "registry" { `, }) - parser := New(fs, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) - if err := parser.ParseFS(context.TODO(), "code"); err != nil { - t.Fatal(err) - } + parser := New(fsys, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) require.NoError(t, err) require.Len(t, modules, 2) @@ -34,7 +34,8 @@ func Test_SpecificRegistry(t *testing.T) { if testing.Short() { t.Skip("skipping integration test in short mode") } - fs := testutil.CreateFS(t, map[string]string{ + + fsys := testutil.CreateFS(t, map[string]string{ "code/test.tf": ` module "registry" { source = "registry.terraform.io/terraform-aws-modules/vpc/aws" @@ -42,10 +43,9 @@ module "registry" { `, }) - parser := New(fs, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) - if err := parser.ParseFS(context.TODO(), "code"); err != nil { - t.Fatal(err) - } + parser := New(fsys, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) require.NoError(t, err) require.Len(t, modules, 2) @@ -55,7 +55,8 @@ func Test_ModuleWithPessimisticVersionConstraint(t *testing.T) { if testing.Short() { t.Skip("skipping integration test in short mode") } - fs := testutil.CreateFS(t, map[string]string{ + + fsys := testutil.CreateFS(t, map[string]string{ "code/test.tf": ` module "registry" { source = "registry.terraform.io/terraform-aws-modules/s3-bucket/aws" @@ -65,10 +66,30 @@ module "registry" { `, }) - parser := New(fs, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) - if err := parser.ParseFS(context.TODO(), "code"); err != nil { - t.Fatal(err) + parser := New(fsys, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + require.Len(t, modules, 2) +} + +func Test_ModuleInSubdir(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") } + + fsys := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` +module "object" { + source = "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/object?ref=v4.1.2" + +}`, + }) + + parser := New(fsys, "", OptionStopOnHCLError(true), OptionWithSkipCachedModules(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) require.NoError(t, err) require.Len(t, modules, 2) diff --git a/pkg/iac/scanners/terraform/parser/resolvers/cache.go b/pkg/iac/scanners/terraform/parser/resolvers/cache.go index 6efc15f72dbb..e08c43ff3ba0 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/cache.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/cache.go @@ -2,7 +2,8 @@ package resolvers import ( "context" - "crypto/md5" // nolint + "crypto/md5" // #nosec + "encoding/hex" "fmt" "io/fs" "os" @@ -15,16 +16,21 @@ var Cache = &cacheResolver{} const tempDirName = ".aqua" -func locateCacheFS() (fs.FS, error) { - dir, err := locateCacheDir() +var defaultCacheDir = filepath.Join(os.TempDir(), tempDirName, "cache") + +func locateCacheFS(cacheDir string) (fs.FS, error) { + dir, err := locateCacheDir(cacheDir) if err != nil { return nil, err } return os.DirFS(dir), nil } -func locateCacheDir() (string, error) { - cacheDir := filepath.Join(os.TempDir(), tempDirName, "cache") +func locateCacheDir(cacheDir string) (string, error) { + if cacheDir == "" { + cacheDir = defaultCacheDir + } + if err := os.MkdirAll(cacheDir, 0o750); err != nil { return "", err } @@ -39,24 +45,29 @@ func (r *cacheResolver) Resolve(_ context.Context, _ fs.FS, opt Options) (filesy opt.Debug("Cache is disabled.") return nil, "", "", false, nil } - cacheFS, err := locateCacheFS() + cacheFS, err := locateCacheFS(opt.CacheDir) if err != nil { opt.Debug("No cache filesystem is available on this machine.") return nil, "", "", false, nil } - key := cacheKey(opt.Source, opt.Version, opt.RelativePath) + + src := removeSubdirFromSource(opt.Source) + key := cacheKey(src, opt.Version) + opt.Debug("Trying to resolve: %s", key) if info, err := fs.Stat(cacheFS, filepath.ToSlash(key)); err == nil && info.IsDir() { opt.Debug("Module '%s' resolving via cache...", opt.Name) - cacheDir, err := locateCacheDir() + cacheDir, err := locateCacheDir(opt.CacheDir) if err != nil { return nil, "", "", true, err } + return os.DirFS(filepath.Join(cacheDir, key)), opt.OriginalSource, ".", true, nil } return nil, "", "", false, nil } -func cacheKey(source, version, relativePath string) string { - return fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%s:%s:%s", source, version, relativePath)))) // nolint +func cacheKey(source, version string) string { + hash := md5.Sum([]byte(source + ":" + version)) // #nosec + return hex.EncodeToString(hash[:]) } diff --git a/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go b/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go new file mode 100644 index 000000000000..a7c6704b6708 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go @@ -0,0 +1,114 @@ +package resolvers_test + +import ( + "context" + "io/fs" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers" +) + +type moduleResolver interface { + Resolve(context.Context, fs.FS, resolvers.Options) (fs.FS, string, string, bool, error) +} + +func TestResolveModuleFromCache(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + + tests := []struct { + name string + opts resolvers.Options + firstResolver moduleResolver + }{ + { + name: "registry", + opts: resolvers.Options{ + Name: "bucket", + Source: "terraform-aws-modules/s3-bucket/aws", + Version: "4.1.2", + }, + firstResolver: resolvers.Registry, + }, + { + name: "registry with subdir", + opts: resolvers.Options{ + Name: "object", + Source: "terraform-aws-modules/s3-bucket/aws//modules/object", + Version: "4.1.2", + }, + firstResolver: resolvers.Registry, + }, + { + name: "remote", + opts: resolvers.Options{ + Name: "bucket", + Source: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git?ref=v4.1.2", + }, + firstResolver: resolvers.Remote, + }, + { + name: "remote with subdir", + opts: resolvers.Options{ + Name: "object", + Source: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/object?ref=v4.1.2", + }, + firstResolver: resolvers.Remote, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + tt.opts.AllowDownloads = true + tt.opts.OriginalSource = tt.opts.Source + tt.opts.OriginalVersion = tt.opts.Version + tt.opts.CacheDir = t.TempDir() + + fsys, _, _, applies, err := tt.firstResolver.Resolve(context.Background(), nil, tt.opts) + require.NoError(t, err) + assert.True(t, applies) + + _, err = fs.Stat(fsys, "main.tf") + require.NoError(t, err) + + _, _, _, applies, err = resolvers.Cache.Resolve(context.Background(), fsys, tt.opts) + require.NoError(t, err) + assert.True(t, applies) + }) + } +} + +func TestResolveModuleFromCacheWithDifferentSubdir(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test in short mode") + } + + cacheDir := t.TempDir() + + fsys, _, _, applies, err := resolvers.Remote.Resolve(context.Background(), nil, resolvers.Options{ + Name: "object", + Source: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/object?ref=v4.1.2", + OriginalSource: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/object?ref=v4.1.2", + AllowDownloads: true, + CacheDir: cacheDir, + }) + require.NoError(t, err) + assert.True(t, applies) + + _, err = fs.Stat(fsys, "main.tf") + require.NoError(t, err) + + _, _, _, applies, err = resolvers.Cache.Resolve(context.Background(), nil, resolvers.Options{ + Name: "notification", + Source: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/notification?ref=v4.1.2", + OriginalSource: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/notification?ref=v4.1.2", + CacheDir: cacheDir, + }) + require.NoError(t, err) + assert.True(t, applies) +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/options.go b/pkg/iac/scanners/terraform/parser/resolvers/options.go index 1a676ae9e733..cdfde6b01bcc 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/options.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/options.go @@ -12,6 +12,7 @@ type Options struct { AllowDownloads bool SkipCache bool RelativePath string + CacheDir string } func (o *Options) hasPrefix(prefixes ...string) bool { diff --git a/pkg/iac/scanners/terraform/parser/resolvers/registry.go b/pkg/iac/scanners/terraform/parser/resolvers/registry.go index 93d80fa0af86..bcdd4734974a 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/registry.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/registry.go @@ -45,7 +45,7 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option } inputVersion := opt.Version - source, relativePath, _ := strings.Cut(opt.Source, "//") + source := removeSubdirFromSource(opt.Source) parts := strings.Split(source, "/") if len(parts) < 3 || len(parts) > 4 { return @@ -146,7 +146,7 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option } opt.Debug("Module '%s' resolved via registry to new source: '%s'", opt.Name, opt.Source) - opt.RelativePath = relativePath + filesystem, prefix, downloadPath, _, err = Remote.Resolve(ctx, target, opt) if err != nil { return nil, "", "", true, err diff --git a/pkg/iac/scanners/terraform/parser/resolvers/remote.go b/pkg/iac/scanners/terraform/parser/resolvers/remote.go index 4a6a26798a8a..df94c775da78 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/remote.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/remote.go @@ -38,10 +38,11 @@ func (r *remoteResolver) Resolve(ctx context.Context, _ fs.FS, opt Options) (fil return nil, "", "", false, nil } - key := cacheKey(opt.OriginalSource, opt.OriginalVersion, opt.RelativePath) + src := removeSubdirFromSource(opt.OriginalSource) + key := cacheKey(src, opt.OriginalVersion) opt.Debug("Storing with cache key %s", key) - baseCacheDir, err := locateCacheDir() + baseCacheDir, err := locateCacheDir(opt.CacheDir) if err != nil { return nil, "", "", true, fmt.Errorf("failed to locate cache directory: %w", err) } diff --git a/pkg/iac/scanners/terraform/parser/resolvers/source.go b/pkg/iac/scanners/terraform/parser/resolvers/source.go new file mode 100644 index 000000000000..d7637ffaf8a5 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/resolvers/source.go @@ -0,0 +1,36 @@ +package resolvers + +import "strings" + +func removeSubdirFromSource(src string) string { + stop := len(src) + if idx := strings.Index(src, "?"); idx > -1 { + stop = idx + } + + // Calculate an offset to avoid accidentally marking the scheme + // as the dir. + var offset int + if idx := strings.Index(src[:stop], "://"); idx > -1 { + offset = idx + 3 + } + + // First see if we even have an explicit subdir + idx := strings.Index(src[offset:stop], "//") + if idx == -1 { + return src + } + + idx += offset + subdir := src[idx+2:] + src = src[:idx] + + // Next, check if we have query parameters and push them onto the + // URL. + if idx = strings.Index(subdir, "?"); idx > -1 { + query := subdir[idx:] + src += query + } + + return src +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/source_test.go b/pkg/iac/scanners/terraform/parser/resolvers/source_test.go new file mode 100644 index 000000000000..3f57ab68b6d3 --- /dev/null +++ b/pkg/iac/scanners/terraform/parser/resolvers/source_test.go @@ -0,0 +1,44 @@ +package resolvers + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRemoveSubdirFromSource(t *testing.T) { + + tests := []struct { + name string + source string + expected string + }{ + { + name: "address with scheme and query string", + source: "git::https://github.com/aquasecurity/terraform-modules.git//modules/ecs-service?ref=v0.1.0", + expected: "git::https://github.com/aquasecurity/terraform-modules.git?ref=v0.1.0", + }, + { + name: "address with scheme", + source: "git::https://github.com/aquasecurity/terraform-modules.git//modules/ecs-service", + expected: "git::https://github.com/aquasecurity/terraform-modules.git", + }, + { + name: "registry address", + source: "hashicorp/consul/aws//modules/consul-cluster", + expected: "hashicorp/consul/aws", + }, + { + name: "without subdir", + source: `hashicorp/consul/aws`, + expected: `hashicorp/consul/aws`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := removeSubdirFromSource(test.source) + assert.Equal(t, test.expected, got) + }) + } +} From 8141a137ba50b553a9da877d95c7ccb491d041c6 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 5 Jun 2024 10:20:54 +0700 Subject: [PATCH 134/352] fix(misconf): parsing numbers without fraction as int (#6834) --- .../cloudformation/parser/intrinsics.go | 2 +- .../cloudformation/parser/parameter.go | 19 +++++++- .../cloudformation/parser/parser_test.go | 47 +++++++++++++++++++ .../scanners/cloudformation/parser/util.go | 16 +++++-- 4 files changed, 79 insertions(+), 5 deletions(-) diff --git a/pkg/iac/scanners/cloudformation/parser/intrinsics.go b/pkg/iac/scanners/cloudformation/parser/intrinsics.go index 1dadc4f6d6fd..cda2bde29e41 100644 --- a/pkg/iac/scanners/cloudformation/parser/intrinsics.go +++ b/pkg/iac/scanners/cloudformation/parser/intrinsics.go @@ -100,7 +100,7 @@ func getIntrinsicTag(tag string) string { } } -func abortIntrinsic(property *Property, msg string, components ...string) (*Property, bool) { +func abortIntrinsic(property *Property, _ string, _ ...string) (*Property, bool) { // return property, false } diff --git a/pkg/iac/scanners/cloudformation/parser/parameter.go b/pkg/iac/scanners/cloudformation/parser/parameter.go index 671058a9f83c..20e2011417d5 100644 --- a/pkg/iac/scanners/cloudformation/parser/parameter.go +++ b/pkg/iac/scanners/cloudformation/parser/parameter.go @@ -27,7 +27,24 @@ func (p *Parameter) UnmarshalYAML(node *yaml.Node) error { } func (p *Parameter) UnmarshalJSONWithMetadata(node jfather.Node) error { - return node.Decode(&p.inner) + + var inner parameterInner + + if err := node.Decode(&inner); err != nil { + return err + } + + // jfather parses Number without fraction as int64 + // https://github.com/liamg/jfather/blob/4ef05d70c05af167226d3333a4ec7d8ac3c9c281/parse_number.go#L33-L42 + switch v := inner.Default.(type) { + case int64: + inner.Default = int(v) + default: + inner.Default = v + } + + p.inner = inner + return nil } func (p *Parameter) Type() cftypes.CfType { diff --git a/pkg/iac/scanners/cloudformation/parser/parser_test.go b/pkg/iac/scanners/cloudformation/parser/parser_test.go index 1c5688b7fa55..396e12f1bf57 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser_test.go +++ b/pkg/iac/scanners/cloudformation/parser/parser_test.go @@ -370,5 +370,52 @@ Resources: assert.Equal(t, "testbucket", bucketNameProp.AsString()) } } +} + +func TestJsonWithNumbers(t *testing.T) { + src := ` +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": { + "SomeIntParam": { + "Type": "Number", + "Default": 1 + }, + "SomeFloatParam": { + "Type": "Number", + "Default": 1.1 + } + }, + "Resources": { + "SomeResource": { + "Type": "Test::Resource", + "Properties": { + "SomeIntProp": 1, + "SomeFloatProp": 1.1 + } + } + } +} +` + + fsys := testutil.CreateFS(t, map[string]string{ + "main.json": src, + }) + + files, err := New().ParseFS(context.TODO(), fsys, ".") + + require.NoError(t, err) + require.Len(t, files, 1) + + file := files[0] + + assert.Equal(t, 1, file.Parameters["SomeIntParam"].Default()) + assert.Equal(t, 1.1, file.Parameters["SomeFloatParam"].Default()) + + res := file.GetResourcesByType("Test::Resource") + assert.NotNil(t, res) + assert.Len(t, res, 1) + assert.Equal(t, 1, res[0].GetProperty("SomeIntProp").AsIntValue().Value()) + assert.Equal(t, 0, res[0].GetProperty("SomeFloatProp").AsIntValue().Value()) } diff --git a/pkg/iac/scanners/cloudformation/parser/util.go b/pkg/iac/scanners/cloudformation/parser/util.go index 03b9bf8da837..5ee61de17b9d 100644 --- a/pkg/iac/scanners/cloudformation/parser/util.go +++ b/pkg/iac/scanners/cloudformation/parser/util.go @@ -13,10 +13,20 @@ import ( func setPropertyValueFromJson(node jfather.Node, propertyData *PropertyInner) error { switch node.Kind() { - case jfather.KindNumber: - propertyData.Type = cftypes.Float64 - return node.Decode(&propertyData.Value) + var val any + if err := node.Decode(&val); err != nil { + return err + } + switch v := val.(type) { + case float64: + propertyData.Type = cftypes.Float64 + propertyData.Value = v + case int64: + propertyData.Type = cftypes.Int + propertyData.Value = int(v) + } + return nil case jfather.KindBoolean: propertyData.Type = cftypes.Bool return node.Decode(&propertyData.Value) From 042d6b08c283105c258a3dda98983b345a5305c3 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:51:19 +0600 Subject: [PATCH 135/352] feat(dart): use first version of constraint for dependencies using SDK version (#6239) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- docs/docs/coverage/language/dart.md | 24 ++++- go.mod | 2 +- go.sum | 3 +- pkg/dependency/parser/dart/pub/parse.go | 89 +++++++++++++++++-- pkg/dependency/parser/dart/pub/parse_test.go | 4 +- .../parser/dart/pub/testdata/happy.lock | 2 +- 6 files changed, 109 insertions(+), 15 deletions(-) diff --git a/docs/docs/coverage/language/dart.md b/docs/docs/coverage/language/dart.md index 0ce6d1cc52b0..a64a19eb83c2 100644 --- a/docs/docs/coverage/language/dart.md +++ b/docs/docs/coverage/language/dart.md @@ -4,9 +4,9 @@ Trivy supports [Dart][dart]. The following scanners are supported. -| Package manager | SBOM | Vulnerability | License | -|-------------------------| :---: | :-----------: |:-------:| -| [Dart][dart-repository] | ✓ | ✓ | - | +| Package manager | SBOM | Vulnerability | License | +|-------------------------|:----:|:-------------:|:-------:| +| [Dart][dart-repository] | ✓ | ✓ | - | The following table provides an outline of the features Trivy offers. @@ -21,6 +21,24 @@ In order to detect dependencies, Trivy searches for `pubspec.lock`. Trivy marks indirect dependencies, but `pubspec.lock` file doesn't have options to separate root and dev transitive dependencies. So Trivy includes all dependencies in report. +### SDK dependencies +Dart uses version `0.0.0` for SDK dependencies (e.g. Flutter). It is not possible to accurately determine the versions of these dependencies. + +Therefore, we use the first version of the constraint for the SDK. + +For example in this case the version of `flutter` should be `3.3.0`: +```yaml +flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" +sdks: + dart: ">=2.18.0 <3.0.0" + flutter: "^3.3.0" +``` + +### Dependency tree To build `dependency tree` Trivy parses [cache directory][cache-directory]. Currently supported default directories and `PUB_CACHE` environment (absolute path only). !!! note Make sure the cache directory contains all the dependencies installed in your application. To download missing dependencies, use `dart pub get` command. diff --git a/go.mod b/go.mod index fd6a361f2c13..45539f6d21e1 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 - github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 + github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d github.com/aquasecurity/loading v0.0.5 github.com/aquasecurity/table v1.8.0 github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334 diff --git a/go.sum b/go.sum index 4ddad3871334..ac4b13249c77 100644 --- a/go.sum +++ b/go.sum @@ -760,8 +760,9 @@ github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798/go.mod github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 h1:vmXNl+HDfqqXgr0uY1UgK1GAhps8nbAAtqHNBcgyf+4= github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46/go.mod h1:olhPNdiiAAMiSujemd1O/sc6GcyePr23f/6uGKtthNg= github.com/aquasecurity/go-version v0.0.0-20201107203531-5e48ac5d022a/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU= -github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 h1:rcEG5HI490FF0a7zuvxOxen52ddygCfNVjP0XOCMl+M= github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU= +github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d h1:4zour5Sh9chOg+IqIinIcJ3qtr3cIf8FdFY6aArlXBw= +github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d/go.mod h1:1cPOp4BaQZ1G2F5fnw4dFz6pkOyXJI9KTuak8ghIl3U= github.com/aquasecurity/loading v0.0.5 h1:2iq02sPSSMU+ULFPmk0v0lXnK/eZ2e0dRAj/Dl5TvuM= github.com/aquasecurity/loading v0.0.5/go.mod h1:NSHeeq1JTDTFuXAe87q4yQ2DX57pXiaQMqq8Zm9HCJA= github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0= diff --git a/pkg/dependency/parser/dart/pub/parse.go b/pkg/dependency/parser/dart/pub/parse.go index 10b1e7ab729a..1bd85a56f1d6 100644 --- a/pkg/dependency/parser/dart/pub/parse.go +++ b/pkg/dependency/parser/dart/pub/parse.go @@ -4,8 +4,10 @@ import ( "golang.org/x/xerrors" "gopkg.in/yaml.v3" + goversion "github.com/aquasecurity/go-version/pkg/version" "github.com/aquasecurity/trivy/pkg/dependency" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -16,21 +18,30 @@ const ( ) // Parser is a parser for pubspec.lock -type Parser struct{} +type Parser struct { + logger *log.Logger +} func NewParser() *Parser { - return &Parser{} + return &Parser{ + logger: log.WithPrefix("pub"), + } } type lock struct { - Packages map[string]Dep `yaml:"packages"` + Packages map[string]Dep `yaml:"packages"` + Sdks map[string]string `yaml:"sdks"` } type Dep struct { - Dependency string `yaml:"dependency"` - Version string `yaml:"version"` + Dependency string `yaml:"dependency"` + Version string `yaml:"version"` + Source string `yaml:"source"` + Description Description `yaml:"description"` } +type Description string + func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { l := &lock{} if err := yaml.NewDecoder(r).Decode(&l); err != nil { @@ -38,15 +49,20 @@ func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency } var pkgs []ftypes.Package for name, dep := range l.Packages { + version := dep.Version + if version == "0.0.0" && dep.Source == "sdk" { + version = p.findSDKVersion(l, name, dep) + } + // We would like to exclude dev dependencies, but we cannot identify // which indirect dependencies were introduced by dev dependencies // as there are 3 dependency types, "direct main", "direct dev" and "transitive". // It will be confusing if we exclude direct dev dependencies and include transitive dev dependencies. // We decided to keep all dev dependencies until Pub will add support for "transitive main" and "transitive dev". pkg := ftypes.Package{ - ID: dependency.ID(ftypes.Pub, name, dep.Version), + ID: dependency.ID(ftypes.Pub, name, version), Name: name, - Version: dep.Version, + Version: version, Relationship: p.relationship(dep.Dependency), } pkgs = append(pkgs, pkg) @@ -55,6 +71,31 @@ func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency return pkgs, nil, nil } +// findSDKVersion detects the first version of the SDK constraint specified in the Description. +// If the constraint is not found, it returns the original version. +func (p Parser) findSDKVersion(l *lock, name string, dep Dep) string { + // Some dependencies use one of the SDK versions. + // In this case dep.Version == `0.0.0`. + // We can't get versions for these dependencies. + // Therefore, we use the first version of the SDK constraint specified in the Description. + // See https://github.com/aquasecurity/trivy/issues/6017 + constraint, ok := l.Sdks[string(dep.Description)] + if !ok { + return dep.Version + } + + v, err := firstVersionOfConstrain(constraint) + if err != nil { + p.logger.Warn("Unable to get sdk version from constraint", log.Err(err)) + return dep.Version + } else if v == "" { + return dep.Version + } + p.logger.Info("Using the first version of the constraint from the sdk source", log.String("dep", name), + log.String("constraint", constraint)) + return v +} + func (p Parser) relationship(dep string) ftypes.Relationship { switch dep { case directMain, directDev: @@ -64,3 +105,37 @@ func (p Parser) relationship(dep string) ftypes.Relationship { } return ftypes.RelationshipUnknown } + +// firstVersionOfConstrain returns the first acceptable version for constraint +func firstVersionOfConstrain(constraint string) (string, error) { + css, err := goversion.NewConstraints(constraint) + if err != nil { + return "", xerrors.Errorf("unable to parse constraints: %w", err) + } + + // Dart uses only `>=` and `^` operators: + // cf. https://dart.dev/tools/pub/dependencies#traditional-syntax + constraints := css.List() + if len(constraints) == 0 || len(constraints[0]) == 0 { + return "", nil + } + // We only need to get the first version from the range + if constraints[0][0].Operator() != ">=" && constraints[0][0].Operator() != "^" { + return "", nil + } + + return constraints[0][0].Version(), nil +} + +func (d *Description) UnmarshalYAML(value *yaml.Node) error { + var tmp any + if err := value.Decode(&tmp); err != nil { + return err + } + // Description can be a string or a struct + // We only need a string value for SDK mapping + if desc, ok := tmp.(string); ok { + *d = Description(desc) + } + return nil +} diff --git a/pkg/dependency/parser/dart/pub/parse_test.go b/pkg/dependency/parser/dart/pub/parse_test.go index be698a7933c5..5f4591201b7c 100644 --- a/pkg/dependency/parser/dart/pub/parse_test.go +++ b/pkg/dependency/parser/dart/pub/parse_test.go @@ -31,9 +31,9 @@ func TestParser_Parse(t *testing.T) { Relationship: ftypes.RelationshipDirect, }, { - ID: "flutter_test@0.0.0", + ID: "flutter_test@3.3.0", Name: "flutter_test", - Version: "0.0.0", + Version: "3.3.0", Relationship: ftypes.RelationshipDirect, }, { diff --git a/pkg/dependency/parser/dart/pub/testdata/happy.lock b/pkg/dependency/parser/dart/pub/testdata/happy.lock index 3a37840aa3bb..b589a2ba3f0a 100644 --- a/pkg/dependency/parser/dart/pub/testdata/happy.lock +++ b/pkg/dependency/parser/dart/pub/testdata/happy.lock @@ -22,4 +22,4 @@ packages: version: "3.0.6" sdks: dart: ">=2.18.0 <3.0.0" - flutter: ">=3.3.0" \ No newline at end of file + flutter: "^3.3.0" \ No newline at end of file From 7d083bc890eccc3bf32765c6d7e922cab2e2ef94 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:38:42 +0600 Subject: [PATCH 136/352] fix(nodejs): fix infinity loops for `pnpm` with cyclic imports (#6857) --- pkg/dependency/parser/nodejs/pnpm/parse.go | 13 ++-- .../parser/nodejs/pnpm/parse_test.go | 12 ++++ .../parser/nodejs/pnpm/parse_testcase.go | 64 ++++++++++++++++++ .../testdata/pnpm-lock_v9_cyclic_import.yaml | 67 +++++++++++++++++++ 4 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9_cyclic_import.yaml diff --git a/pkg/dependency/parser/nodejs/pnpm/parse.go b/pkg/dependency/parser/nodejs/pnpm/parse.go index aad4e138b9fe..bbe6c5a57aab 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse.go @@ -183,7 +183,7 @@ func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependen if dep, ok := lockFile.Importers.Root.DevDependencies[name]; ok && dep.Version == ver { relationship = ftypes.RelationshipDirect } - if dep, ok := lockFile.Importers.Root.Dependencies[name]; ok && dep.Version == ver { + if dep, ok := lockFile.Importers.Root.Dependencies[name]; ok && p.trimPeerDeps(dep.Version, lockVer) == ver { relationship = ftypes.RelationshipDirect dev = false // mark root direct deps to update `dev` field of their child deps. } @@ -208,10 +208,11 @@ func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependen } } + visited := make(map[string]struct{}) // Overwrite the `Dev` field for dev deps and their child dependencies. for _, pkg := range resolvedPkgs { if !pkg.Dev { - p.markRootPkgs(pkg.ID, resolvedPkgs, resolvedDeps) + p.markRootPkgs(pkg.ID, resolvedPkgs, resolvedDeps, visited) } } @@ -219,7 +220,10 @@ func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependen } // markRootPkgs sets `Dev` to false for non dev dependency. -func (p *Parser) markRootPkgs(id string, pkgs map[string]ftypes.Package, deps map[string]ftypes.Dependency) { +func (p *Parser) markRootPkgs(id string, pkgs map[string]ftypes.Package, deps map[string]ftypes.Dependency, visited map[string]struct{}) { + if _, ok := visited[id]; ok { + return + } pkg, ok := pkgs[id] if !ok { return @@ -227,10 +231,11 @@ func (p *Parser) markRootPkgs(id string, pkgs map[string]ftypes.Package, deps ma pkg.Dev = false pkgs[id] = pkg + visited[id] = struct{}{} // Update child deps for _, depID := range deps[id].DependsOn { - p.markRootPkgs(depID, pkgs, deps) + p.markRootPkgs(depID, pkgs, deps, visited) } return } diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_test.go b/pkg/dependency/parser/nodejs/pnpm/parse_test.go index 317b468020c7..ec869d6ff492 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_test.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_test.go @@ -59,6 +59,18 @@ func TestParse(t *testing.T) { want: pnpmV9, wantDeps: pnpmV9Deps, }, + { + name: "v9", + file: "testdata/pnpm-lock_v9.yaml", + want: pnpmV9, + wantDeps: pnpmV9Deps, + }, + { + name: "v9 with cyclic dependencies import", + file: "testdata/pnpm-lock_v9_cyclic_import.yaml", + want: pnpmV9CyclicImport, + wantDeps: pnpmV9CyclicImportDeps, + }, } for _, tt := range tests { diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go index 52b69ce79ee3..3c9383bd1e25 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go @@ -900,4 +900,68 @@ var ( }, }, } + + pnpmV9CyclicImport = []ftypes.Package{ + { + ID: "update-browserslist-db@1.0.16", + Name: "update-browserslist-db", + Version: "1.0.16", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "browserslist@4.23.0", + Name: "browserslist", + Version: "4.23.0", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "caniuse-lite@1.0.30001627", + Name: "caniuse-lite", + Version: "1.0.30001627", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "electron-to-chromium@1.4.789", + Name: "electron-to-chromium", + Version: "1.4.789", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "escalade@3.1.2", + Name: "escalade", + Version: "3.1.2", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "node-releases@2.0.14", + Name: "node-releases", + Version: "2.0.14", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "picocolors@1.0.1", + Name: "picocolors", + Version: "1.0.1", + Relationship: ftypes.RelationshipIndirect, + }, + } + pnpmV9CyclicImportDeps = []ftypes.Dependency{ + { + ID: "browserslist@4.23.0", + DependsOn: []string{ + "caniuse-lite@1.0.30001627", + "electron-to-chromium@1.4.789", + "node-releases@2.0.14", + "update-browserslist-db@1.0.16", + }, + }, + { + ID: "update-browserslist-db@1.0.16", + DependsOn: []string{ + "browserslist@4.23.0", + "escalade@3.1.2", + "picocolors@1.0.1", + }, + }, + } ) diff --git a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9_cyclic_import.yaml b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9_cyclic_import.yaml new file mode 100644 index 000000000000..08a7991b0e79 --- /dev/null +++ b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9_cyclic_import.yaml @@ -0,0 +1,67 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + update-browserslist-db: + specifier: 1.0.16 + version: 1.0.16(browserslist@4.23.0) + +packages: + + browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + caniuse-lite@1.0.30001627: + resolution: {integrity: sha512-4zgNiB8nTyV/tHhwZrFs88ryjls/lHiqFhrxCW4qSTeuRByBVnPYpDInchOIySWknznucaf31Z4KYqjfbrecVw==} + + electron-to-chromium@1.4.789: + resolution: {integrity: sha512-0VbyiaXoT++Fi2vHGo2ThOeS6X3vgRCWrjPeO2FeIAWL6ItiSJ9BqlH8LfCXe3X1IdcG+S0iLoNaxQWhfZoGzQ==} + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + update-browserslist-db@1.0.16: + resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + +snapshots: + + browserslist@4.23.0: + dependencies: + caniuse-lite: 1.0.30001627 + electron-to-chromium: 1.4.789 + node-releases: 2.0.14 + update-browserslist-db: 1.0.16(browserslist@4.23.0) + + caniuse-lite@1.0.30001627: {} + + electron-to-chromium@1.4.789: {} + + escalade@3.1.2: {} + + node-releases@2.0.14: {} + + picocolors@1.0.1: {} + + update-browserslist-db@1.0.16(browserslist@4.23.0): + dependencies: + browserslist: 4.23.0 + escalade: 3.1.2 + picocolors: 1.0.1 From faa9d92cfeb8d924deda2dac583b6c97099c08d9 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:41:39 +0600 Subject: [PATCH 137/352] fix(python): compare pkg names from `poetry.lock` and `pyproject.toml` in lowercase (#6852) --- pkg/dependency/parser/python/poetry/parse.go | 6 ++++-- pkg/fanal/analyzer/language/python/poetry/poetry.go | 9 ++++++++- .../language/python/poetry/testdata/happy/pyproject.toml | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/dependency/parser/python/poetry/parse.go b/pkg/dependency/parser/python/poetry/parse.go index e691a0cb2532..8b06942d3b6b 100644 --- a/pkg/dependency/parser/python/poetry/parse.go +++ b/pkg/dependency/parser/python/poetry/parse.go @@ -105,7 +105,7 @@ func (p *Parser) parseDependencies(deps map[string]any, pkgVersions map[string][ } func (p *Parser) parseDependency(name string, versRange any, pkgVersions map[string][]string) (string, error) { - name = normalizePkgName(name) + name = NormalizePkgName(name) vers, ok := pkgVersions[name] if !ok { return "", xerrors.Errorf("no version found for %q", name) @@ -149,9 +149,11 @@ func matchVersion(currentVersion, constraint string) (bool, error) { return c.Check(v), nil } -func normalizePkgName(name string) string { +// NormalizePkgName normalizes the package name based on pep-0426 +func NormalizePkgName(name string) string { // The package names don't use `_`, `.` or upper case, but dependency names can contain them. // We need to normalize those names. + // cf. https://peps.python.org/pep-0426/#name name = strings.ToLower(name) // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L819 name = strings.ReplaceAll(name, "_", "-") // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L50 name = strings.ReplaceAll(name, ".", "-") // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L816 diff --git a/pkg/fanal/analyzer/language/python/poetry/poetry.go b/pkg/fanal/analyzer/language/python/poetry/poetry.go index 600f53bafc92..b16ba88481c5 100644 --- a/pkg/fanal/analyzer/language/python/poetry/poetry.go +++ b/pkg/fanal/analyzer/language/python/poetry/poetry.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/python/poetry" @@ -102,8 +103,8 @@ func (a poetryAnalyzer) mergePyProject(fsys fs.FS, dir string, app *types.Applic return xerrors.Errorf("unable to parse %s: %w", path, err) } + // Identify the direct/transitive dependencies for i, pkg := range app.Packages { - // Identify the direct/transitive dependencies if _, ok := p[pkg.Name]; ok { app.Packages[i].Relationship = types.RelationshipDirect } else { @@ -127,5 +128,11 @@ func (a poetryAnalyzer) parsePyProject(fsys fs.FS, path string) (map[string]any, if err != nil { return nil, err } + + // Packages from `pyproject.toml` can use uppercase characters, `.` and `_`. + parsed = lo.MapKeys(parsed, func(_ any, pkgName string) string { + return poetry.NormalizePkgName(pkgName) + }) + return parsed, nil } diff --git a/pkg/fanal/analyzer/language/python/poetry/testdata/happy/pyproject.toml b/pkg/fanal/analyzer/language/python/poetry/testdata/happy/pyproject.toml index 2df330973ff2..9d23492736e4 100644 --- a/pkg/fanal/analyzer/language/python/poetry/testdata/happy/pyproject.toml +++ b/pkg/fanal/analyzer/language/python/poetry/testdata/happy/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Trivy"] [tool.poetry.dependencies] python = "^3.9" -flask = "^1.0" +Flask = "^1.0" requests = {version = "2.28.1", optional = true} [tool.poetry.dev-dependencies] From d4aea2788175dabe5ec022c0e6267900f4c24194 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 5 Jun 2024 14:33:12 +0400 Subject: [PATCH 138/352] ci: create release branch (#6859) Signed-off-by: knqyf263 --- .github/workflows/release-please.yaml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml index 290cdce1c4ce..de3e0def9ddd 100644 --- a/.github/workflows/release-please.yaml +++ b/.github/workflows/release-please.yaml @@ -55,7 +55,7 @@ jobs: if: ${{ steps.extract_info.outputs.version }} uses: actions/github-script@v7 with: - github-token: ${{ secrets.ORG_REPO_TOKEN }} + github-token: ${{ secrets.ORG_REPO_TOKEN }} # To trigger another workflow script: | await github.rest.git.createRef({ owner: context.repo.owner, @@ -64,6 +64,23 @@ jobs: sha: context.sha }); + # When v0.50.0 is released, a release branch "release/v0.50" is created. + - name: Create release branch for patch versions + if: ${{ endsWith(steps.extract_info.outputs.version, '.0') }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} # Should not trigger the workflow again + script: | + const version = '${{ steps.extract_info.outputs.version }}'; + const minorVersion = version.slice(0, version.lastIndexOf('.')); + const releaseBranch = `release/v${minorVersion}`; + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/heads/${releaseBranch}`, + sha: context.sha + }); + # Since skip-github-release is specified, googleapis/release-please-action doesn't delete the label from PR. # This label prevents the subsequent PRs from being created. Therefore, we need to delete it ourselves. # cf. https://github.com/googleapis/release-please?tab=readme-ov-file#release-please-bot-does-not-create-a-release-pr-why @@ -71,7 +88,7 @@ jobs: if: ${{ steps.extract_info.outputs.pr_number }} uses: actions/github-script@v7 with: - github-token: ${{ secrets.ORG_REPO_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const prNumber = parseInt('${{ steps.extract_info.outputs.pr_number }}', 10); github.rest.issues.removeLabel({ From 1e2db83e493c5ca7001c45903d9274949ca7ee9f Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 6 Jun 2024 14:08:55 +0400 Subject: [PATCH 139/352] ci: automate backporting process (#6781) Signed-off-by: knqyf263 --- .github/workflows/backport.yaml | 38 +++++++++++++ docs/community/maintainer/backporting.md | 59 ++++++++++++++++++++ misc/backport/backport.sh | 71 ++++++++++++++++++++++++ misc/triage/labels.yaml | 7 ++- mkdocs.yml | 1 + 5 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/backport.yaml create mode 100644 docs/community/maintainer/backporting.md create mode 100755 misc/backport/backport.sh diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml new file mode 100644 index 000000000000..127b10012429 --- /dev/null +++ b/.github/workflows/backport.yaml @@ -0,0 +1,38 @@ +name: Automatic Backporting + +on: + issue_comment: + types: [created] + +jobs: + backport: + name: Backport PR + if: | + github.event.issue.pull_request && + github.event.issue.pull_request.merged_at != null && + startsWith(github.event.comment.body, '@aqua-bot backport release/') && + (github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER') + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract branch name + run: | + BRANCH_NAME=$(echo ${{ github.event.comment.body }} | grep -oE '@aqua-bot backport\s+(\S+)' | awk '{print $3}') + echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV + + - name: Set up Git user + run: | + git config --global user.email "actions@github.com" + git config --global user.name "GitHub Actions" + + - name: Run backport script + run: ./misc/backport/backport.sh ${{ env.BRANCH_NAME }} ${{ github.event.issue.number }} + env: + # Use ORG_REPO_TOKEN instead of GITHUB_TOKEN + # This allows the created PR to trigger tests and other workflows + GITHUB_TOKEN: ${{ secrets.ORG_REPO_TOKEN }} \ No newline at end of file diff --git a/docs/community/maintainer/backporting.md b/docs/community/maintainer/backporting.md new file mode 100644 index 000000000000..bc70f26a9836 --- /dev/null +++ b/docs/community/maintainer/backporting.md @@ -0,0 +1,59 @@ +# Backporting Process + +This document outlines the backporting process for Trivy, including when to create patch releases and how to perform the backporting. + +## When to Create Patch Releases + +In general, small changes should not be backported and should be included in the next minor release. +However, patch releases should be made in the following cases: + +* Fixes for HIGH or CRITICAL vulnerabilities in Trivy itself or Trivy's dependencies +* Fixes for bugs that cause panic during Trivy execution or otherwise interfere with normal usage + +In these cases, the fixes should be backported using the procedure [described below](#backporting-procedure). +At the maintainer's discretion, other bug fixes may be included in the patch release containing these hotfixes. + +## Versioning + +Trivy follows [Semantic Versioning](https://semver.org/), using version numbers in the format MAJOR.MINOR.PATCH. +When creating a patch release, the PATCH part of the version number is incremented. +For example, if a fix is being distributed for v0.50.0, the patch release would be v0.50.1. + +## Backporting Procedure + +1. A release branch (e.g., `release/v0.50`) is automatically created when a new minor version is released. +1. Create a pull request (PR) against the main branch with the necessary fixes. If the fixes are already merged into the main branch, skip this step. +1. Once the PR with the fixes is merged, comment `@aqua-bot backport ` on the PR (e.g., `@aqua-bot backport release/v0.50`). This will trigger the automated backporting process using GitHub Actions. +1. The automated process will create a new PR with the backported changes. Ensure that all tests pass for this PR. +1. Once the tests pass, merge the automatically created PR into the release branch. +1. Merge [a release PR](release-flow.md) on the release branch and release the patch version. + +!!! note + Even if a conflict occurs, a PR is created by forceful commit, in which case the conflict should be resolved manually. + If you want to re-run a backport of the same PR, close the existing PR, delete the branch and re-run it. + +### Example +To better understand the backporting procedure, let's walk through an example using the releases of v0.50. + +```mermaid +gitGraph: + commit id:"Feature 1" + commit id:"v0.50.0 release" tag:"v0.50.0" + + branch "release/v0.50" + + checkout main + commit id:"Bugfix 1" + + checkout "release/v0.50" + cherry-pick id:"Bugfix 1" + + checkout main + commit id:"Feature 2" + commit id:"Bugfix 2" + commit id:"Feature 3" + + checkout "release/v0.50" + cherry-pick id:"Bugfix 2" + commit id:"v0.50.1 release" tag:"v0.50.1" +``` \ No newline at end of file diff --git a/misc/backport/backport.sh b/misc/backport/backport.sh new file mode 100755 index 000000000000..263a27d8b653 --- /dev/null +++ b/misc/backport/backport.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +set -e + +BRANCH_NAME=$1 +PR_NUMBER=$2 + +echo "Backporting PR #$PR_NUMBER to branch $BRANCH_NAME" + +# Get the merge commit hash of the pull request +echo "Fetching merge commit hash of PR #$PR_NUMBER..." +COMMIT_HASH=$(gh api /repos/"$GITHUB_REPOSITORY"/pulls/"$PR_NUMBER" | jq -r '.merge_commit_sha') +echo "Merge commit hash: $COMMIT_HASH" + +# Get the title of the original pull request +echo "Fetching title of PR #$PR_NUMBER..." +ORIGINAL_PR_TITLE=$(gh api /repos/"$GITHUB_REPOSITORY"/pulls/"$PR_NUMBER" | jq -r '.title') +echo "Original PR title: $ORIGINAL_PR_TITLE" + +# Checkout the base branch +echo "Checking out base branch: $BRANCH_NAME" +git checkout "$BRANCH_NAME" + +# Create a new branch with the PR number and branch name +NEW_BRANCH="backport-pr-$PR_NUMBER-to-$BRANCH_NAME" + +echo "Creating new branch: $NEW_BRANCH" +git switch -c "$NEW_BRANCH" + +# Create the pull request title +PR_TITLE="$ORIGINAL_PR_TITLE [backport: $BRANCH_NAME]" + +# Create the pull request description +PR_DESCRIPTION="# Backport + +This will backport the following commits from \`main\` to \`$BRANCH_NAME\`: + - https://github.com/$GITHUB_REPOSITORY/pull/$PR_NUMBER" + +echo "Cherry-picking commit: $COMMIT_HASH" +if git cherry-pick "$COMMIT_HASH"; then + echo "Cherry-pick successful" +else + echo "Cherry-pick failed due to conflicts, force-committing changes" + + # Add only conflicted files + git diff --name-only --diff-filter=U | xargs git add + + # Force-commit the changes with conflicts + git commit -m "Force-committed changes with conflicts for cherry-pick of $COMMIT_HASH" + + PR_DESCRIPTION="$PR_DESCRIPTION + +## ⚠️ Warning +Conflicts occurred during the cherry-pick and were force-committed without proper resolution. Please carefully review the changes, resolve any remaining conflicts, and ensure the code is in a valid state." +fi + +echo "Pushing new branch to origin: $NEW_BRANCH" +git push origin "$NEW_BRANCH" + +echo "Pull request title: $PR_TITLE" + +echo "Pull request description:" +echo "$PR_DESCRIPTION" + +# Create a new pull request with the original PR title, backport suffix, and description +echo "Creating pull request..." +gh pr create --base "$BRANCH_NAME" --head "$NEW_BRANCH" --title "$PR_TITLE" --body "$PR_DESCRIPTION" --repo "$GITHUB_REPOSITORY" --label "backport" + +# Add a comment to the original PR +echo "Adding comment to the original PR #$PR_NUMBER" +gh pr comment "$PR_NUMBER" --body "Backport PR created: https://github.com/$GITHUB_REPOSITORY/pull/$(gh pr view "$NEW_BRANCH" --json number --jq .number)" \ No newline at end of file diff --git a/misc/triage/labels.yaml b/misc/triage/labels.yaml index e2babc4cab12..ef61e1563bf2 100644 --- a/misc/triage/labels.yaml +++ b/misc/triage/labels.yaml @@ -127,10 +127,15 @@ labels: color: 0ebdb0 description: Issues relating to virtual machine scanning -# others +# community - name: good first issue color: 7057ff description: Denotes an issue ready for a new contributor, according to the "help wanted" guidelines. - name: help wanted color: 006b75 description: Denotes an issue that needs help from a contributor. Must meet "help wanted" guidelines. + +# release +- name: backport + color: A8F7BC + description: Backport PRs \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 664924cab59a..92bbbb24ac21 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -200,6 +200,7 @@ nav: - Add Service Support: community/contribute/checks/service-support.md - Maintainer: - Release Flow: community/maintainer/release-flow.md + - Backporting: community/maintainer/backporting.md - Help Wanted: community/maintainer/help-wanted.md - Triage: community/maintainer/triage.md theme: From 63eb85a064e1f40abf4a4eb96ca28d02afe641be Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 6 Jun 2024 17:16:56 +0400 Subject: [PATCH 140/352] docs: explain how VEX is applied (#6864) Signed-off-by: knqyf263 --- docs/docs/supply-chain/vex.md | 101 ++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/docs/docs/supply-chain/vex.md b/docs/docs/supply-chain/vex.md index 6dc02b6d088b..0ceaeeeb67b3 100644 --- a/docs/docs/supply-chain/vex.md +++ b/docs/docs/supply-chain/vex.md @@ -263,6 +263,8 @@ $ trivy image ghcr.io/aquasecurity/trivy:0.50.0 --vex trivy.openvex.json VEX documents can indeed be reused across different container images, eliminating the need to issue separate VEX documents for each image. This is particularly useful when there is a common component or library that is used across multiple projects or container images. +You can see [the appendix](#applying-vex-to-dependency-trees) for more details on how VEX is applied in Trivy. + ### Scan with VEX Provide the VEX when scanning your target. @@ -412,6 +414,8 @@ At present, the specified relationship category is not taken into account and al - installed_on - installed_with +You can see [the appendix](#applying-vex-to-dependency-trees) for more details on how VEX is applied in Trivy. + ### Scan with CSAF VEX Provide the CSAF document when scanning your target. @@ -470,6 +474,103 @@ does not match: - `pkg:maven/com.google.guava/guava@24.1.1?classifier=sources` - `classifier` must have the same value. +### Applying VEX to Dependency Trees + +Trivy internally generates a dependency tree and applies VEX statements to this graph. +Let's consider a project with the following dependency tree, where `Module C v2.0.0` is assumed to have a vulnerability CVE-XXXX-YYYY: + +```mermaid +graph TD; + modRootA(Module Root A v1.0.0) + modB(Module B v1.0.0) + modC(Module C v2.0.0) + + modRootA-->modB + modB-->modC +``` + +Now, suppose a VEX statement is issued for `Module B` as follows: + +```json +"statements": [ + { + "vulnerability": {"name": "CVE-XXXX-YYYY"}, + "products": [ + { + "@id": "pkg:golang/module-b@1.0.0", + "subcomponents": [ + { "@id": "pkg:golang/module-c@2.0.0" } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } +] +``` + +It declares that `Module B` is not affected by CVE-XXXX-YYYY on `Module C`. + +!!! note + The VEX in this example defines the relationship between `Module B` and `Module C`. + However, as Trivy traverses all parents from vulnerable packages, it is also possible to define a VEX for the relationship between a vulnerable package and any parent, such as `Module A` and `Module C`, etc. + +Mapping this VEX onto the dependency tree would look like this: + +```mermaid +graph TD; + modRootA(Module Root A v1.0.0) + + subgraph "VEX (Not Affected)" + modB(Module B v1.0.0) + modC(Module C v2.0.0) + end + + modRootA-->modB + modB-->modC +``` + +In this case, it's clear that `Module Root A` is also not affected by CVE-XXXX-YYYY, so this vulnerability is suppressed. + +Now, let's consider another project: + +```mermaid +graph TD; + modRootZ(Module Root Z v1.0.0) + modB'(Module B v1.0.0) + modC'(Module C v2.0.0) + modD'(Module D v3.0.0) + + modRootZ-->modB' + modRootZ-->modD' + modB'-->modC' + modD'-->modC' +``` + +Assuming the same VEX as before, applying it to this dependency tree would look like: + +```mermaid +graph TD; + modRootZ(Module Root Z v1.0.0) + + subgraph "VEX (Not Affected)" + modB'(Module B v1.0.0) + modC'(Module C v2.0.0) + end + + modD'(Module D v3.0.0) + + modRootZ-->modB' + modRootZ-->modD' + modB'-->modC' + modD'-->modC' +``` + +`Module Root Z` depends on `Module C` via multiple paths. +While the VEX tells us that `Module B` is not affected by the vulnerability, `Module D` might be. +In the absence of a VEX, the default assumption is that it is affected. +Taking all of this into account, Trivy determines that `Module Root Z` is affected by this vulnerability. + [csaf]: https://oasis-open.github.io/csaf-documentation/specification.html [openvex]: https://github.com/openvex/spec From e8d8af45046b2075420850a29025e62347bd268e Mon Sep 17 00:00:00 2001 From: Itay Shakury Date: Thu, 6 Jun 2024 20:35:00 +0300 Subject: [PATCH 141/352] chore: auto label discussions (#5259) --- .github/actions/trivy-triage/Makefile | 3 + .github/actions/trivy-triage/action.yaml | 29 +++++++ .github/actions/trivy-triage/config.json | 14 ++++ .github/actions/trivy-triage/helpers.js | 69 +++++++++++++++++ .github/actions/trivy-triage/helpers.test.js | 77 +++++++++++++++++++ .../testutils/discussion-payload-sample.json | 65 ++++++++++++++++ .../trivy-triage/testutils/fetchDiscussion.sh | 29 +++++++ .../trivy-triage/testutils/fetchLabels.sh | 16 ++++ .../trivy-triage/testutils/labelDiscussion.sh | 16 ++++ .github/trivy-triage.yaml | 16 ++++ 10 files changed, 334 insertions(+) create mode 100644 .github/actions/trivy-triage/Makefile create mode 100644 .github/actions/trivy-triage/action.yaml create mode 100644 .github/actions/trivy-triage/config.json create mode 100644 .github/actions/trivy-triage/helpers.js create mode 100644 .github/actions/trivy-triage/helpers.test.js create mode 100644 .github/actions/trivy-triage/testutils/discussion-payload-sample.json create mode 100755 .github/actions/trivy-triage/testutils/fetchDiscussion.sh create mode 100755 .github/actions/trivy-triage/testutils/fetchLabels.sh create mode 100755 .github/actions/trivy-triage/testutils/labelDiscussion.sh create mode 100644 .github/trivy-triage.yaml diff --git a/.github/actions/trivy-triage/Makefile b/.github/actions/trivy-triage/Makefile new file mode 100644 index 000000000000..de557aa565f7 --- /dev/null +++ b/.github/actions/trivy-triage/Makefile @@ -0,0 +1,3 @@ +.PHONEY: test +test: helpers.js helpers.test.js + node --test helpers.test.js diff --git a/.github/actions/trivy-triage/action.yaml b/.github/actions/trivy-triage/action.yaml new file mode 100644 index 000000000000..f847c4333866 --- /dev/null +++ b/.github/actions/trivy-triage/action.yaml @@ -0,0 +1,29 @@ +name: 'trivy-discussion-triage' +description: 'automatic triage of Trivy discussions' +inputs: + discussion_num: + description: 'Discussion number to triage' + required: false +runs: + using: "composite" + steps: + - name: Conditionally label discussions based on category and content + env: + GH_TOKEN: ${{ github.token }} + uses: actions/github-script@v6 + with: + script: | + const {detectDiscussionLabels, fetchDiscussion, labelDiscussion } = require('${{ github.action_path }}/helpers.js'); + const config = require('${{ github.action_path }}/config.json'); + discussionNum = parseInt(${{ inputs.discussion_num }}); + let discussion; + if (discussionNum > 0) { + discussion = (await fetchDiscussion(github, context.repo.owner, context.repo.repo, discussionNum)).repository.discussion; + } else { + discussion = context.payload.discussion; + } + const labels = detectDiscussionLabels(discussion, config.discussionLabels); + if (labels.length > 0) { + console.log(`Adding labels ${labels} to discussion ${discussion.node_id}`); + labelDiscussion(github, discussion.node_id, labels); + } diff --git a/.github/actions/trivy-triage/config.json b/.github/actions/trivy-triage/config.json new file mode 100644 index 000000000000..a972fb024f58 --- /dev/null +++ b/.github/actions/trivy-triage/config.json @@ -0,0 +1,14 @@ +{ + "discussionLabels": { + "Container Image":"LA_kwDOCsUTCM75TTQU", + "Filesystem":"LA_kwDOCsUTCM75TTQX", + "Git Repository":"LA_kwDOCsUTCM75TTQk", + "Virtual Machine Image":"LA_kwDOCsUTCM8AAAABMpz1bw", + "Kubernetes":"LA_kwDOCsUTCM75TTQv", + "AWS":"LA_kwDOCsUTCM8AAAABMpz1aA", + "Vulnerability":"LA_kwDOCsUTCM75TTPa", + "Misconfiguration":"LA_kwDOCsUTCM75TTP8", + "License":"LA_kwDOCsUTCM77ztRR", + "Secret":"LA_kwDOCsUTCM75TTQL" + } +} \ No newline at end of file diff --git a/.github/actions/trivy-triage/helpers.js b/.github/actions/trivy-triage/helpers.js new file mode 100644 index 000000000000..121d5b38ffaa --- /dev/null +++ b/.github/actions/trivy-triage/helpers.js @@ -0,0 +1,69 @@ +module.exports = { + detectDiscussionLabels: (discussion, configDiscussionLabels) => { + res = []; + const discussionId = discussion.id; + const category = discussion.category.name; + const body = discussion.body; + if (category !== "Ideas") { + consolt.log("skipping discussion with category ${category} and body ${body}"); + } + const scannerPattern = /### Scanner\n\n(.+)/; + const scannerFound = body.match(scannerPattern); + if (scannerFound && scannerFound.length > 1) { + res.push(configDiscussionLabels[scannerFound[1]]); + } + const targetPattern = /### Target\n\n(.+)/; + const targetFound = body.match(targetPattern); + if (targetFound && targetFound.length > 1) { + res.push(configDiscussionLabels[targetFound[1]]); + } + return res; + }, + fetchDiscussion: async (github, owner, repo, discussionNum) => { + const query = `query Discussion ($owner: String!, $repo: String!, $discussion_num: Int!){ + repository(name: $repo, owner: $owner) { + discussion(number: $discussion_num) { + number, + id, + body, + category { + id, + name + }, + labels(first: 100) { + edges { + node { + id, + name + } + } + } + } + } + }`; + const vars = { + owner: owner, + repo: repo, + discussion_num: discussionNum + }; + return github.graphql(query, vars); + }, + labelDiscussion: async (github, discussionId, labelIds) => { + const query = `mutation AddLabels($labelId: ID!, $labelableId:ID!) { + addLabelsToLabelable( + input: {labelIds: [$labelId], labelableId: $labelableId} + ) { + clientMutationId + } + }`; + // TODO: add all labels in one call + labelIds.forEach((labelId) => { + const vars = { + labelId: labelId, + labelableId: discussionId + }; + github.graphql(query, vars); + }); + } +}; + diff --git a/.github/actions/trivy-triage/helpers.test.js b/.github/actions/trivy-triage/helpers.test.js new file mode 100644 index 000000000000..3ef2ef810124 --- /dev/null +++ b/.github/actions/trivy-triage/helpers.test.js @@ -0,0 +1,77 @@ +const assert = require('node:assert/strict'); +const { describe, it } = require('node:test'); +const {detectDiscussionLabels} = require('./helpers.js'); + +const configDiscussionLabels = { + "Container Image":"ContainerImageLabel", + "Filesystem":"FilesystemLabel", + "Vulnerability":"VulnerabilityLabel", + "Misconfiguration":"MisconfigurationLabel", +}; + +describe('trivy-triage', async function() { + describe('detectDiscussionLabels', async function() { + it('detect scanner label', async function() { + const discussion = { + body: 'hello hello\nbla bla.\n### Scanner\n\nVulnerability\n### Target\n\nContainer Image\nbye bye.', + category: { + name: 'Ideas' + } + }; + const labels = detectDiscussionLabels(discussion, configDiscussionLabels); + assert(labels.includes('VulnerabilityLabel')); + }); + it('detect target label', async function() { + const discussion = { + body: 'hello hello\nbla bla.\n### Scanner\n\nVulnerability\n### Target\n\nContainer Image\nbye bye.', + category: { + name: 'Ideas' + } + }; + const labels = detectDiscussionLabels(discussion, configDiscussionLabels); + assert(labels.includes('ContainerImageLabel')); + }); + it('detect label when it is first', async function() { + const discussion = { + body: '### Scanner\n\nVulnerability\n### Target\n\nContainer Image\nbye bye.', + category: { + name: 'Ideas' + } + }; + const labels = detectDiscussionLabels(discussion, configDiscussionLabels); + assert(labels.includes('ContainerImageLabel')); + }); + it('detect label when it is last', async function() { + const discussion = { + body: '### Scanner\n\nVulnerability\n### Target\n\nContainer Image', + category: { + name: 'Ideas' + } + }; + const labels = detectDiscussionLabels(discussion, configDiscussionLabels); + assert(labels.includes('ContainerImageLabel')); + }); + it('detect scanner and target labels', async function() { + const discussion = { + body: 'hello hello\nbla bla.\n### Scanner\n\nVulnerability\n### Target\n\nContainer Image\nbye bye.', + category: { + name: 'Ideas' + } + }; + const labels = detectDiscussionLabels(discussion, configDiscussionLabels); + assert(labels.includes('ContainerImageLabel')); + assert(labels.includes('VulnerabilityLabel')); + }); + it('not detect other labels', async function() { + const discussion = { + body: 'hello hello\nbla bla.\n### Scanner\n\nVulnerability\n### Target\n\nContainer Image\nbye bye.', + category: { + name: 'Ideas' + } + }; + const labels = detectDiscussionLabels(discussion, configDiscussionLabels); + assert(!labels.includes('FilesystemLabel')); + assert(!labels.includes('MisconfigurationLabel')); + }); + }); +}); diff --git a/.github/actions/trivy-triage/testutils/discussion-payload-sample.json b/.github/actions/trivy-triage/testutils/discussion-payload-sample.json new file mode 100644 index 000000000000..5615fddee804 --- /dev/null +++ b/.github/actions/trivy-triage/testutils/discussion-payload-sample.json @@ -0,0 +1,65 @@ +{ + "active_lock_reason": null, + "answer_chosen_at": null, + "answer_chosen_by": null, + "answer_html_url": null, + "author_association": "OWNER", + "body": "### Description\n\nlfdjs lfkdj dflsakjfd ';djk \r\nfadfd \r\nasdlkf \r\na;df \r\ndfsal;kfd ;akjl\n\n### Target\n\nContainer Image\n\n### Scanner\n\nMisconfiguration", + "category": { + "created_at": "2023-07-02T10:14:46.000+03:00", + "description": "Share ideas for new features", + "emoji": ":bulb:", + "id": 39743708, + "is_answerable": false, + "name": "Ideas", + "node_id": "DIC_kwDOE0GiPM4CXnDc", + "repository_id": 323068476, + "slug": "ideas", + "updated_at": "2023-07-02T10:14:46.000+03:00" + }, + "comments": 0, + "created_at": "2023-09-11T08:40:11Z", + "html_url": "https://github.com/itaysk/testactions/discussions/9", + "id": 5614504, + "locked": false, + "node_id": "D_kwDOE0GiPM4AVauo", + "number": 9, + "reactions": { + "+1": 0, + "-1": 0, + "confused": 0, + "eyes": 0, + "heart": 0, + "hooray": 0, + "laugh": 0, + "rocket": 0, + "total_count": 0, + "url": "https://api.github.com/repos/itaysk/testactions/discussions/9/reactions" + }, + "repository_url": "https://api.github.com/repos/itaysk/testactions", + "state": "open", + "state_reason": null, + "timeline_url": "https://api.github.com/repos/itaysk/testactions/discussions/9/timeline", + "title": "Title title", + "updated_at": "2023-09-11T08:40:11Z", + "user": { + "avatar_url": "https://avatars.githubusercontent.com/u/1161307?v=4", + "events_url": "https://api.github.com/users/itaysk/events{/privacy}", + "followers_url": "https://api.github.com/users/itaysk/followers", + "following_url": "https://api.github.com/users/itaysk/following{/other_user}", + "gists_url": "https://api.github.com/users/itaysk/gists{/gist_id}", + "gravatar_id": "", + "html_url": "https://github.com/itaysk", + "id": 1161307, + "login": "itaysk", + "node_id": "MDQ6VXNlcjExNjEzMDc=", + "organizations_url": "https://api.github.com/users/itaysk/orgs", + "received_events_url": "https://api.github.com/users/itaysk/received_events", + "repos_url": "https://api.github.com/users/itaysk/repos", + "site_admin": false, + "starred_url": "https://api.github.com/users/itaysk/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/itaysk/subscriptions", + "type": "User", + "url": "https://api.github.com/users/itaysk" + } +} \ No newline at end of file diff --git a/.github/actions/trivy-triage/testutils/fetchDiscussion.sh b/.github/actions/trivy-triage/testutils/fetchDiscussion.sh new file mode 100755 index 000000000000..9c213f948d91 --- /dev/null +++ b/.github/actions/trivy-triage/testutils/fetchDiscussion.sh @@ -0,0 +1,29 @@ +#! /bin/bash +# fetch discussion by discussion number +# requires authenticated gh cli, assumes repo but current git repository +# args: +# $1: discussion number, e.g 123, required + +discussion_num="$1" +gh api graphql -F discussion_num="$discussion_num" -F repo="{repo}" -F owner="{owner}" -f query=' + query Discussion ($owner: String!, $repo: String!, $discussion_num: Int!){ + repository(name: $repo, owner: $owner) { + discussion(number: $discussion_num) { + number, + id, + body, + category { + id, + name + }, + labels(first: 100) { + edges { + node { + id, + name + } + } + } + } + } + }' \ No newline at end of file diff --git a/.github/actions/trivy-triage/testutils/fetchLabels.sh b/.github/actions/trivy-triage/testutils/fetchLabels.sh new file mode 100755 index 000000000000..87736eefc1aa --- /dev/null +++ b/.github/actions/trivy-triage/testutils/fetchLabels.sh @@ -0,0 +1,16 @@ +#! /bin/bash +# fetch labels and their IDs +# requires authenticated gh cli, assumes repo but current git repository + +gh api graphql -F repo="{repo}" -F owner="{owner}" -f query=' + query GetLabelIds($owner: String!, $repo: String!) { + repository(name: $repo, owner: $owner) { + id + labels(first: 100) { + nodes { + id + name + } + } + } + }' \ No newline at end of file diff --git a/.github/actions/trivy-triage/testutils/labelDiscussion.sh b/.github/actions/trivy-triage/testutils/labelDiscussion.sh new file mode 100755 index 000000000000..e10d043f9f39 --- /dev/null +++ b/.github/actions/trivy-triage/testutils/labelDiscussion.sh @@ -0,0 +1,16 @@ +#! /bin/bash +# add a label to a discussion +# requires authenticated gh cli, assumes repo but current git repository +# args: +# $1: discussion ID (not number!), e.g DIC_kwDOE0GiPM4CXnDc, required +# $2: label ID, e.g. MDU6TGFiZWwzNjIzNjY0MjQ=, required +discussion_id="$1" +label_id="$2" +gh api graphql -F labelableId="$discussion_id" -F labelId="$label_id" -F repo="{repo}" -F owner="{owner}" -f query=' + mutation AddLabels($labelId: ID!, $labelableId:ID!) { + addLabelsToLabelable( + input: {labelIds: [$labelId], labelableId: $labelableId} + ) { + clientMutationId + } + }' \ No newline at end of file diff --git a/.github/trivy-triage.yaml b/.github/trivy-triage.yaml new file mode 100644 index 000000000000..e0ddc568f23e --- /dev/null +++ b/.github/trivy-triage.yaml @@ -0,0 +1,16 @@ +name: Triage Discussion +on: + discussion: + types: [created] + workflow_dispatch: + inputs: + discussion_num: + required: true +jobs: + label: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/trivy-triage + with: + discussion_num: ${{ github.event.inputs.discussion_num }} From 72e20d765be7387faeafb976f4cb4038ea13cebe Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 7 Jun 2024 10:57:03 +0600 Subject: [PATCH 142/352] ci: use author permission check instead of `author_association` field for backport workflow (#6870) --- .github/workflows/backport.yaml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index 127b10012429..9bae822e8815 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -5,13 +5,33 @@ on: types: [created] jobs: + check_permission: + name: Check comment author permissions + runs-on: ubuntu-latest + outputs: + is_maintainer: ${{ steps.check_permission.outputs.is_maintainer }} + steps: + - name: Check permission + id: check_permission + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PERMISSION=$(gh api /repos/${{ github.repository }}/collaborators/${{ github.actor }}/permission --jq '.permission') + if [ "$PERMISSION" == "admin" ] || [ "$PERMISSION" == "write" ]; then + echo "is_maintainer=true" >> $GITHUB_OUTPUT + else + echo "is_maintainer=false" >> $GITHUB_OUTPUT + fi + + backport: name: Backport PR + needs: check_permission # run this job after checking permissions if: | + needs.check_permission.outputs.is_maintainer == 'true' && github.event.issue.pull_request && github.event.issue.pull_request.merged_at != null && - startsWith(github.event.comment.body, '@aqua-bot backport release/') && - (github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER') + startsWith(github.event.comment.body, '@aqua-bot backport release/') runs-on: ubuntu-latest steps: From bb26445e3df198df77930329f532ac5ab7a67af2 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 7 Jun 2024 10:58:27 +0600 Subject: [PATCH 143/352] fix(secret): `Asymmetric Private Key` shouldn't start with space (#6867) --- pkg/fanal/secret/builtin-rules.go | 2 +- pkg/fanal/secret/scanner_test.go | 8 ++++++++ pkg/fanal/secret/testdata/asymmetric-private-key.txt | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/fanal/secret/builtin-rules.go b/pkg/fanal/secret/builtin-rules.go index ae08f494ad7f..6d0c0eacfdcd 100644 --- a/pkg/fanal/secret/builtin-rules.go +++ b/pkg/fanal/secret/builtin-rules.go @@ -173,7 +173,7 @@ var builtinRules = []Rule{ Category: CategoryAsymmetricPrivateKey, Title: "Asymmetric Private Key", Severity: "HIGH", - Regex: MustCompile(`(?i)-----\s*?BEGIN[ A-Z0-9_-]*?PRIVATE KEY( BLOCK)?\s*?-----[\s]*?(?P[\sA-Za-z0-9=+/\\\r\n]+)[\s]*?-----\s*?END[ A-Z0-9_-]*? PRIVATE KEY( BLOCK)?\s*?-----`), + Regex: MustCompile(`(?i)-----\s*?BEGIN[ A-Z0-9_-]*?PRIVATE KEY( BLOCK)?\s*?-----[\s]*?(?P[A-Za-z0-9=+/\\\r\n][A-Za-z0-9=+/\\\s]+)[\s]*?-----\s*?END[ A-Z0-9_-]*? PRIVATE KEY( BLOCK)?\s*?-----`), SecretGroupName: "secret", Keywords: []string{"-----"}, }, diff --git a/pkg/fanal/secret/scanner_test.go b/pkg/fanal/secret/scanner_test.go index d152591cb2ca..04f1f08fc1b2 100644 --- a/pkg/fanal/secret/scanner_test.go +++ b/pkg/fanal/secret/scanner_test.go @@ -510,6 +510,14 @@ func TestSecretScanner(t *testing.T) { FirstCause: true, LastCause: true, }, + { + Number: 2, + Content: "", + Highlighted: "", + IsCause: false, + FirstCause: false, + LastCause: false, + }, }, }, } diff --git a/pkg/fanal/secret/testdata/asymmetric-private-key.txt b/pkg/fanal/secret/testdata/asymmetric-private-key.txt index 926230bd92f0..68e459e1a8c7 100644 --- a/pkg/fanal/secret/testdata/asymmetric-private-key.txt +++ b/pkg/fanal/secret/testdata/asymmetric-private-key.txt @@ -1,4 +1,6 @@ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAu/Nua0/1y08gkbnBfKd6VDHia8Na0ATgMQqZ4YEbi/t73g84IEPQPkLbPF3X De++JA1QzqTZbbePLsW44DbbgMX/5jj+Sh2SvGe5IXNeNwyyMox+DKQccJUPxbCxnhF/gSnF76cM BXJC63nDnGawz4g3qwU1+0sfyKG0ixFI3e3992fk6QnF49Cv5iqwjgKPIZjgfoM70r71XDKJjVTC rJdSBfyQwX2TU0CncglvJSzhhcuTeQZWldbk/BHjxINrqQIxKaG2OfBgkupPjnrImzSAUE9a/gIS REUVSamc69qqQnXER3Jmoy8HXiAQdPI+CpVVkI7FCCq4qD7fVqsNhwIDAQABAoIBAQC5707zNr1Q jk0IHR3+9agdFuSJ+08hr1Ei8vvcjN71kqqtuZyqvquKjJVamPMhRGV0QQAKDidTVV5+xPfqSBrK wBYyaXuXUr5RSMNrBjjUeOjo/PfOBaRk8/IQfoaYe3MKEotQVI+d67WsQl9zoFuWU4nO1G7c1Sry TpbPZSAS+6J7fUClUgT9pvg+EpoboXs+voeWTh9r9eracxUmlclVAdS3tP7xMv5R29EBYtjGKbF6 r0Ku/hXJjPu5Eck4/BeciEinVWn/yqSsqd5XKOUwTuLlUyAGWhJKcn/zWgaBYUvknzSmwePvW/W8 iwrEhP4GNHBEHisJHdWPtbCDdOVxAoGBAPqyR/9ocwZ3GhHz3dI53Z6UjKUPtRnxJb19ZS8UVN57 P7yCXpH+L6KhIxo9yx0D5Z4bdNSYTyjl6eFnv0FZA3UXsM2tyY+Ylih1LOqcttehJkK2JaFmuefx d6bcpPJG00EKFDZoTH5bbnrB3uGKUVJ5TMFlUbOgkATJL652VTNTAoGBAL/tVWwlO5ET80BSheJ/ V88rSF4AxK48ZNt5EG7RHph46KukwywPUnWRoFLxRtVP/udZf9Qq164IPGgDrn4E6VTpZwmp7HDv 6P8sSLwJj/YW3y9c57lA4SMoowO2ik09fbBJVvWLeev4n6taDNwgCZ4fuLUtMf/mUU3r80okeUp9 AoGAIySIyTn4HejmQ6v+5XBtK8TBLoZUKc3PL4/7di0QdJusZJ2V6jtKrC6QgCY3adrY/l/08bRk LGSGc62aduume2yVwU9iWPnX2tYKNN1BGFsjxOhJwCVpXCVSU5bMnJXnGU/zY2kdh/0DMLwqpU1B dyE/7EBqwpZ4eeNGBtvZt7cCgYB8jaZJJ6SPkzXiwWtXwTKYJMuzDaaWOGVvtRKACEBlzNmaQrPS jSMDX31/Nku0tVSEiSWW6DLOI1QoYHNGHyPZ0hrnP5pM9LTtnKybM0109ATlNNLA+6Tf70hTaYw5 cjV2STIg6eI2zEO6rRb5Z+U18/onwevX2X1cJ0rdC+yW9QKBgH0xSLUGFZwFDCPE+jKGgqJQme5Q 8oxHs1CTkV4SxeLFNldA9c6uESMppSUwO7wx+NaFAJw9m2Q9Ifmo57pncAx2iVXOA9Jxaa7YFIsL vKftqLPCPbAPPxkaqQi0Ico/1fzD10znRy66aosPBrbleduiynubgk+GVm9y/R6bDYhR ------END RSA PRIVATE KEY----- \ No newline at end of file +-----END RSA PRIVATE KEY----- + +-----BEGIN RSA PRIVATE KEY----- -----END RSA PRIVATE KEY----- \ No newline at end of file From 04af59c2906bcfc7f7970b4e8f45a90f04313170 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:44:07 +0600 Subject: [PATCH 144/352] fix(sbom): don't overwrite `srcEpoch` when decoding SBOM files (#6866) --- pkg/sbom/io/decode.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/sbom/io/decode.go b/pkg/sbom/io/decode.go index e4df3bee8489..917684962d20 100644 --- a/pkg/sbom/io/decode.go +++ b/pkg/sbom/io/decode.go @@ -271,6 +271,11 @@ func (m *Decoder) fillSrcPkg(c *core.Component, pkg *ftypes.Package) { } m.parseSrcVersion(pkg, c.SrcVersion) + // Source info was added from component or properties + if pkg.SrcName != "" && pkg.SrcVersion != "" { + return + } + // Fill source package information for components in third-party SBOMs . if pkg.SrcName == "" { pkg.SrcName = pkg.Name From 622c67b7647f94d0a0ca3acf711d8f847cdd8d98 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 7 Jun 2024 13:26:58 +0400 Subject: [PATCH 145/352] feat(plugin): add support for nested archives (#6845) Signed-off-by: knqyf263 --- docs/docs/plugin/user-guide.md | 7 ++++--- pkg/plugin/manager.go | 8 ++++++++ pkg/plugin/manager_unix_test.go | 16 ++++++++++++++-- .../test_plugin/{ => test_plugin}/plugin.yaml | 0 .../test_plugin/{ => test_plugin}/test.sh | 0 5 files changed, 26 insertions(+), 5 deletions(-) rename pkg/plugin/testdata/test_plugin/{ => test_plugin}/plugin.yaml (100%) rename pkg/plugin/testdata/test_plugin/{ => test_plugin}/test.sh (100%) diff --git a/docs/docs/plugin/user-guide.md b/docs/docs/plugin/user-guide.md index f26f741f7e6e..a02cd6643321 100644 --- a/docs/docs/plugin/user-guide.md +++ b/docs/docs/plugin/user-guide.md @@ -40,8 +40,6 @@ $ trivy plugin install referrer This command will download the plugin and install it in the plugin cache. - - Trivy adheres to the XDG specification, so the location depends on whether XDG_DATA_HOME is set. Trivy will now search XDG_DATA_HOME for the location of the Trivy plugins cache. The preference order is as follows: @@ -55,7 +53,10 @@ Furthermore, it is possible to download plugins that are not registered in the i $ trivy plugin install github.com/aquasecurity/trivy-plugin-kubectl ``` ```bash -$ trivy plugin install myplugin.tar.gz +$ trivy plugin install https://github.com/aquasecurity/trivy-plugin-kubectl/archive/refs/heads/main.zip +``` +```bash +$ trivy plugin install ./myplugin.tar.gz ``` If the plugin's Git repository is [properly tagged](./developer-guide.md#tagging-plugin-repositories), you can specify the version to install like this: diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 91dd30d368c3..949c87525be7 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -116,6 +116,14 @@ func (m *Manager) install(ctx context.Context, src string, opts Options) (Plugin } defer os.RemoveAll(tempDir) + if entries, err := os.ReadDir(tempDir); err != nil { + return Plugin{}, xerrors.Errorf("failed to read %s: %w", tempDir, err) + } else if len(entries) == 1 && entries[0].IsDir() { + // A single directory may be contained within an archive file. + // e.g. https://github.com/aquasecurity/trivy-plugin-referrer/archive/refs/heads/main.zip + tempDir = filepath.Join(tempDir, entries[0].Name()) + } + m.logger.DebugContext(ctx, "Loading the plugin metadata...") plugin, err := m.loadMetadata(tempDir) if err != nil { diff --git a/pkg/plugin/manager_unix_test.go b/pkg/plugin/manager_unix_test.go index fca1c8fac2ad..0250a80d7907 100644 --- a/pkg/plugin/manager_unix_test.go +++ b/pkg/plugin/manager_unix_test.go @@ -63,12 +63,17 @@ func modifyManifest(t *testing.T, worktree, version string) { } func TestManager_Install(t *testing.T) { - gs := setupGitRepository(t, "test_plugin", "testdata/test_plugin") + gs := setupGitRepository(t, "test_plugin", "testdata/test_plugin/test_plugin") t.Cleanup(gs.Close) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { zr := zip.NewWriter(w) - require.NoError(t, zr.AddFS(os.DirFS("testdata/test_plugin"))) + switch r.URL.Path { + case "/test_plugin.zip": + require.NoError(t, zr.AddFS(os.DirFS("testdata/test_plugin/test_plugin"))) + case "/test_nested.zip": + require.NoError(t, zr.AddFS(os.DirFS("testdata/test_plugin"))) + } require.NoError(t, zr.Close()) })) t.Cleanup(ts.Close) @@ -119,6 +124,13 @@ func TestManager_Install(t *testing.T) { wantFile: ".trivy/plugins/test_plugin/test.sh", wantLogs: fmt.Sprintf(wantLogs, ts.URL+"/test_plugin.zip", "0.2.0"), }, + { + name: "nested archive", + pluginName: ts.URL + "/test_nested.zip", + want: wantPlugin, + wantFile: ".trivy/plugins/test_plugin/test.sh", + wantLogs: fmt.Sprintf(wantLogs, ts.URL+"/test_nested.zip", "0.2.0"), + }, { name: "local path", pluginName: "testdata/test_plugin", diff --git a/pkg/plugin/testdata/test_plugin/plugin.yaml b/pkg/plugin/testdata/test_plugin/test_plugin/plugin.yaml similarity index 100% rename from pkg/plugin/testdata/test_plugin/plugin.yaml rename to pkg/plugin/testdata/test_plugin/test_plugin/plugin.yaml diff --git a/pkg/plugin/testdata/test_plugin/test.sh b/pkg/plugin/testdata/test_plugin/test_plugin/test.sh similarity index 100% rename from pkg/plugin/testdata/test_plugin/test.sh rename to pkg/plugin/testdata/test_plugin/test_plugin/test.sh From bb889373656b419c6167dedf0336214278facc67 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 7 Jun 2024 17:16:23 +0600 Subject: [PATCH 146/352] ci: add created release branch to `rulesets` to enable merge queue (#6880) --- .github/workflows/release-please.yaml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml index de3e0def9ddd..aa8bc4bdb990 100644 --- a/.github/workflows/release-please.yaml +++ b/.github/workflows/release-please.yaml @@ -50,6 +50,7 @@ jobs: run: | echo "version=$( echo "${{ github.event.head_commit.message }}" | sed 's/^release: v\([0-9]\+\.[0-9]\+\.[0-9]\+\).*$/\1/' )" >> $GITHUB_OUTPUT echo "pr_number=$( echo "${{ github.event.head_commit.message }}" | sed 's/.*(\#\([0-9]\+\)).*$/\1/' )" >> $GITHUB_OUTPUT + echo "release_branch=release/v$( echo "${{ github.event.head_commit.message }}" | sed 's/^release: v\([0-9]\+\.[0-9]\+\).*$/\1/' )" >> $GITHUB_OUTPUT - name: Tag release if: ${{ steps.extract_info.outputs.version }} @@ -71,15 +72,24 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} # Should not trigger the workflow again script: | - const version = '${{ steps.extract_info.outputs.version }}'; - const minorVersion = version.slice(0, version.lastIndexOf('.')); - const releaseBranch = `release/v${minorVersion}`; + const releaseBranch = '${{ steps.extract_info.outputs.release_branch }}'; await github.rest.git.createRef({ owner: context.repo.owner, repo: context.repo.repo, ref: `refs/heads/${releaseBranch}`, sha: context.sha }); + + + # Add release branch to rulesets to enable merge queue + - name: Add release branch to rulesets + if: ${{ endsWith(steps.extract_info.outputs.version, '.0') }} + env: + GITHUB_TOKEN: ${{ secrets.ORG_REPO_TOKEN }} + shell: bash + run: | + RULESET_ID=$(gh api /repos/${{ github.repository }}/rulesets --jq '.[] | select(.name=="release") | .id') + gh api /repos/${{ github.repository }}/rulesets/$RULESET_ID | jq '{conditions}' | jq '.conditions.ref_name.include += ["refs/heads/${{ steps.extract_info.outputs.release_branch }}"]' | gh api --method put --input - /repos/${{ github.repository }}/rulesets/$RULESET_ID # Since skip-github-release is specified, googleapis/release-please-action doesn't delete the label from PR. # This label prevents the subsequent PRs from being created. Therefore, we need to delete it ourselves. From 8491469f0b35bd9df706a433669f5b62239d4ef3 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Sat, 8 Jun 2024 08:31:22 +0700 Subject: [PATCH 147/352] feat(misconf): API Gateway V1 support for CloudFormation (#6874) --- .../aws/apigateway/apigateway.go | 7 +- .../aws/apigateway/apigateway_test.go | 98 +++++++++++++++- .../cloudformation/aws/apigateway/apiv1.go | 108 ++++++++++++++++++ .../aws/apigateway/{stage.go => apiv2.go} | 25 +++- 4 files changed, 229 insertions(+), 9 deletions(-) create mode 100644 pkg/iac/adapters/cloudformation/aws/apigateway/apiv1.go rename pkg/iac/adapters/cloudformation/aws/apigateway/{stage.go => apiv2.go} (68%) diff --git a/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway.go b/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway.go index bbc79623a6c4..096ad174a002 100644 --- a/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway.go +++ b/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway.go @@ -11,11 +11,12 @@ import ( func Adapt(cfFile parser.FileContext) apigateway.APIGateway { return apigateway.APIGateway{ V1: v1.APIGateway{ - APIs: nil, - DomainNames: nil, + APIs: adaptAPIsV1(cfFile), + DomainNames: adaptDomainNamesV1(cfFile), }, V2: v2.APIGateway{ - APIs: getApis(cfFile), + APIs: adaptAPIsV2(cfFile), + DomainNames: adaptDomainNamesV2(cfFile), }, } } diff --git a/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway_test.go b/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway_test.go index 8f9e55ef8abd..4386a5baa51c 100644 --- a/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway_test.go +++ b/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway_test.go @@ -5,6 +5,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway" + v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" "github.com/aquasecurity/trivy/pkg/iac/types" ) @@ -19,24 +20,105 @@ func TestAdapt(t *testing.T) { name: "complete", source: `AWSTemplateFormatVersion: 2010-09-09 Resources: - MyApi: + MyRestApi: + Type: 'AWS::ApiGateway::RestApi' + Properties: + Description: A test API + Name: MyRestAPI + ApiResource: + Type: AWS::ApiGateway::Resource + Properties: + RestApiId: !Ref MyRestApi + MethodPOST: + Type: AWS::ApiGateway::Method + Properties: + RestApiId: !Ref MyRestApi + ResourceId: !Ref ApiResource + HttpMethod: POST + AuthorizationType: COGNITO_USER_POOLS + ApiKeyRequired: true + Stage: + Type: AWS::ApiGateway::Stage + Properties: + StageName: Prod + RestApiId: !Ref MyRestApi + TracingEnabled: true + AccessLogSetting: + DestinationArn: test-arn + MethodSettings: + - CacheDataEncrypted: true + CachingEnabled: true + HttpMethod: POST + MyDomainName: + Type: AWS::ApiGateway::DomainName + Properties: + DomainName: mydomainame.us-east-1.com + SecurityPolicy: "TLS_1_2" + + MyApi2: Type: 'AWS::ApiGatewayV2::Api' Properties: - Name: MyApi + Name: MyApi2 ProtocolType: WEBSOCKET - MyStage: + MyStage2: Type: 'AWS::ApiGatewayV2::Stage' Properties: StageName: Prod - ApiId: !Ref MyApi + ApiId: !Ref MyApi2 AccessLogSettings: DestinationArn: some-arn + MyDomainName2: + Type: 'AWS::ApiGatewayV2::DomainName' + Properties: + DomainName: mydomainame.us-east-1.com + DomainNameConfigurations: + - SecurityPolicy: "TLS_1_2" `, expected: apigateway.APIGateway{ + V1: v1.APIGateway{ + APIs: []v1.API{ + { + Name: types.StringTest("MyRestAPI"), + Stages: []v1.Stage{ + { + Name: types.StringTest("Prod"), + XRayTracingEnabled: types.BoolTest(true), + AccessLogging: v1.AccessLogging{ + CloudwatchLogGroupARN: types.StringTest("test-arn"), + }, + RESTMethodSettings: []v1.RESTMethodSettings{ + { + Method: types.StringTest("POST"), + CacheDataEncrypted: types.BoolTest(true), + CacheEnabled: types.BoolTest(true), + }, + }, + }, + }, + Resources: []v1.Resource{ + { + Methods: []v1.Method{ + { + HTTPMethod: types.StringTest("POST"), + AuthorizationType: types.StringTest("COGNITO_USER_POOLS"), + APIKeyRequired: types.BoolTest(true), + }, + }, + }, + }, + }, + }, + DomainNames: []v1.DomainName{ + { + Name: types.StringTest("mydomainame.us-east-1.com"), + SecurityPolicy: types.StringTest("TLS_1_2"), + }, + }, + }, V2: v2.APIGateway{ APIs: []v2.API{ { - Name: types.StringTest("MyApi"), + Name: types.StringTest("MyApi2"), ProtocolType: types.StringTest("WEBSOCKET"), Stages: []v2.Stage{ { @@ -48,6 +130,12 @@ Resources: }, }, }, + DomainNames: []v2.DomainName{ + { + Name: types.StringTest("mydomainame.us-east-1.com"), + SecurityPolicy: types.StringTest("TLS_1_2"), + }, + }, }, }, }, diff --git a/pkg/iac/adapters/cloudformation/aws/apigateway/apiv1.go b/pkg/iac/adapters/cloudformation/aws/apigateway/apiv1.go new file mode 100644 index 000000000000..2a2c46f44f5e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/apigateway/apiv1.go @@ -0,0 +1,108 @@ +package apigateway + +import ( + v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +func adaptAPIsV1(fctx parser.FileContext) []v1.API { + var apis []v1.API + + stages := make(map[string]*parser.Resource) + for _, stageResource := range fctx.GetResourcesByType("AWS::ApiGateway::Stage") { + restApiID := stageResource.GetStringProperty("RestApiId") + if restApiID.IsEmpty() { + continue + } + + stages[restApiID.Value()] = stageResource + } + + resources := make(map[string]*parser.Resource) + for _, resource := range fctx.GetResourcesByType("AWS::ApiGateway::Resource") { + restApiID := resource.GetStringProperty("RestApiId") + if restApiID.IsEmpty() { + continue + } + + resources[restApiID.Value()] = resource + } + + for _, apiResource := range fctx.GetResourcesByType("AWS::ApiGateway::RestApi") { + + api := v1.API{ + Metadata: apiResource.Metadata(), + Name: apiResource.GetStringProperty("Name"), + } + + if stageResource, exists := stages[apiResource.ID()]; exists { + stage := v1.Stage{ + Metadata: stageResource.Metadata(), + Name: stageResource.GetStringProperty("StageName"), + XRayTracingEnabled: stageResource.GetBoolProperty("TracingEnabled"), + } + + if logSetting := stageResource.GetProperty("AccessLogSetting"); logSetting.IsNotNil() { + stage.AccessLogging = v1.AccessLogging{ + Metadata: logSetting.Metadata(), + CloudwatchLogGroupARN: logSetting.GetStringProperty("DestinationArn"), + } + } + + if methodSettings := stageResource.GetProperty("MethodSettings"); methodSettings.IsList() { + for _, methodSetting := range methodSettings.AsList() { + stage.RESTMethodSettings = append(stage.RESTMethodSettings, v1.RESTMethodSettings{ + Metadata: methodSetting.Metadata(), + Method: methodSetting.GetStringProperty("HttpMethod"), + CacheDataEncrypted: methodSetting.GetBoolProperty("CacheDataEncrypted"), + CacheEnabled: methodSetting.GetBoolProperty("CachingEnabled"), + }) + } + } + + api.Stages = append(api.Stages, stage) + } + + if resource, exists := resources[apiResource.ID()]; exists { + res := v1.Resource{ + Metadata: resource.Metadata(), + } + + for _, methodResource := range fctx.GetResourcesByType("AWS::ApiGateway::Method") { + resourceID := methodResource.GetStringProperty("ResourceId") + // TODO: handle RootResourceId + if resourceID.Value() != resource.ID() { + continue + } + + res.Methods = append(res.Methods, v1.Method{ + Metadata: methodResource.Metadata(), + HTTPMethod: methodResource.GetStringProperty("HttpMethod"), + AuthorizationType: methodResource.GetStringProperty("AuthorizationType"), + APIKeyRequired: methodResource.GetBoolProperty("ApiKeyRequired"), + }) + + } + + api.Resources = append(api.Resources, res) + } + + apis = append(apis, api) + } + + return apis +} + +func adaptDomainNamesV1(fctx parser.FileContext) []v1.DomainName { + var domainNames []v1.DomainName + + for _, domainNameResource := range fctx.GetResourcesByType("AWS::ApiGateway::DomainName") { + domainNames = append(domainNames, v1.DomainName{ + Metadata: domainNameResource.Metadata(), + Name: domainNameResource.GetStringProperty("DomainName"), + SecurityPolicy: domainNameResource.GetStringProperty("SecurityPolicy"), + }) + } + + return domainNames +} diff --git a/pkg/iac/adapters/cloudformation/aws/apigateway/stage.go b/pkg/iac/adapters/cloudformation/aws/apigateway/apiv2.go similarity index 68% rename from pkg/iac/adapters/cloudformation/aws/apigateway/stage.go rename to pkg/iac/adapters/cloudformation/aws/apigateway/apiv2.go index 8e9497a91ec3..d3a34a98d91e 100644 --- a/pkg/iac/adapters/cloudformation/aws/apigateway/stage.go +++ b/pkg/iac/adapters/cloudformation/aws/apigateway/apiv2.go @@ -6,7 +6,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getApis(cfFile parser.FileContext) (apis []v2.API) { +func adaptAPIsV2(cfFile parser.FileContext) (apis []v2.API) { apiResources := cfFile.GetResourcesByType("AWS::ApiGatewayV2::Api") for _, apiRes := range apiResources { @@ -66,3 +66,26 @@ func getAccessLogging(r *parser.Resource) v2.AccessLogging { CloudwatchLogGroupARN: destinationProp.AsStringValue(), } } + +func adaptDomainNamesV2(fctx parser.FileContext) []v2.DomainName { + var domainNames []v2.DomainName + + for _, domainNameResource := range fctx.GetResourcesByType("AWS::ApiGateway::DomainName") { + + domainName := v2.DomainName{ + Metadata: domainNameResource.Metadata(), + Name: domainNameResource.GetStringProperty("DomainName"), + SecurityPolicy: domainNameResource.GetStringProperty("SecurityPolicy"), + } + + if domainNameCfgs := domainNameResource.GetProperty("DomainNameConfigurations"); domainNameCfgs.IsList() { + for _, domainNameCfg := range domainNameCfgs.AsList() { + domainName.SecurityPolicy = domainNameCfg.GetStringProperty("SecurityPolicy") + } + } + + domainNames = append(domainNames, domainName) + } + + return domainNames +} From cf5aa336e660e4c98481ebf8d15dd4e54c38581e Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:30:27 +0600 Subject: [PATCH 148/352] fix(nodejs): fix infinite loop when package link from `package-lock.json` file is broken (#6858) --- pkg/dependency/parser/nodejs/npm/parse.go | 16 ++++++++++--- .../parser/nodejs/npm/parse_test.go | 6 +++++ .../testdata/package-lock_v3_broken_link.json | 24 +++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_broken_link.json diff --git a/pkg/dependency/parser/nodejs/npm/parse.go b/pkg/dependency/parser/nodejs/npm/parse.go index ec5d654e7469..6e99cdd1bfcb 100644 --- a/pkg/dependency/parser/nodejs/npm/parse.go +++ b/pkg/dependency/parser/nodejs/npm/parse.go @@ -194,8 +194,16 @@ func (p *Parser) parseV2(packages map[string]Package) ([]ftypes.Package, []ftype // node_modules/func1 -> link to target // see `package-lock_v3_with_workspace.json` to better understanding func (p *Parser) resolveLinks(packages map[string]Package) { - links := lo.PickBy(packages, func(_ string, pkg Package) bool { - return pkg.Link + links := lo.PickBy(packages, func(pkgPath string, pkg Package) bool { + if !pkg.Link { + return false + } + if pkg.Resolved == "" { + p.logger.Warn("`package-lock.json` contains broken link with empty `resolved` field. This package will be skipped to avoid receiving an empty package", log.String("pkg", pkgPath)) + delete(packages, pkgPath) + return false + } + return true }) // Early return if len(links) == 0 { @@ -208,7 +216,9 @@ func (p *Parser) resolveLinks(packages map[string]Package) { } workspaces := rootPkg.Workspaces - for pkgPath, pkg := range packages { + // Changing the map during the map iteration causes unexpected behavior, + // so we need to iterate over the cloned `packages` map, but change the original `packages` map. + for pkgPath, pkg := range maps.Clone(packages) { for linkPath, link := range links { if !strings.HasPrefix(pkgPath, link.Resolved) { continue diff --git a/pkg/dependency/parser/nodejs/npm/parse_test.go b/pkg/dependency/parser/nodejs/npm/parse_test.go index 68f5d1e8491f..7b0b5042c831 100644 --- a/pkg/dependency/parser/nodejs/npm/parse_test.go +++ b/pkg/dependency/parser/nodejs/npm/parse_test.go @@ -53,6 +53,12 @@ func TestParse(t *testing.T) { want: npmV3WithoutRootDepsField, wantDeps: npmV3WithoutRootDepsFieldDeps, }, + { + name: "lock version v3 with broken link", + file: "testdata/package-lock_v3_broken_link.json", + want: nil, + wantDeps: nil, + }, } for _, tt := range tests { diff --git a/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_broken_link.json b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_broken_link.json new file mode 100644 index 000000000000..d01c0fe3e996 --- /dev/null +++ b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_broken_link.json @@ -0,0 +1,24 @@ +{ + "name": "node_v3_without_direct_deps", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node_v3_without_direct_deps", + "version": "1.0.0", + "license": "ISC" + }, + "functions/func1": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "debug": "^2.6.9" + } + }, + "node_modules/func1": { + "resolved": "", + "link": true + } + } +} \ No newline at end of file From 089b953462260f01c40bdf588b2568ae0ef658bc Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Mon, 10 Jun 2024 10:37:39 +0400 Subject: [PATCH 149/352] fix(debian): take installed files from the origin layer (#6849) Signed-off-by: knqyf263 Co-authored-by: DmitriyLewen --- pkg/fanal/applier/docker.go | 20 ++-- pkg/fanal/applier/docker_test.go | 182 +++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+), 9 deletions(-) diff --git a/pkg/fanal/applier/docker.go b/pkg/fanal/applier/docker.go index 706da683066d..d4f88bc851a7 100644 --- a/pkg/fanal/applier/docker.go +++ b/pkg/fanal/applier/docker.go @@ -30,24 +30,24 @@ type History struct { CreatedBy string `json:"created_by"` } -func containsPackage(e ftypes.Package, s []ftypes.Package) bool { +func findPackage(e ftypes.Package, s []ftypes.Package) *ftypes.Package { for _, a := range s { if a.Name == e.Name && a.Version == e.Version && a.Release == e.Release { - return true + return &a } } - return false + return nil } -func lookupOriginLayerForPkg(pkg ftypes.Package, layers []ftypes.BlobInfo) (string, string, *ftypes.BuildInfo) { +func lookupOriginLayerForPkg(pkg ftypes.Package, layers []ftypes.BlobInfo) (string, string, []string, *ftypes.BuildInfo) { for i, layer := range layers { for _, info := range layer.PackageInfos { - if containsPackage(pkg, info.Packages) { - return layer.Digest, layer.DiffID, lookupBuildInfo(i, layers) + if p := findPackage(pkg, info.Packages); p != nil { + return layer.Digest, layer.DiffID, p.InstalledFiles, lookupBuildInfo(i, layers) } } } - return "", "", nil + return "", "", nil, nil } // lookupBuildInfo looks up Red Hat content sets from all layers @@ -81,7 +81,7 @@ func lookupOriginLayerForLib(filePath string, lib ftypes.Package, layers []ftype if filePath != layerApp.FilePath { continue } - if containsPackage(lib, layerApp.Packages) { + if findPackage(lib, layerApp.Packages) != nil { return layer.Digest, layer.DiffID } } @@ -210,12 +210,14 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { for i, pkg := range mergedLayer.Packages { // Skip lookup for SBOM if lo.IsEmpty(pkg.Layer) { - originLayerDigest, originLayerDiffID, buildInfo := lookupOriginLayerForPkg(pkg, layers) + originLayerDigest, originLayerDiffID, installedFiles, buildInfo := lookupOriginLayerForPkg(pkg, layers) mergedLayer.Packages[i].Layer = ftypes.Layer{ Digest: originLayerDigest, DiffID: originLayerDiffID, } mergedLayer.Packages[i].BuildInfo = buildInfo + // Debian/Ubuntu has the installed files only in the first layer where the package is installed. + mergedLayer.Packages[i].InstalledFiles = installedFiles } if mergedLayer.OS.Family != "" { diff --git a/pkg/fanal/applier/docker_test.go b/pkg/fanal/applier/docker_test.go index f5bccfa86bf6..93facbd39186 100644 --- a/pkg/fanal/applier/docker_test.go +++ b/pkg/fanal/applier/docker_test.go @@ -843,6 +843,188 @@ func TestApplyLayers(t *testing.T) { }, }, }, + { + name: "happy path with filling system files for debian packages", + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 2, + DiffID: "sha256:cdd7c73923174e45ea648d66996665c288e1b17a0f45efdbeca860f6dafdf731", + OS: types.OS{ + Family: "ubuntu", + Name: "24.04", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: types.Packages{ + { + ID: "apt@2.4.9", + Name: "apt", + Version: "2.4.9", + Arch: "amd64", + SrcName: "apt", + SrcVersion: "2.4.9", + InstalledFiles: []string{ + "/etc/apt/apt.conf.d/01-vendor-ubuntu", + "/etc/apt/apt.conf.d/01autoremove", + "/etc/apt/auth.conf.d", + "/etc/apt/keyrings", + }, + }, + }, + }, + }, + }, + // Install `curl` + { + SchemaVersion: 2, + DiffID: "sha256:faf30fa9c41c10f93b3b134d7b2c16e07753320393e020c481f0c97d10db067d", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: types.Packages{ + { + ID: "apt@2.4.9", + Name: "apt", + Version: "2.4.9", + Arch: "amd64", + SrcName: "apt", + SrcVersion: "2.4.9", + }, + { + ID: "curl@8.5.0-2ubuntu10.1", + Name: "curl", + Version: "8.5.0", + Release: "2ubuntu10.1", + Arch: "arm64", + SrcName: "curl", + SrcVersion: "8.5.0", + SrcRelease: "2ubuntu10.1", + InstalledFiles: []string{ + "/usr/bin/curl", + "/usr/share/doc/curl/README.Debian", + "/usr/share/doc/curl/changelog.Debian.gz", + "/usr/share/doc/curl/copyright", + "/usr/share/man/man1/curl.1.gz", + "/usr/share/zsh/vendor-completions/_curl", + }, + }, + }, + }, + }, + }, + // Upgrade `apt` + { + SchemaVersion: 2, + DiffID: "sha256:440e26edc0eb9b4fee6e1d40d8af9eb59500d38e25edfc5d5302c55f59394c1e", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: types.Packages{ + { + ID: "apt@2.4.12", + Name: "apt", + Version: "2.4.12", + Arch: "amd64", + SrcName: "apt", + SrcVersion: "2.4.12", + InstalledFiles: []string{ + "/etc/apt/apt.conf.d/01-vendor-ubuntu", + "/etc/apt/apt.conf.d/01autoremove", + "/etc/apt/auth.conf.d", + "/etc/apt/keyrings", + "/usr/share/man/it/man5/sources.list.5.gz", + }, + }, + { + ID: "curl@8.5.0-2ubuntu10.1", + Name: "curl", + Version: "8.5.0", + Release: "2ubuntu10.1", + Arch: "arm64", + SrcName: "curl", + SrcVersion: "8.5.0", + SrcRelease: "2ubuntu10.1", + }, + }, + }, + }, + }, + // Remove curl + { + SchemaVersion: 2, + DiffID: "sha256:cb04e1d437de723d8d04bc7df89dc42271530c5f8ea1724c6072e3f0e7d6d38a", + WhiteoutFiles: []string{ + "usr/bin/curl", + "usr/share/doc/curl", + "usr/share/zsh", + "var/lib/dpkg/info/curl.list", + "var/lib/dpkg/info/curl.md5sums", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: types.Packages{ + { + ID: "apt@2.4.12", + Name: "apt", + Version: "2.4.12", + Arch: "amd64", + SrcName: "apt", + SrcVersion: "2.4.12", + }, + }, + }, + }, + }, + }, + want: types.ArtifactDetail{ + OS: types.OS{ + Family: "ubuntu", + Name: "24.04", + }, + Packages: types.Packages{ + { + ID: "apt@2.4.12", + Name: "apt", + Version: "2.4.12", + Arch: "amd64", + SrcName: "apt", + SrcVersion: "2.4.12", + + Identifier: types.PkgIdentifier{ + UID: "80bc98a8f3159db9", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "ubuntu", + Name: "apt", + Version: "2.4.12", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "amd64", + }, + { + Key: "distro", + Value: "ubuntu-24.04", + }, + }, + }, + }, + Layer: types.Layer{ + DiffID: "sha256:440e26edc0eb9b4fee6e1d40d8af9eb59500d38e25edfc5d5302c55f59394c1e", + }, + InstalledFiles: []string{ + "/etc/apt/apt.conf.d/01-vendor-ubuntu", + "/etc/apt/apt.conf.d/01autoremove", + "/etc/apt/auth.conf.d", + "/etc/apt/keyrings", + "/usr/share/man/it/man5/sources.list.5.gz", + }, + }, + }, + }, + }, { name: "happy path, opaque dirs with the trailing slash", inputLayers: []types.BlobInfo{ From 9b31697274c8743d6e5a8f7a1a05daf60cd15910 Mon Sep 17 00:00:00 2001 From: Maksim Nabokikh Date: Mon, 10 Jun 2024 11:05:03 +0400 Subject: [PATCH 150/352] feat(image): Set User-Agent header for Trivy container registry requests (#6868) Signed-off-by: m.nabokikh --- goreleaser-canary.yml | 2 +- goreleaser.yml | 8 +-- magefiles/magefile.go | 2 +- pkg/commands/app.go | 3 +- pkg/dependency/parser/golang/binary/parse.go | 2 +- .../parser/golang/binary/parse_test.go | 4 +- pkg/flag/options.go | 4 +- pkg/remote/remote.go | 20 +++--- pkg/remote/remote_test.go | 64 +++++++++++++++++++ pkg/version/app/version.go | 9 +++ pkg/version/version.go | 11 +--- 11 files changed, 100 insertions(+), 29 deletions(-) create mode 100644 pkg/version/app/version.go diff --git a/goreleaser-canary.yml b/goreleaser-canary.yml index da395ab53e9d..0c8ef21fcf83 100644 --- a/goreleaser-canary.yml +++ b/goreleaser-canary.yml @@ -6,7 +6,7 @@ builds: ldflags: - -s -w - "-extldflags '-static'" - - -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}} + - -X github.com/aquasecurity/trivy/pkg/version/app.ver={{.Version}} env: - CGO_ENABLED=0 goos: diff --git a/goreleaser.yml b/goreleaser.yml index 0a546162bed7..66cca9735b0c 100644 --- a/goreleaser.yml +++ b/goreleaser.yml @@ -6,7 +6,7 @@ builds: ldflags: - -s -w - "-extldflags '-static'" - - -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}} + - -X github.com/aquasecurity/trivy/pkg/version/app.ver={{.Version}} env: - CGO_ENABLED=0 goos: @@ -26,7 +26,7 @@ builds: ldflags: - -s -w - "-extldflags '-static'" - - -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}} + - -X github.com/aquasecurity/trivy/pkg/version/app.ver={{.Version}} env: - CGO_ENABLED=0 goos: @@ -41,7 +41,7 @@ builds: ldflags: - -s -w - "-extldflags '-static'" - - -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}} + - -X github.com/aquasecurity/trivy/pkg/version/app.ver={{.Version}} env: - CGO_ENABLED=0 goos: @@ -57,7 +57,7 @@ builds: ldflags: - -s -w - "-extldflags '-static'" - - -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}} + - -X github.com/aquasecurity/trivy/pkg/version/app.ver={{.Version}} env: - CGO_ENABLED=0 goos: diff --git a/magefiles/magefile.go b/magefiles/magefile.go index 3e1efd4481f8..ca90864c2e54 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -48,7 +48,7 @@ func buildLdflags() (string, error) { if err != nil { return "", err } - return fmt.Sprintf("-s -w -X=github.com/aquasecurity/trivy/pkg/version.ver=%s", ver), nil + return fmt.Sprintf("-s -w -X=github.com/aquasecurity/trivy/pkg/version/app.ver=%s", ver), nil } type Tool mg.Namespace diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 4a8331a71053..7746e1b70784 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -28,6 +28,7 @@ import ( "github.com/aquasecurity/trivy/pkg/plugin" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/version" + "github.com/aquasecurity/trivy/pkg/version/app" xstrings "github.com/aquasecurity/trivy/pkg/x/strings" ) @@ -178,7 +179,7 @@ func NewRootCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { Args: cobra.NoArgs, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { // Set the Trivy version here so that we can override version printer. - cmd.Version = version.AppVersion() + cmd.Version = app.Version() // viper.BindPFlag cannot be called in init(). // cf. https://github.com/spf13/cobra/issues/875 diff --git a/pkg/dependency/parser/golang/binary/parse.go b/pkg/dependency/parser/golang/binary/parse.go index 2ef2b4cf1c47..6959e0ede5fa 100644 --- a/pkg/dependency/parser/golang/binary/parse.go +++ b/pkg/dependency/parser/golang/binary/parse.go @@ -217,7 +217,7 @@ func isValidSemVer(ver string) bool { // versionPrefix returns version prefix from `-ldflags` flag key // e.g. -// - `github.com/aquasecurity/trivy/pkg/version.version` => `version` +// - `github.com/aquasecurity/trivy/pkg/version/app.ver` => `version` // - `github.com/google/go-containerregistry/cmd/crane/common.ver` => `common` func versionPrefix(s string) string { // Trim module part. diff --git a/pkg/dependency/parser/golang/binary/parse_test.go b/pkg/dependency/parser/golang/binary/parse_test.go index fd500df4aaf5..2fbb6acff7b2 100644 --- a/pkg/dependency/parser/golang/binary/parse_test.go +++ b/pkg/dependency/parser/golang/binary/parse_test.go @@ -168,7 +168,7 @@ func TestParser_ParseLDFlags(t *testing.T) { "-s", "-w", "-X=foo=bar", - "-X='github.com/aquasecurity/trivy/pkg/version.version=v0.50.1'", + "-X='github.com/aquasecurity/trivy/pkg/version/app.ver=v0.50.1'", }, }, want: "v0.50.1", @@ -194,7 +194,7 @@ func TestParser_ParseLDFlags(t *testing.T) { "-s", "-w", "-X=foo=bar", - "-X='github.com/aquasecurity/trivy/pkg/version.ver=v0.50.1'", + "-X='github.com/aquasecurity/trivy/pkg/version/app.ver=v0.50.1'", }, }, want: "v0.50.1", diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 13ed256b1c35..69b3585226cc 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -23,7 +23,7 @@ import ( "github.com/aquasecurity/trivy/pkg/plugin" "github.com/aquasecurity/trivy/pkg/result" "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/version" + "github.com/aquasecurity/trivy/pkg/version/app" ) type FlagType interface { @@ -602,7 +602,7 @@ func (f *Flags) Bind(cmd *cobra.Command) error { func (f *Flags) ToOptions(args []string) (Options, error) { var err error opts := Options{ - AppVersion: version.AppVersion(), + AppVersion: app.Version(), } if f.GlobalFlagGroup != nil { diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index b989df18a35b..9e49e2e4f24f 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -3,6 +3,7 @@ package remote import ( "context" "crypto/tls" + "fmt" "net" "net/http" "time" @@ -11,6 +12,7 @@ import ( "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/remote/transport" v1types "github.com/google/go-containerregistry/pkg/v1/types" "github.com/hashicorp/go-multierror" "github.com/samber/lo" @@ -19,6 +21,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/image/registry" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/version/app" ) type Descriptor = remote.Descriptor @@ -26,7 +29,7 @@ type Descriptor = remote.Descriptor // Get is a wrapper of google/go-containerregistry/pkg/v1/remote.Get // so that it can try multiple authentication methods. func Get(ctx context.Context, ref name.Reference, option types.RegistryOptions) (*Descriptor, error) { - transport, err := httpTransport(option) + tr, err := httpTransport(option) if err != nil { return nil, xerrors.Errorf("failed to create http transport: %w", err) } @@ -35,7 +38,7 @@ func Get(ctx context.Context, ref name.Reference, option types.RegistryOptions) // Try each authentication method until it succeeds for _, authOpt := range authOptions(ctx, ref, option) { remoteOpts := []remote.Option{ - remote.WithTransport(transport), + remote.WithTransport(tr), authOpt, } @@ -71,7 +74,7 @@ func Get(ctx context.Context, ref name.Reference, option types.RegistryOptions) // Image is a wrapper of google/go-containerregistry/pkg/v1/remote.Image // so that it can try multiple authentication methods. func Image(ctx context.Context, ref name.Reference, option types.RegistryOptions) (v1.Image, error) { - transport, err := httpTransport(option) + tr, err := httpTransport(option) if err != nil { return nil, xerrors.Errorf("failed to create http transport: %w", err) } @@ -80,7 +83,7 @@ func Image(ctx context.Context, ref name.Reference, option types.RegistryOptions // Try each authentication method until it succeeds for _, authOpt := range authOptions(ctx, ref, option) { remoteOpts := []remote.Option{ - remote.WithTransport(transport), + remote.WithTransport(tr), authOpt, } index, err := remote.Image(ref, remoteOpts...) @@ -98,7 +101,7 @@ func Image(ctx context.Context, ref name.Reference, option types.RegistryOptions // Referrers is a wrapper of google/go-containerregistry/pkg/v1/remote.Referrers // so that it can try multiple authentication methods. func Referrers(ctx context.Context, d name.Digest, option types.RegistryOptions) (v1.ImageIndex, error) { - transport, err := httpTransport(option) + tr, err := httpTransport(option) if err != nil { return nil, xerrors.Errorf("failed to create http transport: %w", err) } @@ -107,7 +110,7 @@ func Referrers(ctx context.Context, d name.Digest, option types.RegistryOptions) // Try each authentication method until it succeeds for _, authOpt := range authOptions(ctx, d, option) { remoteOpts := []remote.Option{ - remote.WithTransport(transport), + remote.WithTransport(tr), authOpt, } index, err := remote.Referrers(d, remoteOpts...) @@ -122,7 +125,7 @@ func Referrers(ctx context.Context, d name.Digest, option types.RegistryOptions) return nil, errs } -func httpTransport(option types.RegistryOptions) (*http.Transport, error) { +func httpTransport(option types.RegistryOptions) (http.RoundTripper, error) { d := &net.Dialer{ Timeout: 10 * time.Minute, } @@ -138,7 +141,8 @@ func httpTransport(option types.RegistryOptions) (*http.Transport, error) { tr.TLSClientConfig.Certificates = []tls.Certificate{cert} } - return tr, nil + tripper := transport.NewUserAgent(tr, fmt.Sprintf("trivy/%s", app.Version())) + return tripper, nil } func authOptions(ctx context.Context, ref name.Reference, option types.RegistryOptions) []remote.Option { diff --git a/pkg/remote/remote_test.go b/pkg/remote/remote_test.go index 468bc069177b..33c5d2a7c68e 100644 --- a/pkg/remote/remote_test.go +++ b/pkg/remote/remote_test.go @@ -4,9 +4,11 @@ import ( "context" "encoding/base64" "fmt" + "net/http" "net/http/httptest" "os" "path/filepath" + "sync" "testing" "github.com/google/go-containerregistry/pkg/name" @@ -17,6 +19,7 @@ import ( "github.com/aquasecurity/testdocker/auth" "github.com/aquasecurity/testdocker/registry" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/version/app" ) func setupPrivateRegistry() *httptest.Server { @@ -32,6 +35,7 @@ func setupPrivateRegistry() *httptest.Server { }, }) + tr.Config.Handler = newUserAgentsTrackingHandler(tr.Config.Handler) return tr } @@ -206,3 +210,63 @@ func TestGet(t *testing.T) { }) } } + +type userAgentsTrackingHandler struct { + hr http.Handler + + mu sync.Mutex + agents map[string]struct{} +} + +func newUserAgentsTrackingHandler(hr http.Handler) *userAgentsTrackingHandler { + return &userAgentsTrackingHandler{hr: hr, agents: make(map[string]struct{})} +} + +func (uh *userAgentsTrackingHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + for _, agent := range r.Header["User-Agent"] { + // Skip test framework user agent + if agent != "Go-http-client/1.1" { + uh.agents[agent] = struct{}{} + } + } + uh.hr.ServeHTTP(rw, r) +} + +func setupAgentTrackingRegistry() (*httptest.Server, *userAgentsTrackingHandler) { + imagePaths := map[string]string{ + "v2/library/alpine:3.10": "../fanal/test/testdata/alpine-310.tar.gz", + } + tr := registry.NewDockerRegistry(registry.Option{ + Images: imagePaths, + }) + + tracker := newUserAgentsTrackingHandler(tr.Config.Handler) + tr.Config.Handler = tracker + + return tr, tracker +} + +func TestUserAgents(t *testing.T) { + tr, tracker := setupAgentTrackingRegistry() + defer tr.Close() + + serverAddr := tr.Listener.Addr().String() + + n, err := name.ParseReference(fmt.Sprintf("%s/library/alpine:3.10", serverAddr)) + require.NoError(t, err) + + _, err = Get(context.Background(), n, types.RegistryOptions{ + Credentials: []types.Credential{ + { + Username: "test", + Password: "testpass", + }, + }, + Insecure: true, + }) + require.NoError(t, err) + + require.Len(t, tracker.agents, 1) + _, ok := tracker.agents[fmt.Sprintf("trivy/%s go-containerregistry", app.Version())] + require.True(t, ok, `user-agent header equals to "trivy/dev go-containerregistry"`) +} diff --git a/pkg/version/app/version.go b/pkg/version/app/version.go new file mode 100644 index 000000000000..8a7013078c9a --- /dev/null +++ b/pkg/version/app/version.go @@ -0,0 +1,9 @@ +package app + +var ( + ver = "dev" +) + +func Version() string { + return ver +} diff --git a/pkg/version/version.go b/pkg/version/version.go index c6b18be1eaef..4490364db9aa 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -8,16 +8,9 @@ import ( javadb "github.com/aquasecurity/trivy-java-db/pkg/db" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/policy" + "github.com/aquasecurity/trivy/pkg/version/app" ) -var ( - ver = "dev" -) - -func AppVersion() string { - return ver -} - type VersionInfo struct { Version string `json:",omitempty"` VulnerabilityDB *metadata.Metadata `json:",omitempty"` @@ -99,7 +92,7 @@ func NewVersionInfo(cacheDir string) VersionInfo { } return VersionInfo{ - Version: ver, + Version: app.Version(), VulnerabilityDB: dbMeta, JavaDB: javadbMeta, CheckBundle: pbMeta, From 1bdc135fe784e22a53632483c642374871fca221 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:39:02 +0600 Subject: [PATCH 151/352] ci: bump `github.com/goreleaser/goreleaser` to `v2.0.0` (#6887) --- .github/workflows/reusable-release.yaml | 4 ++-- .github/workflows/test.yaml | 4 ++-- goreleaser-canary.yml | 2 ++ goreleaser.yml | 2 ++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/reusable-release.yaml b/.github/workflows/reusable-release.yaml index 2305a83b2159..ecae0832b93a 100644 --- a/.github/workflows/reusable-release.yaml +++ b/.github/workflows/reusable-release.yaml @@ -98,9 +98,9 @@ jobs: mkdir tmp - name: GoReleaser - uses: goreleaser/goreleaser-action@v5 + uses: goreleaser/goreleaser-action@v6 with: - version: v1.20.0 + version: v2.0.0 args: release -f=${{ inputs.goreleaser_config}} ${{ inputs.goreleaser_options}} env: GITHUB_TOKEN: ${{ secrets.ORG_REPO_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5688c7b3f031..8ff082624f97 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -213,7 +213,7 @@ jobs: fi - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 + uses: goreleaser/goreleaser-action@v6 with: - version: v1.20.0 + version: v2.0.0 args: build --snapshot --clean --timeout 90m ${{ steps.goreleaser_id.outputs.id }} diff --git a/goreleaser-canary.yml b/goreleaser-canary.yml index 0c8ef21fcf83..b49abde49d34 100644 --- a/goreleaser-canary.yml +++ b/goreleaser-canary.yml @@ -1,3 +1,5 @@ +version: 2 + project_name: trivy_canary_build builds: - diff --git a/goreleaser.yml b/goreleaser.yml index 66cca9735b0c..b427899773b0 100644 --- a/goreleaser.yml +++ b/goreleaser.yml @@ -1,3 +1,5 @@ +version: 2 + project_name: trivy builds: - id: build-linux From 6e7f62d2debbb4acbeb7ad58ecd8ac7379f19e40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 21:50:38 +0400 Subject: [PATCH 152/352] chore(deps): bump the aws group with 8 updates (#6898) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 34 ++++++++++++++--------------- go.sum | 68 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index 45539f6d21e1..e041336347b5 100644 --- a/go.mod +++ b/go.mod @@ -31,14 +31,14 @@ require ( github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7 - github.com/aws/aws-sdk-go-v2 v1.27.0 - github.com/aws/aws-sdk-go-v2/config v1.27.16 - github.com/aws/aws-sdk-go-v2/credentials v1.17.16 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.21 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.162.0 - github.com/aws/aws-sdk-go-v2/service/ecr v1.28.3 - github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3 - github.com/aws/aws-sdk-go-v2/service/sts v1.28.10 + github.com/aws/aws-sdk-go-v2 v1.27.2 + github.com/aws/aws-sdk-go-v2/config v1.27.18 + github.com/aws/aws-sdk-go-v2/credentials v1.17.18 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.24 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.1 + github.com/aws/aws-sdk-go-v2/service/ecr v1.28.5 + github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 github.com/aws/smithy-go v1.20.2 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 @@ -172,11 +172,11 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.53.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.9 // indirect github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.26.7 // indirect github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6 // indirect github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6 // indirect @@ -198,10 +198,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/emr v1.36.0 // indirect github.com/aws/aws-sdk-go-v2/service/iam v1.28.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.11 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.9 // indirect github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5 // indirect github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.32.1 // indirect @@ -213,8 +213,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/service/sns v1.26.6 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.9 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect diff --git a/go.sum b/go.sum index ac4b13249c77..5c4cc4d02054 100644 --- a/go.sum +++ b/go.sum @@ -796,26 +796,26 @@ github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.53.0 h1:MMo1x1ggPPxDfHMXJnQudTbGXYlD4UigUAud1DJxPVo= github.com/aws/aws-sdk-go v1.53.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/aws/aws-sdk-go-v2 v1.27.0 h1:7bZWKoXhzI+mMR/HjdMx8ZCC5+6fY0lS5tr0bbgiLlo= -github.com/aws/aws-sdk-go-v2 v1.27.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8= +github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= -github.com/aws/aws-sdk-go-v2/config v1.27.16 h1:knpCuH7laFVGYTNd99Ns5t+8PuRjDn4HnnZK48csipM= -github.com/aws/aws-sdk-go-v2/config v1.27.16/go.mod h1:vutqgRhDUktwSge3hrC3nkuirzkJ4E/mLj5GvI0BQas= -github.com/aws/aws-sdk-go-v2/credentials v1.17.16 h1:7d2QxY83uYl0l58ceyiSpxg9bSbStqBC6BeEeHEchwo= -github.com/aws/aws-sdk-go-v2/credentials v1.17.16/go.mod h1:Ae6li/6Yc6eMzysRL2BXlPYvnrLLBg3D11/AmOjw50k= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 h1:dQLK4TjtnlRGb0czOht2CevZ5l6RSyRWAnKeGd7VAFE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3/go.mod h1:TL79f2P6+8Q7dTsILpiVST+AL9lkF6PPGI167Ny0Cjw= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.21 h1:1v8Ii0MRVGYB/sdhkbxrtolCA7Tp+lGh+5OJTs5vmZ8= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.21/go.mod h1:cxdd1rc8yxCjKz28hi30XN1jDXr2DxZvD44vLxTz/bg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 h1:lf/8VTF2cM+N4SLzaYJERKEWAXq8MOMpZfU6wEPWsPk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7/go.mod h1:4SjkU7QiqK2M9oozyMzfZ/23LmUY+h3oFqhdeP5OMiI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 h1:4OYVp0705xu8yjdyoWix0r9wPIRXnIzzOoUpQVHIJ/g= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7/go.mod h1:vd7ESTEvI76T2Na050gODNmNU7+OyKrIKroYTu4ABiI= +github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk= +github.com/aws/aws-sdk-go-v2/config v1.27.18/go.mod h1:0xz6cgdX55+kmppvPm2IaKzIXOheGJhAufacPJaXZ7c= +github.com/aws/aws-sdk-go-v2/credentials v1.17.18 h1:D/ALDWqK4JdY3OFgA2thcPO1c9aYTT5STS/CvnkqY1c= +github.com/aws/aws-sdk-go-v2/credentials v1.17.18/go.mod h1:JuitCWq+F5QGUrmMPsk945rop6bB57jdscu+Glozdnc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 h1:dDgptDO9dxeFkXy+tEgVkzSClHZje/6JkPW5aZyEvrQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5/go.mod h1:gjvE2KBUgUQhcv89jqxrIxH9GaKs1JbZzWejj/DaHGA= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.24 h1:FzNwpVTZDCvm597Ty6mGYvxTolyC1oup0waaKntZI4E= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.24/go.mod h1:wM9NElT/Wn6n3CT1eyVcXtfCy8lSVjjQXfdawQbSShc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 h1:cy8ahBJuhtM8GTTSyOkfy6WVPV1IE+SS5/wfXUYuulw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9/go.mod h1:CZBXGLaJnEZI6EVNcPd7a6B5IC5cA/GkRWtu9fp3S6Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 h1:A4SYk07ef04+vxZToz9LWvAXl9LW0NClpPpMsi31cz0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9/go.mod h1:5jJcHuwDagxN+ErjQ3PU3ocf6Ylc/p9x+BLO/+X4iXw= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 h1:/FUtT3xsoHO3cfh+I/kCbcMCN98QZRsiFet/V8QkWSs= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7/go.mod h1:MaCAgWpGooQoCWZnMur97rGn5dp350w2+CeiV5406wE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.9 h1:vHyZxoLVOgrI8GqX7OMHLXp4YYoxeEsrjweXKpye+ds= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.9/go.mod h1:z9VXZsWA2BvZNH1dT0ToUYwMu/CR9Skkj/TBX+mceZw= github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.26.7 h1:rLdKcienXrk+JFX1+DZg160ebG8lIF2nFvnEZL7dnII= github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.26.7/go.mod h1:cwqaWBOZXu8pqEE1ZC4Sw2ycZLjwKrRP5tOAJFgCbYc= github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6 h1:ePPaOVn92r5n8Neecdpy93hDmR0PBH6H6b7VQCE5vKE= @@ -840,10 +840,10 @@ github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 h1:XKO0BswTDeZMLDBd/b5pCEZ github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8/go.mod h1:N5tqZcYMM0N1PN7UQYJNWuGyO886OfnMhf/3MAbqMcI= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.162.0 h1:A1YMX7uMzXhfIEL9zc5049oQgSaH4ZeXx/sOth0dk/I= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.162.0/go.mod h1:iJ2sQeUTkjNp3nL7kE/Bav0xXYhtiRCRP5ZXk4jFhCQ= -github.com/aws/aws-sdk-go-v2/service/ecr v1.28.3 h1:NsP8PA4Kw1sA6UKl3ZFRIcA9dWomePbmoRIvfOl+HKs= -github.com/aws/aws-sdk-go-v2/service/ecr v1.28.3/go.mod h1:X52zjAVRaXklEU1TE/wO8kyyJSr9cJx9ZsqliWbyRys= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.1 h1:0RiDkJO1veM6/FQ+GJcGiIhZgPwXlscX29B0zFE4Ulo= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.1/go.mod h1:gYk1NtyvkH1SxPcndDtfro3lwbiE5t0tW4eRki5YnOQ= +github.com/aws/aws-sdk-go-v2/service/ecr v1.28.5 h1:dvvTFXpWSv9+8lTNPl1EPNZL6BCUV6MgVckEMvXaOgk= +github.com/aws/aws-sdk-go-v2/service/ecr v1.28.5/go.mod h1:Ogt6AOZ/sPBlJZpVFJgOK+jGGREuo8DMjNg+O/7gpjI= github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6 h1:Sc2mLjyA1R8z2l705AN7Wr7QOlnUxVnGPJeDIVyUSrs= github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6/go.mod h1:LzHcyOEvaLjbc5e+fP/KmPWBr+h/Ef+EHvnf1Pzo368= github.com/aws/aws-sdk-go-v2/service/efs v1.28.1 h1:dKtJBzCIew4/VDsYgrx6v140cIpQVoe93kCNniYATtE= @@ -862,14 +862,14 @@ github.com/aws/aws-sdk-go-v2/service/iam v1.28.7 h1:FKPRDYZOO0Eur19vWUL1B40Op0j8 github.com/aws/aws-sdk-go-v2/service/iam v1.28.7/go.mod h1:YzMYyQ7S4twfYzLjwP24G1RAxypozVZeNaG1r2jxRms= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 h1:UXqEWQI0n+q0QixzU0yUUQBZXRd5037qdInTIHFTl98= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9/go.mod h1:xP6Gq6fzGZT8w/ZN+XvGMZ2RU1LeEs7b2yUP5DN8NY4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.11 h1:4vt9Sspk59EZyHCAEMaktHKiq0C09noRTQorXD/qV+s= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.11/go.mod h1:5jHR79Tv+Ccq6rwYh+W7Nptmw++WiFafMfR42XhwNl8= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11 h1:e9AVb17H4x5FTE5KWIP5M1Du+9M86pS+Hw0lBUdN8EY= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11/go.mod h1:B90ZQJa36xo0ph9HsoteI1+r8owgQH/U1QNfqZQkj1Q= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 h1:Wx0rlZoEJR7JwlSZcHnEa7CNjrSIyVxMFWGAaXy4fJY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9/go.mod h1:aVMHdE0aHO3v+f/iw01fmXV/5DbfQ3Bi9nN7nd9bE9Y= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 h1:uO5XR6QGBcmPyo2gxofYJLFkcVQ4izOoGDNenlZhTEk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7/go.mod h1:feeeAYfAcwTReM6vbwjEyDmiGho+YgBhaFULuXDW8kc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 h1:o4T+fKxA3gTMcluBNZZXE9DNaMkJuUL1O3mffCUjoJo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11/go.mod h1:84oZdJ+VjuJKs9v1UTC9NaodRZRseOXCTgku+vQJWR8= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.9 h1:TE2i0A9ErH1YfRSvXfCr2SQwfnqsoJT9nPQ9kj0lkxM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.9/go.mod h1:9TzXX3MehQNGPwCZ3ka4CpwQsoAMWSF48/b+De9rfVM= github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5 h1:yCkyZDGahaCaAkdpVx8Te05t6eW2FarBLunVC8S23nU= github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5/go.mod h1:/KmX+vXMPJGAB56reo95tnsXa6QPNx6qli4L1AmYb7E= github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6 h1:FO/aIHk86VePDUh/3Q/A5pnvu45miO1GZB8rIq2BUlA= @@ -886,20 +886,20 @@ github.com/aws/aws-sdk-go-v2/service/rds v1.66.1 h1:TafjIpDW/+l7s+f3EIONaFsNvNfw github.com/aws/aws-sdk-go-v2/service/rds v1.66.1/go.mod h1:MYzRMSdY70kcS8AFg0aHmk/xj6VAe0UfaCCoLrBWPow= github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7 h1:k4WaqQ7LHSGrSftCRXTRLv7WaozXu+fZ1jdisQSR2eU= github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7/go.mod h1:8hU0Ax6q6QA+jrMcWTE0A4YH594MQoWP3EzGO3GH5Dw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3 h1:57NtjG+WLims0TxIQbjTqebZUKDM03DfM11ANAekW0s= -github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3/go.mod h1:739CllldowZiPPsDFcJHNF4FXrVxaSGVnZ9Ez9Iz9hc= +github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1 h1:UAxBuh0/8sFJk1qOkvOKewP5sWeWaTPDknbQz0ZkDm0= +github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1/go.mod h1:hWjsYGjVuqCgfoveVcVFPXIWgz0aByzwaxKlN1StKcM= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 h1:dPCRgAL4WD9tSMaDglRNGOiAtSTjkwNiUW5GDpWFfHA= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0/go.mod h1:4Ae1NCLK6ghmjzd45Tc33GgCKhUWD2ORAlULtMO1Cbs= github.com/aws/aws-sdk-go-v2/service/sns v1.26.6 h1:w2YwF8889ardGU3Y0qZbJ4Zzh+Q/QqKZ4kwkK7JFvnI= github.com/aws/aws-sdk-go-v2/service/sns v1.26.6/go.mod h1:IrcbquqMupzndZ20BXxDxjM7XenTRhbwBOetk4+Z5oc= github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6 h1:UdbDTllc7cmusTTMy1dcTrYKRl4utDEsmKh9ZjvhJCc= github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6/go.mod h1:mCUv04gd/7g+/HNzDB4X6dzJuygji0ckvB3Lg/TdG5Y= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.9 h1:aD7AGQhvPuAxlSUfo0CWU7s6FpkbyykMhGYMvlqTjVs= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.9/go.mod h1:c1qtZUWtygI6ZdvKppzCSXsDOq5I4luJPZ0Ud3juFCA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3 h1:Pav5q3cA260Zqez42T9UhIlsd9QeypszRPwC9LdSSsQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3/go.mod h1:9lmoVDVLz/yUZwLaQ676TK02fhCu4+PgRSmMaKR1ozk= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.10 h1:69tpbPED7jKPyzMcrwSvhWcJ9bPnZsZs18NT40JwM0g= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.10/go.mod h1:0Aqn1MnEuitqfsCNyKsdKLhDUOr4txD/g19EfiUqgws= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 h1:gEYM2GSpr4YNWc6hCd5nod4+d4kd9vWIAWrmGuLdlMw= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.11/go.mod h1:gVvwPdPNYehHSP9Rs7q27U1EU+3Or2ZpXvzAYJNh63w= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 h1:iXjh3uaH3vsVcnyZX7MqCoCfcyxIrVE9iOQruRaWPrQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5/go.mod h1:5ZXesEuy/QcO0WUnt+4sDkxhdXRHTu2yG0uCSH8B6os= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 h1:M/1u4HBpwLuMtjlxuI2y6HoVLzF5e2mfxHCg7ZVMYmk= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.12/go.mod h1:kcfd+eTdEi/40FIbLq4Hif3XMXnl5b/+t/KTfLt9xIk= github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1 h1:pqxn3fcZDgWmo8GMUjlxVBdakcGo0AeUb7mjX33pJIQ= github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1/go.mod h1:kP5rUlnqfno/obflnKX4KMBWkoVHLDI8oCka9U0opRo= github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= From 09e50ce6a82073ba62f1732d5aa0cd2701578693 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:41:07 +0600 Subject: [PATCH 153/352] feat(sbom): migrate to `CycloneDX v1.6` (#6903) --- go.mod | 2 +- go.sum | 4 +- .../testdata/conda-cyclonedx.json.golden | 4 +- .../conda-environment-cyclonedx.json.golden | 4 +- ...fluentd-multiple-lockfiles.cdx.json.golden | 4 +- .../testdata/pom-cyclonedx.json.golden | 4 +- pkg/sbom/cyclonedx/marshal_test.go | 42 +++++++++---------- pkg/sbom/cyclonedx/testdata/happy/bom.json | 2 +- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index e041336347b5..e89d26e96169 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 github.com/BurntSushi/toml v1.4.0 - github.com/CycloneDX/cyclonedx-go v0.8.0 + github.com/CycloneDX/cyclonedx-go v0.9.0 github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible github.com/Masterminds/sprig/v3 v3.2.3 github.com/NYTimes/gziphandler v1.1.1 diff --git a/go.sum b/go.sum index 5c4cc4d02054..134e1d777079 100644 --- a/go.sum +++ b/go.sum @@ -653,8 +653,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CycloneDX/cyclonedx-go v0.8.0 h1:FyWVj6x6hoJrui5uRQdYZcSievw3Z32Z88uYzG/0D6M= -github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= +github.com/CycloneDX/cyclonedx-go v0.9.0 h1:inaif7qD8bivyxp7XLgxUYtOXWtDez7+j72qKTMQTb8= +github.com/CycloneDX/cyclonedx-go v0.9.0/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKcTwu8u0ccsACEsw= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible h1:juIaKLLVhqzP55d8x4cSVgwyQv76Z55/fRv/UBr2KkQ= diff --git a/integration/testdata/conda-cyclonedx.json.golden b/integration/testdata/conda-cyclonedx.json.golden index 9640112cce12..7f3a352fcce7 100644 --- a/integration/testdata/conda-cyclonedx.json.golden +++ b/integration/testdata/conda-cyclonedx.json.golden @@ -1,7 +1,7 @@ { - "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", "bomFormat": "CycloneDX", - "specVersion": "1.5", + "specVersion": "1.6", "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000004", "version": 1, "metadata": { diff --git a/integration/testdata/conda-environment-cyclonedx.json.golden b/integration/testdata/conda-environment-cyclonedx.json.golden index e927b7594bfb..7062e1e1a356 100644 --- a/integration/testdata/conda-environment-cyclonedx.json.golden +++ b/integration/testdata/conda-environment-cyclonedx.json.golden @@ -1,7 +1,7 @@ { - "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", "bomFormat": "CycloneDX", - "specVersion": "1.5", + "specVersion": "1.6", "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000004", "version": 1, "metadata": { diff --git a/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden b/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden index 934bda200639..cc442e7d881d 100644 --- a/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden +++ b/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden @@ -1,7 +1,7 @@ { - "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", "bomFormat": "CycloneDX", - "specVersion": "1.5", + "specVersion": "1.6", "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000163", "version": 1, "metadata": { diff --git a/integration/testdata/pom-cyclonedx.json.golden b/integration/testdata/pom-cyclonedx.json.golden index 0baa2382d58c..42650c62b54e 100644 --- a/integration/testdata/pom-cyclonedx.json.golden +++ b/integration/testdata/pom-cyclonedx.json.golden @@ -1,7 +1,7 @@ { - "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", "bomFormat": "CycloneDX", - "specVersion": "1.5", + "specVersion": "1.6", "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000005", "version": 1, "metadata": { diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go index d86cbfd1a218..d1fc8a455a2a 100644 --- a/pkg/sbom/cyclonedx/marshal_test.go +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -254,10 +254,10 @@ func TestMarshaler_MarshalReport(t *testing.T) { }, }, want: &cdx.BOM{ - XMLNS: "http://cyclonedx.org/schema/bom/1.5", + XMLNS: "http://cyclonedx.org/schema/bom/1.6", BOMFormat: "CycloneDX", - SpecVersion: cdx.SpecVersion1_5, - JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SpecVersion: cdx.SpecVersion1_6, + JSONSchema: "http://cyclonedx.org/schema/bom-1.6.schema.json", SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000014", Version: 1, Metadata: &cdx.Metadata{ @@ -909,10 +909,10 @@ func TestMarshaler_MarshalReport(t *testing.T) { }, }, want: &cdx.BOM{ - XMLNS: "http://cyclonedx.org/schema/bom/1.5", + XMLNS: "http://cyclonedx.org/schema/bom/1.6", BOMFormat: "CycloneDX", - SpecVersion: cdx.SpecVersion1_5, - JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SpecVersion: cdx.SpecVersion1_6, + JSONSchema: "http://cyclonedx.org/schema/bom-1.6.schema.json", SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000007", Version: 1, Metadata: &cdx.Metadata{ @@ -1293,10 +1293,10 @@ func TestMarshaler_MarshalReport(t *testing.T) { }, }, want: &cdx.BOM{ - XMLNS: "http://cyclonedx.org/schema/bom/1.5", + XMLNS: "http://cyclonedx.org/schema/bom/1.6", BOMFormat: "CycloneDX", - SpecVersion: cdx.SpecVersion1_5, - JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SpecVersion: cdx.SpecVersion1_6, + JSONSchema: "http://cyclonedx.org/schema/bom-1.6.schema.json", SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000007", Version: 1, Metadata: &cdx.Metadata{ @@ -1518,10 +1518,10 @@ func TestMarshaler_MarshalReport(t *testing.T) { BOM: testSBOM, }, want: &cdx.BOM{ - XMLNS: "http://cyclonedx.org/schema/bom/1.5", + XMLNS: "http://cyclonedx.org/schema/bom/1.6", BOMFormat: "CycloneDX", - SpecVersion: cdx.SpecVersion1_5, - JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SpecVersion: cdx.SpecVersion1_6, + JSONSchema: "http://cyclonedx.org/schema/bom-1.6.schema.json", SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000002", Version: 1, Metadata: &cdx.Metadata{ @@ -1770,10 +1770,10 @@ func TestMarshaler_MarshalReport(t *testing.T) { }, }, want: &cdx.BOM{ - XMLNS: "http://cyclonedx.org/schema/bom/1.5", + XMLNS: "http://cyclonedx.org/schema/bom/1.6", BOMFormat: "CycloneDX", - SpecVersion: cdx.SpecVersion1_5, - JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SpecVersion: cdx.SpecVersion1_6, + JSONSchema: "http://cyclonedx.org/schema/bom-1.6.schema.json", SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000004", Version: 1, Metadata: &cdx.Metadata{ @@ -1956,10 +1956,10 @@ func TestMarshaler_MarshalReport(t *testing.T) { }, }, want: &cdx.BOM{ - XMLNS: "http://cyclonedx.org/schema/bom/1.5", + XMLNS: "http://cyclonedx.org/schema/bom/1.6", BOMFormat: "CycloneDX", - SpecVersion: cdx.SpecVersion1_5, - JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SpecVersion: cdx.SpecVersion1_6, + JSONSchema: "http://cyclonedx.org/schema/bom-1.6.schema.json", SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000003", Version: 1, Metadata: &cdx.Metadata{ @@ -2044,10 +2044,10 @@ func TestMarshaler_MarshalReport(t *testing.T) { Results: types.Results{}, }, want: &cdx.BOM{ - XMLNS: "http://cyclonedx.org/schema/bom/1.5", + XMLNS: "http://cyclonedx.org/schema/bom/1.6", BOMFormat: "CycloneDX", - SpecVersion: cdx.SpecVersion1_5, - JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", + SpecVersion: cdx.SpecVersion1_6, + JSONSchema: "http://cyclonedx.org/schema/bom-1.6.schema.json", SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000002", Version: 1, Metadata: &cdx.Metadata{ diff --git a/pkg/sbom/cyclonedx/testdata/happy/bom.json b/pkg/sbom/cyclonedx/testdata/happy/bom.json index a7a1a474b8bd..e2d68e96b38b 100644 --- a/pkg/sbom/cyclonedx/testdata/happy/bom.json +++ b/pkg/sbom/cyclonedx/testdata/happy/bom.json @@ -1,6 +1,6 @@ { "bomFormat": "CycloneDX", - "specVersion": "1.5", + "specVersion": "1.6", "serialNumber": "urn:uuid:c986ba94-e37d-49c8-9e30-96daccd0415b", "version": 1, "metadata": { From baa1216895a77f0e8ca781d58857ec9746ae1ba0 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 13 Jun 2024 13:34:39 +0600 Subject: [PATCH 154/352] test: bump docker API to 1.45 (#6914) --- go.mod | 2 +- go.sum | 125 +++++++++++--------------- integration/docker_engine_test.go | 9 +- pkg/fanal/image/daemon/image_test.go | 3 +- pkg/fanal/image/daemon/podman_test.go | 2 +- pkg/fanal/image/image_test.go | 4 +- 6 files changed, 62 insertions(+), 83 deletions(-) diff --git a/go.mod b/go.mod index e89d26e96169..708609e1423e 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d github.com/aquasecurity/loading v0.0.5 github.com/aquasecurity/table v1.8.0 - github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334 + github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac github.com/aquasecurity/tml v0.6.1 github.com/aquasecurity/trivy-aws v0.9.0 github.com/aquasecurity/trivy-checks v0.11.0 diff --git a/go.sum b/go.sum index 134e1d777079..206b0c4caaba 100644 --- a/go.sum +++ b/go.sum @@ -175,6 +175,7 @@ cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63 cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= @@ -275,7 +276,6 @@ cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCV cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= @@ -623,6 +623,7 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJC github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= @@ -650,6 +651,7 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -683,8 +685,8 @@ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugX github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= @@ -767,8 +769,8 @@ github.com/aquasecurity/loading v0.0.5 h1:2iq02sPSSMU+ULFPmk0v0lXnK/eZ2e0dRAj/Dl github.com/aquasecurity/loading v0.0.5/go.mod h1:NSHeeq1JTDTFuXAe87q4yQ2DX57pXiaQMqq8Zm9HCJA= github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0= github.com/aquasecurity/table v1.8.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw= -github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334 h1:MgvbLyLBW8+uVD/Tv6uKw9ia8dfHynwVT/VKn5s5idI= -github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334/go.mod h1:TKXn7bPfMM52ETP4sjjwkTKCZ18CqCs+I/vtFePSdBc= +github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac h1:dy7xjLOAAeCNycqJ3kws4vDFGm8WdeCovkHXf2um5uA= +github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac/go.mod h1:nyavBQqxtIkQh99lQE1ssup3i2uIq1+giL7tOSHapYk= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= github.com/aquasecurity/trivy-aws v0.9.0 h1:0Xl5p5LtEwFMwZpuRQ6SSzVJN/fJZZtLenaacxjQFvE= @@ -783,10 +785,7 @@ github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7 h1 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7/go.mod h1:HSpAJE8Y5Cjjg0Aw/0lqd3vMihN/FxBEj/f/7yDi/Uc= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -915,7 +914,6 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c h1:C4UZIaS+HAw+X6jGUsoP2ZbM99PuqhCttjomg1yhNAI= github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= @@ -977,6 +975,7 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= @@ -1052,7 +1051,7 @@ github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3 github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/stargz-snapshotter/estargz v0.7.0/go.mod h1:83VWDqHnurTKliEB0YvWMiCfLDwv4Cjj1X9Vk98GJZw= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= @@ -1093,17 +1092,18 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/csaf-poc/csaf_distribution/v3 v3.0.0 h1:ob9+Fmpff0YWgTP3dYaw7G2hKQ9cegh9l3zksc+q3sM= @@ -1115,6 +1115,7 @@ github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1S github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -1140,19 +1141,19 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284= github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v26.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo= github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= @@ -1195,10 +1196,12 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= @@ -1335,7 +1338,6 @@ github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblf github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI= github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -1426,7 +1428,6 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.6.0/go.mod h1:euCCtNbZ6tKqi1E72vwDj2xZcN5ttKpZLfa/wSo5iLw= github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -1460,6 +1461,7 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -1492,6 +1494,7 @@ github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqE github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= @@ -1502,7 +1505,6 @@ github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= @@ -1527,40 +1529,29 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4Zs github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-getter v1.7.4 h1:3yQjWuxICvSpYwqSayAdKRFcvBl1y/vogCxczWSmix0= github.com/hashicorp/go-getter v1.7.4/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -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.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4= @@ -1573,10 +1564,6 @@ 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/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= @@ -1616,7 +1603,6 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -1637,10 +1623,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.0/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -1700,7 +1686,6 @@ github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WV github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -1733,7 +1718,6 @@ github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= @@ -1757,32 +1741,24 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 h1:TLygBUBxikNJJfLwgm+Qwdgq1FtfV8Uh7bcxRyTzK8s= github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032/go.mod h1:vYT9HE7WCvL64iVeZylKmCsWKfE+JZ8105iuh2Trk8g= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -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.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= @@ -1811,6 +1787,7 @@ github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGq github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1834,7 +1811,6 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -1870,6 +1846,7 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -1900,10 +1877,8 @@ github.com/owenrumney/squealer v1.2.2 h1:zsnZSwkWi8Y2lgwmg77b565vlHQovlvBrSBzmAs github.com/owenrumney/squealer v1.2.2/go.mod h1:pDCW33bWJ2kDOuz7+2BSXDgY38qusVX0MtjPCSFtdSo= github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 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.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= @@ -1929,7 +1904,6 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= @@ -1992,12 +1966,12 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= +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.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= -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/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= @@ -2008,7 +1982,6 @@ github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXn github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= @@ -2036,14 +2009,14 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sosedoff/gitkit v0.4.0 h1:opyQJ/h9xMRLsz2ca/2CRXtstePcpldiZN8DpLLF8Os= github.com/sosedoff/gitkit v0.4.0/go.mod h1:V3EpGZ0nvCBhXerPsbDeqtyReNb48cwP9KtkUYTKT5I= @@ -2067,11 +2040,10 @@ github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cA 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 v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -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 v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -2079,7 +2051,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= @@ -2106,7 +2077,6 @@ github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -2141,7 +2111,11 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/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.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= +github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= @@ -2202,9 +2176,6 @@ go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= @@ -2244,21 +2215,17 @@ go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93V go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -2274,11 +2241,15 @@ 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-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-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= @@ -2341,6 +2312,7 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= @@ -2349,9 +2321,7 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2394,7 +2364,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -2420,6 +2389,7 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= @@ -2434,7 +2404,6 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/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= @@ -2454,6 +2423,7 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2472,15 +2442,14 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2559,7 +2528,6 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2570,6 +2538,7 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2593,7 +2562,9 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2604,6 +2575,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= @@ -2620,6 +2593,7 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= @@ -2648,6 +2622,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2676,7 +2651,6 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -2722,6 +2696,7 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= @@ -2766,7 +2741,6 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= @@ -2803,6 +2777,7 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= google.golang.org/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk= google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -2996,9 +2971,11 @@ google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCD google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= @@ -3028,7 +3005,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= @@ -3042,7 +3018,6 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8 gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= diff --git a/integration/docker_engine_test.go b/integration/docker_engine_test.go index dd82b5f34b51..ad7d5020e9c3 100644 --- a/integration/docker_engine_test.go +++ b/integration/docker_engine_test.go @@ -304,7 +304,14 @@ func TestDockerEngine(t *testing.T) { osArgs = append(osArgs, tt.input) // Run Trivy - runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{wantErr: tt.wantErr}) + runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{ + wantErr: tt.wantErr, + // Container field was removed in Docker Engine v26.0 + // cf. https://github.com/docker/cli/blob/v26.1.3/docs/deprecated.md#container-and-containerconfig-fields-in-image-inspect + override: overrideFuncs(overrideUID, func(t *testing.T, want, _ *types.Report) { + want.Metadata.ImageConfig.Container = "" + }), + }) }) } } diff --git a/pkg/fanal/image/daemon/image_test.go b/pkg/fanal/image/daemon/image_test.go index ce8b5f852596..35ac8f278137 100644 --- a/pkg/fanal/image/daemon/image_test.go +++ b/pkg/fanal/image/daemon/image_test.go @@ -25,7 +25,7 @@ var imagePaths = map[string]string{ // for Docker var opt = engine.Option{ - APIVersion: "1.38", + APIVersion: "1.45", ImagePaths: imagePaths, } @@ -167,7 +167,6 @@ func Test_image_ConfigFile(t *testing.T) { imageName: "alpine:3.11", want: &v1.ConfigFile{ Architecture: "amd64", - Container: "fb71ddde5f6411a82eb056a9190f0cc1c80d7f77a8509ee90a2054428edb0024", OS: "linux", Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 196162891, time.UTC)}, DockerVersion: "18.09.7", diff --git a/pkg/fanal/image/daemon/podman_test.go b/pkg/fanal/image/daemon/podman_test.go index 697895fa95e8..106d821a0e32 100644 --- a/pkg/fanal/image/daemon/podman_test.go +++ b/pkg/fanal/image/daemon/podman_test.go @@ -31,7 +31,7 @@ func setupPodmanSock(t *testing.T) *httptest.Server { sockPath := filepath.Join(dir, "podman.sock") opt := engine.Option{ - APIVersion: "1.40", + APIVersion: "1.45", ImagePaths: map[string]string{ "index.docker.io/library/alpine:3.11": "../../test/testdata/alpine-311.tar.gz", }, diff --git a/pkg/fanal/image/image_test.go b/pkg/fanal/image/image_test.go index afcb2b978dbf..0ae4e50f8b11 100644 --- a/pkg/fanal/image/image_test.go +++ b/pkg/fanal/image/image_test.go @@ -26,7 +26,7 @@ func setupEngineAndRegistry() (*httptest.Server, *httptest.Server) { "a187dde48cd2": "../test/testdata/alpine-311.tar.gz", } opt := engine.Option{ - APIVersion: "1.38", + APIVersion: "1.45", ImagePaths: imagePaths, } te := engine.NewDockerEngine(opt) @@ -74,7 +74,6 @@ func TestNewDockerImage(t *testing.T) { wantRepoTags: []string{"alpine:3.11"}, wantConfigFile: &v1.ConfigFile{ Architecture: "amd64", - Container: "fb71ddde5f6411a82eb056a9190f0cc1c80d7f77a8509ee90a2054428edb0024", OS: "linux", Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 196162891, time.UTC)}, DockerVersion: "18.09.7", @@ -118,7 +117,6 @@ func TestNewDockerImage(t *testing.T) { wantRepoTags: []string{"alpine:3.11"}, wantConfigFile: &v1.ConfigFile{ Architecture: "amd64", - Container: "fb71ddde5f6411a82eb056a9190f0cc1c80d7f77a8509ee90a2054428edb0024", OS: "linux", Created: v1.Time{Time: time.Date(2020, 3, 23, 21, 19, 34, 196162891, time.UTC)}, DockerVersion: "18.09.7", From fdf799e6a7cafb3ffa7d7d0d6508fa9ec511bb09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 12:25:16 +0400 Subject: [PATCH 155/352] chore(deps): bump github.com/Azure/azure-sdk-for-go/sdk/azidentity from 1.5.2 to 1.6.0 (#6910) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 16 ++++++++-------- go.sum | 34 ++++++++++++++++------------------ 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 708609e1423e..b9d81fec970c 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.22.2 require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 github.com/BurntSushi/toml v1.4.0 github.com/CycloneDX/cyclonedx-go v0.9.0 github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible @@ -118,13 +118,13 @@ require ( github.com/zclconf/go-cty v1.14.4 github.com/zclconf/go-cty-yaml v1.0.3 go.etcd.io/bbolt v1.3.10 - golang.org/x/crypto v0.23.0 + golang.org/x/crypto v0.24.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/mod v0.17.0 - golang.org/x/net v0.25.0 + golang.org/x/net v0.26.0 golang.org/x/sync v0.7.0 - golang.org/x/term v0.20.0 - golang.org/x/text v0.15.0 + golang.org/x/term v0.21.0 + golang.org/x/text v0.16.0 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 @@ -143,7 +143,7 @@ require ( dario.cat/mergo v1.0.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect @@ -391,9 +391,9 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect - golang.org/x/sys v0.20.0 // indirect + golang.org/x/sys v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.19.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/api v0.172.0 // indirect google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect diff --git a/go.sum b/go.sum index 206b0c4caaba..715ca7ad7598 100644 --- a/go.sum +++ b/go.sum @@ -618,10 +618,10 @@ github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 h1:FDif4R1+UUR+00q6wquyX90K7A8dN+R5E8GEadoP7sU= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJCAIKs92XiPyQfTaBWqvHujDwKb6CBU= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= @@ -1139,8 +1139,6 @@ github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5 github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284= github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= @@ -2251,8 +2249,8 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2391,8 +2389,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2579,8 +2577,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2595,8 +2593,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2615,8 +2613,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2699,8 +2697,8 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 04ed5edbaaedd7df9b8ca4b0156b8c063298ca5f Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:28:16 +0600 Subject: [PATCH 156/352] ci: add `trivy` group for `dependabot` (#6908) --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cc152f6b231c..30b1683a0b56 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,6 +21,8 @@ updates: directory: / schedule: interval: weekly + ignore: + - dependency-name: "github.com/aquasecurity/trivy-*" ## `trivy-*` dependencies are updated manually groups: aws: patterns: @@ -33,5 +35,7 @@ updates: patterns: - "github.com/testcontainers/*" common: + exclude-patterns: + - "github.com/aquasecurity/trivy-*" patterns: - "*" \ No newline at end of file From 08a428a084ed29ac21c606fced80d7ab93fca866 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 13 Jun 2024 14:59:59 +0400 Subject: [PATCH 157/352] ci: move triage workflow yaml under .github/workflows (#6895) Signed-off-by: knqyf263 --- .github/{trivy-triage.yaml => workflows/triage.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{trivy-triage.yaml => workflows/triage.yaml} (100%) diff --git a/.github/trivy-triage.yaml b/.github/workflows/triage.yaml similarity index 100% rename from .github/trivy-triage.yaml rename to .github/workflows/triage.yaml From cd360dde202769b884c094326e07ef0332eb0daf Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 13 Jun 2024 20:30:09 +0600 Subject: [PATCH 158/352] BREAKING(misconf): flatten recursive types (#6862) --- go.mod | 2 +- go.sum | 4 +- .../adapters/terraform/google/iam/adapt.go | 102 ++---- .../terraform/google/iam/adapt_test.go | 317 +++++++++++++----- .../terraform/google/iam/folder_iam.go | 118 +++---- .../adapters/terraform/google/iam/folders.go | 43 --- .../adapters/terraform/google/iam/org_iam.go | 116 +++---- .../terraform/google/iam/project_iam.go | 221 ++++-------- .../adapters/terraform/google/iam/projects.go | 58 ---- .../adapters/terraform/google/storage/iam.go | 8 +- pkg/iac/providers/aws/iam/iam.go | 2 - pkg/iac/providers/google/iam/iam.go | 36 +- pkg/iac/rego/schemas/cloud.json | 56 +--- pkg/iac/terraform/modules.go | 9 + 14 files changed, 475 insertions(+), 617 deletions(-) delete mode 100644 pkg/iac/adapters/terraform/google/iam/folders.go delete mode 100644 pkg/iac/adapters/terraform/google/iam/projects.go diff --git a/go.mod b/go.mod index b9d81fec970c..97bc846086a5 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/aquasecurity/table v1.8.0 github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac github.com/aquasecurity/tml v0.6.1 - github.com/aquasecurity/trivy-aws v0.9.0 + github.com/aquasecurity/trivy-aws v0.9.1-0.20240607040622-8a7f09cd891f github.com/aquasecurity/trivy-checks v0.11.0 github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 diff --git a/go.sum b/go.sum index 715ca7ad7598..d47b409f6090 100644 --- a/go.sum +++ b/go.sum @@ -773,8 +773,8 @@ github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac h1:dy7xjLO github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac/go.mod h1:nyavBQqxtIkQh99lQE1ssup3i2uIq1+giL7tOSHapYk= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= -github.com/aquasecurity/trivy-aws v0.9.0 h1:0Xl5p5LtEwFMwZpuRQ6SSzVJN/fJZZtLenaacxjQFvE= -github.com/aquasecurity/trivy-aws v0.9.0/go.mod h1:KOrgoMtAxHmGa1oIixLxCdJsmyZdplo/9EI+DJ0vUUM= +github.com/aquasecurity/trivy-aws v0.9.1-0.20240607040622-8a7f09cd891f h1:LS8Xb8Lb0mosGay+hk7hkt8jVc+L8msTdjJCU+ICcS8= +github.com/aquasecurity/trivy-aws v0.9.1-0.20240607040622-8a7f09cd891f/go.mod h1:pfwElhU8kilUmgib1xBw91ZBPJya6EZ1unwvqC0ijh4= github.com/aquasecurity/trivy-checks v0.11.0 h1:hS5gSQyuyIITrY/kCY2AWQMUSwXLpdtbHDPaCs6eSaI= github.com/aquasecurity/trivy-checks v0.11.0/go.mod h1:IAK3eHcKNxIHo/ckxKoHsXmEpUG45/38grW5bBjL9lw= github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTUAkbGqpzt9nJsO24RAdfG+ZSiLFj0G2jO8c= diff --git a/pkg/iac/adapters/terraform/google/iam/adapt.go b/pkg/iac/adapters/terraform/google/iam/adapt.go index 4bc8014e14e8..e63f9f272d7d 100644 --- a/pkg/iac/adapters/terraform/google/iam/adapt.go +++ b/pkg/iac/adapters/terraform/google/iam/adapt.go @@ -1,109 +1,59 @@ package iam import ( - "github.com/google/uuid" + "golang.org/x/exp/maps" "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" "github.com/aquasecurity/trivy/pkg/iac/terraform" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Adapt(modules terraform.Modules) iam.IAM { return (&adapter{ - orgs: make(map[string]iam.Organization), - modules: modules, + orgs: make(map[string]*iam.Organization), + projects: make(map[string]*iam.Project), + projectsByID: make(map[string]string), // projectID -> blockID + folders: make(map[string]*iam.Folder), + modules: modules, }).Adapt() } type adapter struct { modules terraform.Modules - orgs map[string]iam.Organization - folders []parentedFolder - projects []parentedProject + orgs map[string]*iam.Organization + folders map[string]*iam.Folder + projects map[string]*iam.Project + projectsByID map[string]string workloadIdentityPoolProviders []iam.WorkloadIdentityPoolProvider } func (a *adapter) Adapt() iam.IAM { a.adaptOrganizationIAM() - a.adaptFolders() a.adaptFolderIAM() - a.adaptProjects() a.adaptProjectIAM() a.adaptWorkloadIdentityPoolProviders() - return a.merge() + return a.buildIAMOutput() } -func (a *adapter) addOrg(blockID string) { - if _, ok := a.orgs[blockID]; !ok { - a.orgs[blockID] = iam.Organization{ - Metadata: types.NewUnmanagedMetadata(), - } +func (a *adapter) buildIAMOutput() iam.IAM { + return iam.IAM{ + Organizations: fromPtrSlice(maps.Values(a.orgs)), + Folders: fromPtrSlice(maps.Values(a.folders)), + Projects: fromPtrSlice(maps.Values(a.projects)), + WorkloadIdentityPoolProviders: a.workloadIdentityPoolProviders, } } -func (a *adapter) merge() iam.IAM { - - // add projects to folders, orgs -PROJECT: - for _, project := range a.projects { - for i, folder := range a.folders { - if project.folderBlockID != "" && project.folderBlockID == folder.blockID { - folder.folder.Projects = append(folder.folder.Projects, project.project) - a.folders[i] = folder - continue PROJECT - } - } - if project.orgBlockID != "" { - if org, ok := a.orgs[project.orgBlockID]; ok { - org.Projects = append(org.Projects, project.project) - a.orgs[project.orgBlockID] = org - continue PROJECT - } - } - - org := iam.Organization{ - Metadata: types.NewUnmanagedMetadata(), - Projects: []iam.Project{project.project}, - } - a.orgs[uuid.NewString()] = org +func fromPtrSlice[T any](collection []*T) []T { + if len(collection) == 0 { + return nil } - // add folders to folders, orgs -FOLDER_NESTED: // nolint: gocritic - for _, folder := range a.folders { - for i, existing := range a.folders { - if folder.parentBlockID != "" && folder.parentBlockID == existing.blockID { - existing.folder.Folders = append(existing.folder.Folders, folder.folder) - a.folders[i] = existing - continue FOLDER_NESTED // nolint: gocritic - } - - } - } -FOLDER_ORG: // nolint: gocritic - for _, folder := range a.folders { - if folder.parentBlockID != "" { - if org, ok := a.orgs[folder.parentBlockID]; ok { - org.Folders = append(org.Folders, folder.folder) - a.orgs[folder.parentBlockID] = org - continue FOLDER_ORG // nolint: gocritic - } - } else { - // add to placeholder? - org := iam.Organization{ - Metadata: types.NewUnmanagedMetadata(), - Folders: []iam.Folder{folder.folder}, - } - a.orgs[uuid.NewString()] = org + result := make([]T, 0, len(collection)) + for _, item := range collection { + if item == nil { + continue } + result = append(result, *item) } - - output := iam.IAM{ - Organizations: nil, - WorkloadIdentityPoolProviders: a.workloadIdentityPoolProviders, - } - for _, org := range a.orgs { - output.Organizations = append(output.Organizations, org) - } - return output + return result } diff --git a/pkg/iac/adapters/terraform/google/iam/adapt_test.go b/pkg/iac/adapters/terraform/google/iam/adapt_test.go index 2a7cbf2640a0..6d6b6cb38b00 100644 --- a/pkg/iac/adapters/terraform/google/iam/adapt_test.go +++ b/pkg/iac/adapters/terraform/google/iam/adapt_test.go @@ -21,92 +21,90 @@ func Test_Adapt(t *testing.T) { { name: "basic", terraform: ` - data "google_organization" "org" { - domain = "example.com" - } - - resource "google_project" "my_project" { - name = "My Project" - project_id = "your-project-id" - org_id = data.google_organization.org.id - auto_create_network = true - } - - resource "google_folder" "department1" { - display_name = "Department 1" - parent = data.google_organization.org.id - } - - resource "google_folder_iam_member" "admin" { - folder = google_folder.department1.name - role = "roles/editor" - member = "user:alice@gmail.com" - } - - resource "google_folder_iam_binding" "folder-123" { - folder = google_folder.department1.name - role = "roles/nothing" - members = [ - "user:not-alice@gmail.com", - ] - } - - resource "google_organization_iam_member" "org-123" { - org_id = data.google_organization.org.id - role = "roles/whatever" - member = "user:member@gmail.com" - } - - resource "google_organization_iam_binding" "binding" { - org_id = data.google_organization.org.id - role = "roles/browser" - - members = [ - "user:member_2@gmail.com", - ] - } - - resource "google_iam_workload_identity_pool_provider" "example" { - workload_identity_pool_id = "example-pool" - workload_identity_pool_provider_id = "example-provider" - attribute_condition = "assertion.repository_owner=='your-github-organization'" - } +data "google_organization" "org" { + domain = "example.com" +} + +resource "google_project" "my_project" { + name = "My Project" + project_id = "your-project-id" + org_id = data.google_organization.org.org_id + auto_create_network = true +} + +resource "google_folder" "department1" { + display_name = "Department 1" + parent = data.google_organization.org.org_id +} + +resource "google_folder_iam_member" "admin" { + folder = google_folder.department1.name + role = "roles/editor" + member = "user:alice@gmail.com" +} + +resource "google_folder_iam_binding" "folder-123" { + folder = google_folder.department1.name + role = "roles/nothing" + members = [ + "user:not-alice@gmail.com", + ] +} + +resource "google_organization_iam_member" "org-123" { + org_id = data.google_organization.org.org_id + role = "roles/whatever" + member = "user:member@gmail.com" +} + +resource "google_organization_iam_binding" "binding" { + org_id = data.google_organization.org.org_id + role = "roles/browser" + + members = [ + "user:member_2@gmail.com", + ] +} + +resource "google_iam_workload_identity_pool_provider" "example" { + workload_identity_pool_id = "example-pool" + workload_identity_pool_provider_id = "example-provider" + attribute_condition = "assertion.repository_owner=='your-github-organization'" +} `, expected: iam.IAM{ - Organizations: []iam.Organization{ + Projects: []iam.Project{ + { + Metadata: iacTypes.NewTestMetadata(), + AutoCreateNetwork: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + }, + }, + Folders: []iam.Folder{ { Metadata: iacTypes.NewTestMetadata(), - - Projects: []iam.Project{ + Members: []iam.Member{ { - Metadata: iacTypes.NewTestMetadata(), - AutoCreateNetwork: iacTypes.Bool(true, iacTypes.NewTestMetadata()), + Metadata: iacTypes.NewTestMetadata(), + Member: iacTypes.String("user:alice@gmail.com", iacTypes.NewTestMetadata()), + Role: iacTypes.String("roles/editor", iacTypes.NewTestMetadata()), + DefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), }, }, - - Folders: []iam.Folder{ + Bindings: []iam.Binding{ { Metadata: iacTypes.NewTestMetadata(), - Members: []iam.Member{ - { - Metadata: iacTypes.NewTestMetadata(), - Member: iacTypes.String("user:alice@gmail.com", iacTypes.NewTestMetadata()), - Role: iacTypes.String("roles/editor", iacTypes.NewTestMetadata()), - DefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), - }, - }, - Bindings: []iam.Binding{ - { - Metadata: iacTypes.NewTestMetadata(), - Members: []iacTypes.StringValue{ - iacTypes.String("user:not-alice@gmail.com", iacTypes.NewTestMetadata()), - }, - Role: iacTypes.String("roles/nothing", iacTypes.NewTestMetadata()), - IncludesDefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), - }, + Members: []iacTypes.StringValue{ + iacTypes.String("user:not-alice@gmail.com", iacTypes.NewTestMetadata()), }, + Role: iacTypes.String("roles/nothing", iacTypes.NewTestMetadata()), + IncludesDefaultServiceAccount: iacTypes.Bool(false, iacTypes.NewTestMetadata()), }, }, + }, + }, + Organizations: []iam.Organization{ + { + Metadata: iacTypes.NewTestMetadata(), Members: []iam.Member{ { Metadata: iacTypes.NewTestMetadata(), @@ -137,6 +135,169 @@ func Test_Adapt(t *testing.T) { }, }, }, + { + name: "iam policies", + terraform: ` +resource "google_folder" "test" { + display_name = "Department 1" +} + +resource "google_folder_iam_policy" "folder" { + folder = google_folder.test.folder_id + policy_data = data.google_iam_policy.folder_admin.policy_data +} + +data "google_iam_policy" "folder_admin" { + binding { + role = "roles/editor" + + members = [ + "user:jane@example.com", + ] + } +} + +data "google_organization" "test" { + domain = "example.com" +} + +resource "google_organization_iam_policy" "organization" { + org_id = data.google_organization.test.name + policy_data = data.google_iam_policy.org_admin.policy_data +} + +data "google_iam_policy" "org_admin" { + binding { + role = "roles/editor" + + members = [ + "user:jane2@example.com", + ] + } +} + +resource "google_project" "test" { + name = "My Project2" +} + +resource "google_project_iam_policy" "project" { + project = google_project.test.id + policy_data = data.google_iam_policy.project_admin.policy_data +} + +data "google_iam_policy" "project_admin" { + binding { + role = "roles/editor" + + members = [ + "user:jane3@example.com", + ] + } +} +`, + expected: iam.IAM{ + Folders: []iam.Folder{ + { + Metadata: iacTypes.NewTestMetadata(), + Bindings: []iam.Binding{ + { + Metadata: iacTypes.NewTestMetadata(), + Role: iacTypes.StringTest("roles/editor"), + Members: []iacTypes.StringValue{ + iacTypes.StringTest("user:jane@example.com"), + }, + }, + }, + }, + }, + Organizations: []iam.Organization{ + { + Metadata: iacTypes.NewTestMetadata(), + Bindings: []iam.Binding{ + { + Metadata: iacTypes.NewTestMetadata(), + Role: iacTypes.StringTest("roles/editor"), + Members: []iacTypes.StringValue{ + iacTypes.StringTest("user:jane2@example.com"), + }, + }, + }, + }, + }, + Projects: []iam.Project{ + { + AutoCreateNetwork: iacTypes.BoolTest(true), + Metadata: iacTypes.NewTestMetadata(), + Bindings: []iam.Binding{ + { + Metadata: iacTypes.NewTestMetadata(), + Role: iacTypes.StringTest("roles/editor"), + Members: []iacTypes.StringValue{ + iacTypes.StringTest("user:jane3@example.com"), + }, + }, + }, + }, + }, + }, + }, + { + name: "google_project_iam ref by value", + terraform: ` +resource "google_project" "my_project" { + name = "My Project" + project_id = "your-project-id" + org_id = "1234567" +} + + +resource "google_project_iam_member" "project" { + project = "your-project-id" + role = "roles/editor" + member = "user:jane@example.com" +} +`, + expected: iam.IAM{ + Projects: []iam.Project{ + { + Metadata: iacTypes.NewTestMetadata(), + AutoCreateNetwork: iacTypes.BoolTest(true), + Members: []iam.Member{ + { + Metadata: iacTypes.NewTestMetadata(), + Role: iacTypes.StringTest("roles/editor"), + Member: iacTypes.StringTest("user:jane@example.com"), + }, + }, + }, + }, + }, + }, + { + name: "only google_project_iam", + terraform: ` +resource "google_project_iam_member" "project" { + project = "your-project-id" + role = "roles/editor" + member = "user:jane@example.com" +} +`, + expected: iam.IAM{ + Projects: []iam.Project{ + { + Metadata: iacTypes.NewTestMetadata(), + AutoCreateNetwork: iacTypes.BoolTest(false), + Members: []iam.Member{ + { + Metadata: iacTypes.NewTestMetadata(), + Role: iacTypes.StringTest("roles/editor"), + Member: iacTypes.StringTest("user:jane@example.com"), + }, + }, + }, + }, + }, + }, } for _, test := range tests { @@ -205,14 +366,14 @@ func TestLines(t *testing.T) { adapted := Adapt(modules) require.Len(t, adapted.Organizations, 1) - require.Len(t, adapted.Organizations[0].Projects, 1) - require.Len(t, adapted.Organizations[0].Folders, 1) + require.Len(t, adapted.Projects, 1) + require.Len(t, adapted.Folders, 1) require.Len(t, adapted.Organizations[0].Bindings, 1) require.Len(t, adapted.Organizations[0].Members, 1) require.Len(t, adapted.WorkloadIdentityPoolProviders, 1) - project := adapted.Organizations[0].Projects[0] - folder := adapted.Organizations[0].Folders[0] + project := adapted.Projects[0] + folder := adapted.Folders[0] binding := adapted.Organizations[0].Bindings[0] member := adapted.Organizations[0].Members[0] pool := adapted.WorkloadIdentityPoolProviders[0] diff --git a/pkg/iac/adapters/terraform/google/iam/folder_iam.go b/pkg/iac/adapters/terraform/google/iam/folder_iam.go index eccdef2c638c..7b572b2bc634 100644 --- a/pkg/iac/adapters/terraform/google/iam/folder_iam.go +++ b/pkg/iac/adapters/terraform/google/iam/folder_iam.go @@ -1,45 +1,44 @@ package iam import ( + "github.com/google/uuid" + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" + "github.com/aquasecurity/trivy/pkg/iac/terraform" "github.com/aquasecurity/trivy/pkg/iac/types" ) // see https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_folder_iam func (a *adapter) adaptFolderIAM() { + a.adaptFolders() a.adaptFolderMembers() a.adaptFolderBindings() } +const googleFolder = "google_folder" + +func (a *adapter) adaptFolders() { + for _, folderBlock := range a.modules.GetResourcesByType(googleFolder) { + a.folders[folderBlock.ID()] = &iam.Folder{ + Metadata: folderBlock.GetMetadata(), + } + } +} + func (a *adapter) adaptFolderMembers() { for _, iamBlock := range a.modules.GetResourcesByType("google_folder_iam_member") { member := a.adaptMember(iamBlock) - folderAttr := iamBlock.GetAttribute("folder") - if refBlock, err := a.modules.GetReferencedBlock(folderAttr, iamBlock); err == nil { - if refBlock.TypeLabel() == GoogleFolder { - var foundFolder bool - for i, folder := range a.folders { - if folder.blockID == refBlock.ID() { - folder.folder.Members = append(folder.folder.Members, member) - a.folders[i] = folder - foundFolder = true - break - } - } - if foundFolder { - continue - } - } - } - // we didn't find the folder - add an unmanaged one - a.folders = append(a.folders, parentedFolder{ - folder: iam.Folder{ + if folder := a.findFolder(iamBlock); folder != nil { + folder.Members = append(folder.Members, member) + } else { + // we didn't find the folder - add an unmanaged one + a.folders[uuid.NewString()] = &iam.Folder{ Metadata: types.NewUnmanagedMetadata(), Members: []iam.Member{member}, - }, - }) + } + } } } @@ -51,67 +50,50 @@ func (a *adapter) adaptFolderBindings() { if policyAttr.IsNil() { continue } + policyBlock, err := a.modules.GetReferencedBlock(policyAttr, iamBlock) if err != nil { continue } - bindings := ParsePolicyBlock(policyBlock) - folderAttr := iamBlock.GetAttribute("folder") - - if refBlock, err := a.modules.GetReferencedBlock(folderAttr, iamBlock); err == nil { - if refBlock.TypeLabel() == GoogleFolder { - var foundFolder bool - for i, folder := range a.folders { - if folder.blockID == refBlock.ID() { - folder.folder.Bindings = append(folder.folder.Bindings, bindings...) - a.folders[i] = folder - foundFolder = true - break - } - } - if foundFolder { - continue - } - } - } + bindings := ParsePolicyBlock(policyBlock) - // we didn't find the project - add an unmanaged one - a.folders = append(a.folders, parentedFolder{ - folder: iam.Folder{ + if folder := a.findFolder(iamBlock); folder != nil { + folder.Bindings = append(folder.Bindings, bindings...) + } else { + // we didn't find the folder - add an unmanaged one + a.folders[uuid.NewString()] = &iam.Folder{ Metadata: types.NewUnmanagedMetadata(), Bindings: bindings, - }, - }) + } + } } for _, iamBlock := range a.modules.GetResourcesByType("google_folder_iam_binding") { binding := a.adaptBinding(iamBlock) - folderAttr := iamBlock.GetAttribute("folder") - if refBlock, err := a.modules.GetReferencedBlock(folderAttr, iamBlock); err == nil { - if refBlock.TypeLabel() == GoogleFolder { - var foundFolder bool - for i, folder := range a.folders { - if folder.blockID == refBlock.ID() { - folder.folder.Bindings = append(folder.folder.Bindings, binding) - a.folders[i] = folder - foundFolder = true - break - } - } - if foundFolder { - continue - } + if folder := a.findFolder(iamBlock); folder != nil { + folder.Bindings = append(folder.Bindings, binding) + } else { + // we didn't find the folder - add an unmanaged one + a.folders[uuid.NewString()] = &iam.Folder{ + Metadata: types.NewUnmanagedMetadata(), + Bindings: []iam.Binding{binding}, } } + } +} - // we didn't find the folder - add an unmanaged one - a.folders = append(a.folders, parentedFolder{ - folder: iam.Folder{ - Metadata: types.NewUnmanagedMetadata(), - Bindings: []iam.Binding{binding}, - }, - }) +func (a *adapter) findFolder(iamBlock *terraform.Block) *iam.Folder { + folderAttr := iamBlock.GetAttribute("folder") + refBlock, err := a.modules.GetReferencedBlock(folderAttr, iamBlock) + if err != nil { + return nil } + + if folder, exists := a.folders[refBlock.ID()]; exists { + return folder + } + + return nil } diff --git a/pkg/iac/adapters/terraform/google/iam/folders.go b/pkg/iac/adapters/terraform/google/iam/folders.go deleted file mode 100644 index 1091f625dc0a..000000000000 --- a/pkg/iac/adapters/terraform/google/iam/folders.go +++ /dev/null @@ -1,43 +0,0 @@ -package iam - -import ( - "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" -) - -const GoogleOrganization = "google_organization" -const GoogleFolder = "google_folder" - -type parentedFolder struct { - blockID string - parentBlockID string - parentRef string - folder iam.Folder -} - -func (a *adapter) adaptFolders() { - for _, folderBlock := range a.modules.GetResourcesByType(GoogleFolder) { - var folder parentedFolder - parentAttr := folderBlock.GetAttribute("parent") - if parentAttr.IsNil() { - continue - } - - folder.folder.Metadata = folderBlock.GetMetadata() - folder.blockID = folderBlock.ID() - if parentAttr.IsString() { - folder.parentRef = parentAttr.Value().AsString() - } - - if referencedBlock, err := a.modules.GetReferencedBlock(parentAttr, folderBlock); err == nil { - if referencedBlock.TypeLabel() == GoogleFolder { - folder.parentBlockID = referencedBlock.ID() - } - if referencedBlock.TypeLabel() == GoogleOrganization { - folder.parentBlockID = referencedBlock.ID() - a.addOrg(folder.parentBlockID) - } - } - - a.folders = append(a.folders, folder) - } -} diff --git a/pkg/iac/adapters/terraform/google/iam/org_iam.go b/pkg/iac/adapters/terraform/google/iam/org_iam.go index 8ce88053a2f1..fe6ac9917fbe 100644 --- a/pkg/iac/adapters/terraform/google/iam/org_iam.go +++ b/pkg/iac/adapters/terraform/google/iam/org_iam.go @@ -4,51 +4,40 @@ import ( "github.com/google/uuid" "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" + "github.com/aquasecurity/trivy/pkg/iac/terraform" "github.com/aquasecurity/trivy/pkg/iac/types" ) // see https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_organization_iam func (a *adapter) adaptOrganizationIAM() { + a.adaptOrganizations() a.adaptOrganizationMembers() a.adaptOrganizationBindings() } +func (a *adapter) adaptOrganizations() { + for _, orgBlock := range a.modules.GetDatasByType("google_organization") { + a.orgs[orgBlock.ID()] = &iam.Organization{ + Metadata: orgBlock.GetMetadata(), + } + } +} + func (a *adapter) adaptOrganizationMembers() { for _, iamBlock := range a.modules.GetResourcesByType("google_organization_iam_member") { + member := a.adaptMember(iamBlock) - organizationAttr := iamBlock.GetAttribute("organization") - if organizationAttr.IsNil() { - organizationAttr = iamBlock.GetAttribute("org_id") - } - if refBlock, err := a.modules.GetReferencedBlock(organizationAttr, iamBlock); err == nil { - if refBlock.TypeLabel() == GoogleOrganization { - a.addOrg(refBlock.ID()) - org, ok := a.orgs[refBlock.ID()] - if !ok { - org = iam.Organization{ - Metadata: refBlock.GetMetadata(), - Folders: nil, - Projects: nil, - Members: []iam.Member{member}, - Bindings: nil, - } - } - org.Members = append(org.Members, member) - a.orgs[refBlock.ID()] = org - continue + if org := a.findOrganization(iamBlock); org != nil { + org.Members = append(org.Members, member) + } else { + // we didn't find the org - add an unmanaged one + a.orgs[uuid.NewString()] = &iam.Organization{ + Metadata: types.NewUnmanagedMetadata(), + Members: []iam.Member{member}, } } - - // we didn't find the organization - add an unmanaged one - placeholderID := uuid.NewString() - org := iam.Organization{ - Metadata: types.NewUnmanagedMetadata(), - Members: []iam.Member{member}, - } - a.orgs[placeholderID] = org - } } @@ -60,55 +49,54 @@ func (a *adapter) adaptOrganizationBindings() { if policyAttr.IsNil() { continue } + policyBlock, err := a.modules.GetReferencedBlock(policyAttr, iamBlock) if err != nil { continue } + bindings := ParsePolicyBlock(policyBlock) - orgAttr := iamBlock.GetAttribute("organization") - - if refBlock, err := a.modules.GetReferencedBlock(orgAttr, iamBlock); err == nil { - if refBlock.TypeLabel() == GoogleOrganization { - if org, ok := a.orgs[refBlock.ID()]; ok { - org.Bindings = append(org.Bindings, bindings...) - a.orgs[refBlock.ID()] = org - continue - } - } - } - // we didn't find the organization - add an unmanaged one - placeholderID := uuid.NewString() - org := iam.Organization{ - Metadata: types.NewUnmanagedMetadata(), - Bindings: bindings, + if org := a.findOrganization(iamBlock); org != nil { + org.Bindings = append(org.Bindings, bindings...) + } else { + // we didn't find the org - add an unmanaged one + a.orgs[uuid.NewString()] = &iam.Organization{ + Metadata: types.NewUnmanagedMetadata(), + Bindings: bindings, + } } - a.orgs[placeholderID] = org } for _, iamBlock := range a.modules.GetResourcesByType("google_organization_iam_binding") { + binding := a.adaptBinding(iamBlock) - organizationAttr := iamBlock.GetAttribute("organization") - if organizationAttr.IsNil() { - organizationAttr = iamBlock.GetAttribute("org_id") - } - if refBlock, err := a.modules.GetReferencedBlock(organizationAttr, iamBlock); err == nil { - if refBlock.TypeLabel() == GoogleOrganization { - a.addOrg(refBlock.ID()) - org := a.orgs[refBlock.ID()] - org.Bindings = append(org.Bindings, binding) - a.orgs[refBlock.ID()] = org - continue + if org := a.findOrganization(iamBlock); org != nil { + org.Bindings = append(org.Bindings, binding) + } else { + // we didn't find the org - add an unmanaged one + a.orgs[uuid.NewString()] = &iam.Organization{ + Metadata: types.NewUnmanagedMetadata(), + Bindings: []iam.Binding{binding}, } } + } +} - // we didn't find the organization - add an unmanaged one - placeholderID := uuid.NewString() - org := iam.Organization{ - Metadata: types.NewUnmanagedMetadata(), - Bindings: []iam.Binding{binding}, - } - a.orgs[placeholderID] = org +func (a *adapter) findOrganization(iamBlock *terraform.Block) *iam.Organization { + orgAttr := iamBlock.GetAttribute("organization") + if orgAttr.IsNil() { + orgAttr = iamBlock.GetAttribute("org_id") + } + refBlock, err := a.modules.GetReferencedBlock(orgAttr, iamBlock) + if err != nil { + return nil } + + if org, exists := a.orgs[refBlock.ID()]; exists { + return org + } + + return nil } diff --git a/pkg/iac/adapters/terraform/google/iam/project_iam.go b/pkg/iac/adapters/terraform/google/iam/project_iam.go index a3f670ee3be3..fc7f539ded12 100644 --- a/pkg/iac/adapters/terraform/google/iam/project_iam.go +++ b/pkg/iac/adapters/terraform/google/iam/project_iam.go @@ -3,6 +3,8 @@ package iam import ( "strings" + "github.com/google/uuid" + "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" "github.com/aquasecurity/trivy/pkg/iac/terraform" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" @@ -13,10 +15,25 @@ import ( const GoogleProject = "google_project" func (a *adapter) adaptProjectIAM() { + a.adaptProjects() a.adaptProjectMembers() a.adaptProjectBindings() } +func (a *adapter) adaptProjects() { + for _, projectBlock := range a.modules.GetResourcesByType(GoogleProject) { + idAttr := projectBlock.GetAttribute("project_id") + if idAttr.IsString() { + a.projectsByID[idAttr.Value().AsString()] = projectBlock.ID() + } + + a.projects[projectBlock.ID()] = &iam.Project{ + Metadata: projectBlock.GetMetadata().Root(), + AutoCreateNetwork: projectBlock.GetAttribute("auto_create_network").AsBoolValueOrDefault(true, projectBlock), + } + } +} + func (a *adapter) adaptMember(iamBlock *terraform.Block) iam.Member { return AdaptMember(iamBlock, a.modules) } @@ -39,13 +56,13 @@ func AdaptMember(iamBlock *terraform.Block, modules terraform.Modules) iam.Membe return member } +// TODO(nikita): add new resources var projectMemberResources = []string{ "google_project_iam_member", "google_cloud_run_service_iam_member", "google_compute_instance_iam_member", "google_compute_subnetwork_iam_member", "google_data_catalog_entry_group_iam_member", - "google_folder_iam_member", "google_pubsub_subscription_iam_member", "google_pubsub_topic_iam_member", "google_sourcerepo_repository_iam_member", @@ -58,64 +75,19 @@ func (a *adapter) adaptProjectMembers() { for _, memberType := range projectMemberResources { for _, iamBlock := range a.modules.GetResourcesByType(memberType) { - member := a.adaptMember(iamBlock) - projectAttr := iamBlock.GetAttribute("project") - if projectAttr.IsString() { - var foundProject bool - projectID := projectAttr.Value().AsString() - for i, project := range a.projects { - if project.id == projectID { - project.project.Members = append(project.project.Members, member) - a.projects[i] = project - foundProject = true - break - } - } - if foundProject { - continue - } - } - - if refBlock, err := a.modules.GetReferencedBlock(projectAttr, iamBlock); err == nil { - if refBlock.TypeLabel() == GoogleProject { - var foundProject bool - for i, project := range a.projects { - if project.blockID == refBlock.ID() { - project.project.Members = append(project.project.Members, member) - a.projects[i] = project - foundProject = true - break - } - } - if foundProject { - continue - } - } - } - - // we didn't find the project - add an unmanaged one - // unless it already belongs to an existing folder - var foundFolder bool - if refBlock, err := a.modules.GetReferencedBlock(iamBlock.GetAttribute("folder"), iamBlock); err == nil { - for _, folder := range a.folders { - if folder.blockID == refBlock.ID() { - foundFolder = true - } - } - } - if foundFolder { - continue - } + member := a.adaptMember(iamBlock) - a.projects = append(a.projects, parentedProject{ - project: iam.Project{ + if project := a.findProject(iamBlock); project != nil { + project.Members = append(project.Members, member) + } else { + // we didn't find the folder - add an unmanaged one + a.projects[uuid.NewString()] = &iam.Project{ Metadata: iacTypes.NewUnmanagedMetadata(), AutoCreateNetwork: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), Members: []iam.Member{member}, - Bindings: nil, - }, - }) + } + } } } } @@ -144,13 +116,13 @@ func AdaptBinding(iamBlock *terraform.Block, modules terraform.Modules) iam.Bind return binding } +// TODO(nikita): add new resources var projectBindingResources = []string{ "google_project_iam_binding", "google_cloud_run_service_iam_binding", "google_compute_instance_iam_binding", "google_compute_subnetwork_iam_binding", "google_data_catalog_entry_group_iam_binding", - "google_folder_iam_binding", "google_pubsub_subscription_iam_binding", "google_pubsub_topic_iam_binding", "google_sourcerepo_repository_iam_binding", @@ -166,55 +138,24 @@ func (a *adapter) adaptProjectDataBindings() { if policyAttr.IsNil() { continue } + policyBlock, err := a.modules.GetReferencedBlock(policyAttr, iamBlock) if err != nil { continue } - bindings := ParsePolicyBlock(policyBlock) - projectAttr := iamBlock.GetAttribute("project") - if projectAttr.IsString() { - var foundProject bool - projectID := projectAttr.Value().AsString() - for i, project := range a.projects { - if project.id == projectID { - project.project.Bindings = append(project.project.Bindings, bindings...) - a.projects[i] = project - foundProject = true - break - } - } - if foundProject { - continue - } - } - if refBlock, err := a.modules.GetReferencedBlock(projectAttr, iamBlock); err == nil { - if refBlock.TypeLabel() == GoogleProject { - var foundProject bool - for i, project := range a.projects { - if project.blockID == refBlock.ID() { - project.project.Bindings = append(project.project.Bindings, bindings...) - a.projects[i] = project - foundProject = true - break - } - } - if foundProject { - continue - } - - } - } + bindings := ParsePolicyBlock(policyBlock) - // we didn't find the project - add an unmanaged one - a.projects = append(a.projects, parentedProject{ - project: iam.Project{ + if project := a.findProject(iamBlock); project != nil { + project.Bindings = append(project.Bindings, bindings...) + } else { + // we didn't find the folder - add an unmanaged one + a.projects[uuid.NewString()] = &iam.Project{ Metadata: iacTypes.NewUnmanagedMetadata(), AutoCreateNetwork: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), - Members: nil, Bindings: bindings, - }, - }) + } + } } } @@ -225,63 +166,49 @@ func (a *adapter) adaptProjectBindings() { for _, bindingType := range projectBindingResources { for _, iamBlock := range a.modules.GetResourcesByType(bindingType) { - binding := a.adaptBinding(iamBlock) - projectAttr := iamBlock.GetAttribute("project") - if projectAttr.IsString() { - var foundProject bool - projectID := projectAttr.Value().AsString() - for i, project := range a.projects { - if project.id == projectID { - project.project.Bindings = append(project.project.Bindings, binding) - a.projects[i] = project - foundProject = true - break - } - } - if foundProject { - continue - } - } - if refBlock, err := a.modules.GetReferencedBlock(projectAttr, iamBlock); err == nil { - if refBlock.TypeLabel() == GoogleProject { - var foundProject bool - for i, project := range a.projects { - if project.blockID == refBlock.ID() { - project.project.Bindings = append(project.project.Bindings, binding) - a.projects[i] = project - foundProject = true - break - } - } - if foundProject { - continue - } - - } - } + binding := a.adaptBinding(iamBlock) - // we didn't find the project - add an unmanaged one - // unless it already belongs to an existing folder - var foundFolder bool - if refBlock, err := a.modules.GetReferencedBlock(iamBlock.GetAttribute("folder"), iamBlock); err == nil { - for _, folder := range a.folders { - if folder.blockID == refBlock.ID() { - foundFolder = true - } - } - } - if foundFolder { - continue - } - a.projects = append(a.projects, parentedProject{ - project: iam.Project{ + if project := a.findProject(iamBlock); project != nil { + project.Bindings = append(project.Bindings, binding) + } else { + // we didn't find the folder - add an unmanaged one + a.projects[uuid.NewString()] = &iam.Project{ Metadata: iacTypes.NewUnmanagedMetadata(), AutoCreateNetwork: iacTypes.BoolDefault(false, iacTypes.NewUnmanagedMetadata()), - Members: nil, Bindings: []iam.Binding{binding}, - }, - }) + } + } + } + } +} + +func (a *adapter) resolveProjectBlockID(projectAttr *terraform.Attribute, iamBlock *terraform.Block) string { + + if projectAttr.IsString() { + projectID := projectAttr.Value().AsString() + if blockID, exists := a.projectsByID[projectID]; exists { + return blockID } } + + refBlock, err := a.modules.GetReferencedBlock(projectAttr, iamBlock) + if err != nil { + return "" + } + return refBlock.ID() +} + +func (a *adapter) findProject(iamBlock *terraform.Block) *iam.Project { + projectAttr := iamBlock.GetAttribute("project") + blockID := a.resolveProjectBlockID(projectAttr, iamBlock) + if blockID == "" { + return nil + } + + if project, exists := a.projects[blockID]; exists { + return project + } + + return nil } diff --git a/pkg/iac/adapters/terraform/google/iam/projects.go b/pkg/iac/adapters/terraform/google/iam/projects.go deleted file mode 100644 index f77ded00f3cc..000000000000 --- a/pkg/iac/adapters/terraform/google/iam/projects.go +++ /dev/null @@ -1,58 +0,0 @@ -package iam - -import ( - "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" -) - -type parentedProject struct { - blockID string - orgBlockID string - folderBlockID string - id string - orgID string - folderID string - project iam.Project -} - -func (a *adapter) adaptProjects() { - for _, projectBlock := range a.modules.GetResourcesByType(GoogleProject) { - var project parentedProject - project.project.Metadata = projectBlock.GetMetadata() - idAttr := projectBlock.GetAttribute("project_id") - if !idAttr.IsString() { - continue - } - project.id = idAttr.Value().AsString() - - project.blockID = projectBlock.ID() - - orgAttr := projectBlock.GetAttribute("org_id") - if orgAttr.IsString() { - project.orgID = orgAttr.Value().AsString() - } - folderAttr := projectBlock.GetAttribute("folder_id") - if folderAttr.IsString() { - project.folderID = folderAttr.Value().AsString() - } - - autoCreateNetworkAttr := projectBlock.GetAttribute("auto_create_network") - project.project.AutoCreateNetwork = autoCreateNetworkAttr.AsBoolValueOrDefault(true, projectBlock) - - if orgAttr.IsNotNil() { - if referencedBlock, err := a.modules.GetReferencedBlock(orgAttr, projectBlock); err == nil { - if referencedBlock.TypeLabel() == GoogleOrganization { - project.orgBlockID = referencedBlock.ID() - a.addOrg(project.orgBlockID) - } - } - } - if folderAttr.IsNotNil() { - if referencedBlock, err := a.modules.GetReferencedBlock(folderAttr, projectBlock); err == nil { - if referencedBlock.TypeLabel() == GoogleFolder { - project.folderBlockID = referencedBlock.ID() - } - } - } - a.projects = append(a.projects, project) - } -} diff --git a/pkg/iac/adapters/terraform/google/storage/iam.go b/pkg/iac/adapters/terraform/google/storage/iam.go index be399304b168..6f6cdd000c94 100644 --- a/pkg/iac/adapters/terraform/google/storage/iam.go +++ b/pkg/iac/adapters/terraform/google/storage/iam.go @@ -1,7 +1,7 @@ package storage import ( - iam2 "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/google/iam" + "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform/google/iam" iamTypes "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" ) @@ -48,7 +48,7 @@ func (a *adapter) adaptBindings() { continue } - parented.bindings = iam2.ParsePolicyBlock(policyBlock) + parented.bindings = iam.ParsePolicyBlock(policyBlock) a.bindings = append(a.bindings, parented) } @@ -56,7 +56,7 @@ func (a *adapter) adaptBindings() { var parented parentedBinding parented.blockID = iamBlock.ID() - parented.bindings = []iamTypes.Binding{iam2.AdaptBinding(iamBlock, a.modules)} + parented.bindings = []iamTypes.Binding{iam.AdaptBinding(iamBlock, a.modules)} bucketAttr := iamBlock.GetAttribute("bucket") if bucketAttr.IsString() { @@ -79,7 +79,7 @@ func (a *adapter) adaptMembers() { var parented parentedMember parented.blockID = iamBlock.ID() - parented.member = iam2.AdaptMember(iamBlock, a.modules) + parented.member = iam.AdaptMember(iamBlock, a.modules) bucketAttr := iamBlock.GetAttribute("bucket") if bucketAttr.IsString() { diff --git a/pkg/iac/providers/aws/iam/iam.go b/pkg/iac/providers/aws/iam/iam.go index 7589c7cbce48..7bec336a8960 100644 --- a/pkg/iac/providers/aws/iam/iam.go +++ b/pkg/iac/providers/aws/iam/iam.go @@ -59,14 +59,12 @@ func (d Document) ToRego() any { type Group struct { Metadata iacTypes.Metadata Name iacTypes.StringValue - Users []User Policies []Policy } type User struct { Metadata iacTypes.Metadata Name iacTypes.StringValue - Groups []Group Policies []Policy AccessKeys []AccessKey MFADevices []MFADevice diff --git a/pkg/iac/providers/google/iam/iam.go b/pkg/iac/providers/google/iam/iam.go index c48192e76b97..1711f8609bf0 100755 --- a/pkg/iac/providers/google/iam/iam.go +++ b/pkg/iac/providers/google/iam/iam.go @@ -7,20 +7,18 @@ import ( type IAM struct { Organizations []Organization WorkloadIdentityPoolProviders []WorkloadIdentityPoolProvider + Projects []Project + Folders []Folder } type Organization struct { Metadata iacTypes.Metadata - Folders []Folder - Projects []Project Members []Member Bindings []Binding } type Folder struct { Metadata iacTypes.Metadata - Folders []Folder - Projects []Project Members []Member Bindings []Binding } @@ -54,35 +52,9 @@ type WorkloadIdentityPoolProvider struct { } func (p *IAM) AllProjects() []Project { - var projects []Project - for _, org := range p.Organizations { - projects = append(projects, org.Projects...) - for _, folder := range org.Folders { - projects = append(projects, folder.Projects...) - for _, desc := range folder.AllFolders() { - projects = append(projects, desc.Projects...) - } - } - } - return projects + return p.Projects } func (p *IAM) AllFolders() []Folder { - var folders []Folder - for _, org := range p.Organizations { - folders = append(folders, org.Folders...) - for _, folder := range org.Folders { - folders = append(folders, folder.AllFolders()...) - } - } - return folders -} - -func (f *Folder) AllFolders() []Folder { - var folders []Folder - for _, folder := range f.Folders { - folders = append(folders, folder) - folders = append(folders, folder.AllFolders()...) - } - return folders + return p.Folders } diff --git a/pkg/iac/rego/schemas/cloud.json b/pkg/iac/rego/schemas/cloud.json index e5b3ec740196..ad81b6488ae8 100644 --- a/pkg/iac/rego/schemas/cloud.json +++ b/pkg/iac/rego/schemas/cloud.json @@ -2524,13 +2524,6 @@ "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Policy" } - }, - "users": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.User" - } } } }, @@ -2696,13 +2689,6 @@ "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.AccessKey" } }, - "groups": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.iam.Group" - } - }, "lastaccess": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.TimeValue" @@ -6639,32 +6625,25 @@ "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Binding" } }, - "folders": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Folder" - } - }, "members": { "type": "array", "items": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Member" } - }, - "projects": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Project" - } } } }, "github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.IAM": { "type": "object", "properties": { + "folders": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Folder" + } + }, "organizations": { "type": "array", "items": { @@ -6672,6 +6651,13 @@ "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Organization" } }, + "projects": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Project" + } + }, "workloadidentitypoolproviders": { "type": "array", "items": { @@ -6716,26 +6702,12 @@ "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Binding" } }, - "folders": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Folder" - } - }, "members": { "type": "array", "items": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Member" } - }, - "projects": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.google.iam.Project" - } } } }, diff --git a/pkg/iac/terraform/modules.go b/pkg/iac/terraform/modules.go index 8a9cad25c433..515d3dc8007d 100644 --- a/pkg/iac/terraform/modules.go +++ b/pkg/iac/terraform/modules.go @@ -23,6 +23,15 @@ func (r ResourceIDResolutions) Orphans() (orphanIDs []string) { return orphanIDs } +func (m Modules) GetDatasByType(typeLabel string) Blocks { + var blocks Blocks + for _, module := range m { + blocks = append(blocks, module.GetDatasByType(typeLabel)...) + } + + return blocks +} + func (m Modules) GetResourcesByType(typeLabel ...string) Blocks { var blocks Blocks for _, module := range m { From 55fa6109cd0463fd3221aae41ca7b1d8c44ad430 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Fri, 14 Jun 2024 02:44:43 +0600 Subject: [PATCH 159/352] feat(misconf): add support for AWS::EC2::SecurityGroupIngress/Egress (#6755) --- .../cloudformation/aws/ec2/adapt_test.go | 51 +++++++++++ .../cloudformation/aws/ec2/security_group.go | 85 ++++++++++++------- 2 files changed, 104 insertions(+), 32 deletions(-) diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go b/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go index ac05f8f7b263..01fb201768e9 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go @@ -272,6 +272,57 @@ Resources: }, }, }, + { + name: "security group with ingress and egress rules", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MySecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupName: MySecurityGroup + GroupDescription: MySecurityGroup + InboundRule: + Type: AWS::EC2::SecurityGroupIngress + Properties: + GroupId: !Ref MySecurityGroup + Description: Inbound + CidrIp: 0.0.0.0/0 + OutboundRule: + Type: AWS::EC2::SecurityGroupEgress + Properties: + GroupId: !GetAtt MySecurityGroup.GroupId + Description: Outbound + CidrIp: 0.0.0.0/0 + RuleWithoutGroup: + Type: AWS::EC2::SecurityGroupIngress + Properties: + CidrIpv6: ::/0 + Description: Inbound +`, + expected: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Description: types.StringTest("MySecurityGroup"), + IngressRules: []ec2.SecurityGroupRule{ + { + Description: types.StringTest("Inbound"), + CIDRs: []types.StringValue{ + types.StringTest("0.0.0.0/0"), + }, + }, + }, + EgressRules: []ec2.SecurityGroupRule{ + { + Description: types.StringTest("Outbound"), + CIDRs: []types.StringValue{ + types.StringTest("0.0.0.0/0"), + }, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go index 72546aa116e0..6de47f302682 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go @@ -1,12 +1,16 @@ package ec2 import ( + "golang.org/x/exp/maps" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getSecurityGroups(ctx parser.FileContext) (groups []ec2.SecurityGroup) { +func getSecurityGroups(ctx parser.FileContext) []ec2.SecurityGroup { + mGroups := make(map[string]ec2.SecurityGroup) + for _, r := range ctx.GetResourcesByType("AWS::EC2::SecurityGroup") { group := ec2.SecurityGroup{ Metadata: r.Metadata(), @@ -17,52 +21,69 @@ func getSecurityGroups(ctx parser.FileContext) (groups []ec2.SecurityGroup) { VPCID: r.GetStringProperty("VpcId"), } - groups = append(groups, group) + mGroups[r.ID()] = group + } + + for _, r := range ctx.GetResourcesByType("AWS::EC2::SecurityGroupIngress") { + groupID := r.GetProperty("GroupId").AsString() + + if group, ok := mGroups[groupID]; ok { + group.IngressRules = append(group.IngressRules, adaptRule(r)) + mGroups[groupID] = group + } } - return groups + + for _, r := range ctx.GetResourcesByType("AWS::EC2::SecurityGroupEgress") { + groupID := r.GetProperty("GroupId").AsString() + + if group, ok := mGroups[groupID]; ok { + group.EgressRules = append(group.EgressRules, adaptRule(r)) + mGroups[groupID] = group + } + } + + if len(mGroups) > 0 { + return maps.Values(mGroups) + } + return nil } func getIngressRules(r *parser.Resource) (sgRules []ec2.SecurityGroupRule) { if ingressProp := r.GetProperty("SecurityGroupIngress"); ingressProp.IsList() { for _, ingress := range ingressProp.AsList() { - rule := ec2.SecurityGroupRule{ - Metadata: ingress.Metadata(), - Description: ingress.GetStringProperty("Description"), - CIDRs: nil, - } - v4Cidr := ingress.GetProperty("CidrIp") - if v4Cidr.IsString() && v4Cidr.AsStringValue().IsNotEmpty() { - rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v4Cidr.AsString(), v4Cidr.Metadata())) - } - v6Cidr := ingress.GetProperty("CidrIpv6") - if v6Cidr.IsString() && v6Cidr.AsStringValue().IsNotEmpty() { - rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v6Cidr.AsString(), v6Cidr.Metadata())) - } - - sgRules = append(sgRules, rule) + sgRules = append(sgRules, adaptRule(ingress)) } } + return sgRules } func getEgressRules(r *parser.Resource) (sgRules []ec2.SecurityGroupRule) { if egressProp := r.GetProperty("SecurityGroupEgress"); egressProp.IsList() { for _, egress := range egressProp.AsList() { - rule := ec2.SecurityGroupRule{ - Metadata: egress.Metadata(), - Description: egress.GetStringProperty("Description"), - } - v4Cidr := egress.GetProperty("CidrIp") - if v4Cidr.IsString() && v4Cidr.AsStringValue().IsNotEmpty() { - rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v4Cidr.AsString(), v4Cidr.Metadata())) - } - v6Cidr := egress.GetProperty("CidrIpv6") - if v6Cidr.IsString() && v6Cidr.AsStringValue().IsNotEmpty() { - rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v6Cidr.AsString(), v6Cidr.Metadata())) - } - - sgRules = append(sgRules, rule) + sgRules = append(sgRules, adaptRule(egress)) } } return sgRules } + +func adaptRule(r interface { + GetProperty(string) *parser.Property + Metadata() types.Metadata + GetStringProperty(string, ...string) types.StringValue +}) ec2.SecurityGroupRule { + rule := ec2.SecurityGroupRule{ + Metadata: r.Metadata(), + Description: r.GetStringProperty("Description"), + } + v4Cidr := r.GetProperty("CidrIp") + if v4Cidr.IsString() && v4Cidr.AsStringValue().IsNotEmpty() { + rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v4Cidr.AsString(), v4Cidr.Metadata())) + } + v6Cidr := r.GetProperty("CidrIpv6") + if v6Cidr.IsString() && v6Cidr.AsStringValue().IsNotEmpty() { + rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v6Cidr.AsString(), v6Cidr.Metadata())) + } + + return rule +} From d77d9ce384a4af2f5fbd347fba595fb9af577871 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:16:26 +0600 Subject: [PATCH 160/352] ci: use `ubuntu-latest-m` runner (#6918) --- .github/workflows/bypass-test.yaml | 4 +- .github/workflows/reusable-release.yaml | 11 +---- .github/workflows/test.yaml | 54 ++++--------------------- 3 files changed, 11 insertions(+), 58 deletions(-) diff --git a/.github/workflows/bypass-test.yaml b/.github/workflows/bypass-test.yaml index abf91aff45f6..b46addba1d37 100644 --- a/.github/workflows/bypass-test.yaml +++ b/.github/workflows/bypass-test.yaml @@ -20,12 +20,12 @@ jobs: runs-on: ${{ matrix.operating-system }} strategy: matrix: - operating-system: [ubuntu-latest, windows-latest, macos-latest] + operating-system: [ubuntu-latest-m, windows-latest, macos-latest] steps: - run: 'echo "No test required"' integration: name: Integration Test - runs-on: ubuntu-latest + runs-on: ubuntu-latest-m steps: - run: 'echo "No test required"' \ No newline at end of file diff --git a/.github/workflows/reusable-release.yaml b/.github/workflows/reusable-release.yaml index ecae0832b93a..49b3ced06daa 100644 --- a/.github/workflows/reusable-release.yaml +++ b/.github/workflows/reusable-release.yaml @@ -19,7 +19,7 @@ env: jobs: release: name: Release - runs-on: ubuntu-latest + runs-on: ubuntu-latest-m env: DOCKER_CLI_EXPERIMENTAL: "enabled" permissions: @@ -27,15 +27,6 @@ jobs: packages: write # For GHCR contents: read # Not required for public repositories, but for clarity steps: - - name: Maximize build space - uses: easimon/maximize-build-space@v10 - with: - root-reserve-mb: 32768 # The Go cache (`~/.cache/go-build` and `~/go/pkg`) requires a lot of storage space. - remove-android: 'true' - remove-docker-images: 'true' - remove-dotnet: 'true' - remove-haskell: 'true' - - name: Cosign install uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8ff082624f97..c472b931ea61 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -15,18 +15,8 @@ jobs: runs-on: ${{ matrix.operating-system }} strategy: matrix: - operating-system: [ubuntu-latest, windows-latest, macos-latest] + operating-system: [ubuntu-latest-m, windows-latest, macos-latest] steps: - - name: Maximize build space - uses: easimon/maximize-build-space@v10 - with: - root-reserve-mb: 32768 # The golangci-lint uses a lot of space. - remove-android: "true" - remove-docker-images: "true" - remove-dotnet: "true" - remove-haskell: "true" - if: matrix.operating-system == 'ubuntu-latest' - - uses: actions/checkout@v4.1.6 - name: Set up Go @@ -40,7 +30,7 @@ jobs: echo "Run 'go mod tidy' and push it" exit 1 fi - if: matrix.operating-system == 'ubuntu-latest' + if: matrix.operating-system == 'ubuntu-latest-m' - name: Lint id: lint @@ -48,7 +38,7 @@ jobs: with: version: v1.58 args: --verbose --out-format=line-number - if: matrix.operating-system == 'ubuntu-latest' + if: matrix.operating-system == 'ubuntu-latest-m' - name: Check if linter failed run: | @@ -69,14 +59,14 @@ jobs: echo "Run 'mage docs:generate' and push it" exit 1 fi - if: matrix.operating-system == 'ubuntu-latest' + if: matrix.operating-system == 'ubuntu-latest-m' - name: Run unit tests run: mage test:unit integration: name: Integration Test - runs-on: ubuntu-latest + runs-on: ubuntu-latest-m steps: - name: Check out code into the Go module directory uses: actions/checkout@v4.1.6 @@ -96,17 +86,8 @@ jobs: k8s-integration: name: K8s Integration Test - runs-on: ubuntu-latest + runs-on: ubuntu-latest-m steps: - - name: Maximize build space - uses: easimon/maximize-build-space@v10 - with: - root-reserve-mb: 32768 # The Go cache (`~/.cache/go-build` and `~/go/pkg`) requires a lot of storage space. - remove-android: "true" - remove-docker-images: "true" - remove-dotnet: "true" - remove-haskell: "true" - - name: Check out code into the Go module directory uses: actions/checkout@v4.1.6 @@ -147,17 +128,8 @@ jobs: vm-test: name: VM Integration Test - runs-on: ubuntu-latest + runs-on: ubuntu-latest-m steps: - - name: Maximize build space - uses: easimon/maximize-build-space@v10 - with: - root-reserve-mb: 32768 # The Go cache (`~/.cache/go-build` and `~/go/pkg`) requires a lot of storage space. - remove-android: 'true' - remove-docker-images: 'true' - remove-dotnet: 'true' - remove-haskell: 'true' - - name: Checkout uses: actions/checkout@v4.1.6 @@ -178,20 +150,10 @@ jobs: runs-on: ${{ matrix.operating-system }} strategy: matrix: - operating-system: [ubuntu-latest, windows-latest, macos-latest] + operating-system: [ubuntu-latest-m, windows-latest, macos-latest] env: DOCKER_CLI_EXPERIMENTAL: "enabled" steps: - - name: Maximize build space - uses: easimon/maximize-build-space@v10 - with: - root-reserve-mb: 32768 # The Go cache (`~/.cache/go-build` and `~/go/pkg`) requires a lot of storage space. - remove-android: 'true' - remove-docker-images: 'true' - remove-dotnet: 'true' - remove-haskell: 'true' - if: matrix.operating-system == 'ubuntu-latest' - - name: Checkout uses: actions/checkout@v4.1.6 From 52f7aa54b520a90a19736703f8ea63cc20fab104 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:36:47 +0600 Subject: [PATCH 161/352] fix(license): return license separation using separators `,`, `or`, etc. (#6916) --- pkg/dependency/parser/conda/meta/parse.go | 4 +- pkg/dependency/parser/php/composer/parse.go | 25 ++++++- .../parser/php/composer/parse_test.go | 8 +- .../php/composer/testdata/composer_happy.lock | 8 +- .../parser/python/packaging/parse.go | 4 +- .../parser/python/packaging/parse_test.go | 74 ++++++++++++------- 6 files changed, 80 insertions(+), 43 deletions(-) diff --git a/pkg/dependency/parser/conda/meta/parse.go b/pkg/dependency/parser/conda/meta/parse.go index 08f5623fa052..2dd09d551def 100644 --- a/pkg/dependency/parser/conda/meta/parse.go +++ b/pkg/dependency/parser/conda/meta/parse.go @@ -3,10 +3,10 @@ package meta import ( "encoding/json" - "github.com/samber/lo" "golang.org/x/xerrors" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -40,7 +40,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc { Name: data.Name, Version: data.Version, - Licenses: lo.Ternary(data.License != "", []string{data.License}, nil), + Licenses: licensing.SplitLicenses(data.License), }, }, nil, nil } diff --git a/pkg/dependency/parser/php/composer/parse.go b/pkg/dependency/parser/php/composer/parse.go index 1b1e72bb7a10..c95901686ba0 100644 --- a/pkg/dependency/parser/php/composer/parse.go +++ b/pkg/dependency/parser/php/composer/parse.go @@ -11,6 +11,7 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -22,7 +23,7 @@ type packageInfo struct { Name string `json:"name"` Version string `json:"version"` Require map[string]string `json:"require"` - License []string `json:"license"` + License any `json:"license"` StartLine int EndLine int } @@ -55,7 +56,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc Name: lpkg.Name, Version: lpkg.Version, Relationship: ftypes.RelationshipUnknown, // composer.lock file doesn't have info about direct/indirect dependencies - Licenses: lpkg.License, + Licenses: licenses(lpkg.License), Locations: []ftypes.Location{ { StartLine: lpkg.StartLine, @@ -114,3 +115,23 @@ func (t *packageInfo) UnmarshalJSONWithMetadata(node jfather.Node) error { t.EndLine = node.Range().End.Line return nil } + +// licenses returns slice of licenses from string, string with separators (`or`, `and`, etc.) or string array +// cf. https://getcomposer.org/doc/04-schema.md#license +func licenses(val any) []string { + switch v := val.(type) { + case string: + if v != "" { + return licensing.SplitLicenses(v) + } + case []any: + var lics []string + for _, l := range v { + if lic, ok := l.(string); ok { + lics = append(lics, lic) + } + } + return lics + } + return nil +} diff --git a/pkg/dependency/parser/php/composer/parse_test.go b/pkg/dependency/parser/php/composer/parse_test.go index 58e19982720d..7a06ed87db4a 100644 --- a/pkg/dependency/parser/php/composer/parse_test.go +++ b/pkg/dependency/parser/php/composer/parse_test.go @@ -98,7 +98,7 @@ var ( Locations: []ftypes.Location{ { StartLine: 502, - EndLine: 585, + EndLine: 583, }, }, }, @@ -106,11 +106,11 @@ var ( ID: "symfony/polyfill-php72@v1.27.0", Name: "symfony/polyfill-php72", Version: "v1.27.0", - Licenses: []string{"MIT"}, + Licenses: []string{"MIT", "BSD-2-Clause"}, Locations: []ftypes.Location{ { - StartLine: 586, - EndLine: 661, + StartLine: 584, + EndLine: 657, }, }, }, diff --git a/pkg/dependency/parser/php/composer/testdata/composer_happy.lock b/pkg/dependency/parser/php/composer/testdata/composer_happy.lock index 2987ab8d58d9..832f35d854d1 100644 --- a/pkg/dependency/parser/php/composer/testdata/composer_happy.lock +++ b/pkg/dependency/parser/php/composer/testdata/composer_happy.lock @@ -541,9 +541,7 @@ ] }, "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], + "license": "MIT", "authors": [ { "name": "Nicolas Grekas", @@ -619,9 +617,7 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], + "license": "MIT or BSD-2-Clause", "authors": [ { "name": "Nicolas Grekas", diff --git a/pkg/dependency/parser/python/packaging/parse.go b/pkg/dependency/parser/python/packaging/parse.go index c8376a8066f2..fa1758308860 100644 --- a/pkg/dependency/parser/python/packaging/parse.go +++ b/pkg/dependency/parser/python/packaging/parse.go @@ -7,10 +7,10 @@ import ( "net/textproto" "strings" - "github.com/samber/lo" "golang.org/x/xerrors" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -87,7 +87,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc { Name: name, Version: version, - Licenses: lo.Ternary(license != "", []string{license}, nil), + Licenses: licensing.SplitLicenses(license), }, }, nil, nil } diff --git a/pkg/dependency/parser/python/packaging/parse_test.go b/pkg/dependency/parser/python/packaging/parse_test.go index cde70dea19ce..2051762699b1 100644 --- a/pkg/dependency/parser/python/packaging/parse_test.go +++ b/pkg/dependency/parser/python/packaging/parse_test.go @@ -35,9 +35,11 @@ func TestParse(t *testing.T) { // tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}' want: []ftypes.Package{ { - Name: "setuptools", - Version: "51.3.3", - Licenses: []string{"UNKNOWN"}, + Name: "setuptools", + Version: "51.3.3", + Licenses: []string{ + "UNKNOWN", + }, }, }, }, @@ -46,9 +48,11 @@ func TestParse(t *testing.T) { input: "testdata/unidecode-egg-info.PKG-INFO", want: []ftypes.Package{ { - Name: "Unidecode", - Version: "0.4.1", - Licenses: []string{"UNKNOWN"}, + Name: "Unidecode", + Version: "0.4.1", + Licenses: []string{ + "UNKNOWN", + }, }, }, }, @@ -63,9 +67,11 @@ func TestParse(t *testing.T) { // tr "\n" "\t" | awk -F "\t" '{printf("\{\""$1"\", \""$2"\", \""$3"\"\}\n")}' want: []ftypes.Package{ { - Name: "distlib", - Version: "0.3.1", - Licenses: []string{"Python license"}, + Name: "distlib", + Version: "0.3.1", + Licenses: []string{ + "Python license", + }, }, }, }, @@ -96,9 +102,11 @@ func TestParse(t *testing.T) { input: "testdata/distlib-0.3.1.METADATA", want: []ftypes.Package{ { - Name: "distlib", - Version: "0.3.1", - Licenses: []string{"Python Software Foundation License"}, + Name: "distlib", + Version: "0.3.1", + Licenses: []string{ + "Python Software Foundation License", + }, }, }, }, @@ -109,9 +117,11 @@ func TestParse(t *testing.T) { want: []ftypes.Package{ { - Name: "asyncssh", - Version: "2.14.2", - Licenses: []string{"Eclipse Public License v2.0"}, + Name: "asyncssh", + Version: "2.14.2", + Licenses: []string{ + "Eclipse Public License v2.0", + }, }, }, }, @@ -122,9 +132,13 @@ func TestParse(t *testing.T) { want: []ftypes.Package{ { - Name: "pyphen", - Version: "0.14.0", - Licenses: []string{"GNU General Public License v2 or later (GPLv2+), GNU Lesser General Public License v2 or later (LGPLv2+), Mozilla Public License 1.1 (MPL 1.1)"}, + Name: "pyphen", + Version: "0.14.0", + Licenses: []string{ + "GNU General Public License v2 or later (GPLv2+)", + "GNU Lesser General Public License v2 or later (LGPLv2+)", + "Mozilla Public License 1.1 (MPL 1.1)", + }, }, }, }, @@ -138,9 +152,11 @@ func TestParse(t *testing.T) { input: "testdata/iniconfig-2.0.0.METADATA", want: []ftypes.Package{ { - Name: "iniconfig", - Version: "2.0.0", - Licenses: []string{"MIT"}, + Name: "iniconfig", + Version: "2.0.0", + Licenses: []string{ + "MIT", + }, }, }, }, @@ -149,9 +165,11 @@ func TestParse(t *testing.T) { input: "testdata/zipp-3.12.1.METADATA", want: []ftypes.Package{ { - Name: "zipp", - Version: "3.12.1", - Licenses: []string{"MIT License"}, + Name: "zipp", + Version: "3.12.1", + Licenses: []string{ + "MIT License", + }, }, }, }, @@ -160,9 +178,11 @@ func TestParse(t *testing.T) { input: "testdata/networkx-3.0.METADATA", want: []ftypes.Package{ { - Name: "networkx", - Version: "3.0", - Licenses: []string{"file://LICENSE.txt"}, + Name: "networkx", + Version: "3.0", + Licenses: []string{ + "file://LICENSE.txt", + }, }, }, }, From 735aadf2d5eade014379e4eff5fd6cefd7772342 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:39:55 +0600 Subject: [PATCH 162/352] ci: don't run `tests` for `release-please` PRs (#6936) --- .github/workflows/bypass-test.yaml | 2 ++ .github/workflows/test.yaml | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/bypass-test.yaml b/.github/workflows/bypass-test.yaml index b46addba1d37..eafff9769a1d 100644 --- a/.github/workflows/bypass-test.yaml +++ b/.github/workflows/bypass-test.yaml @@ -8,12 +8,14 @@ on: - 'docs/**' - 'mkdocs.yml' - 'LICENSE' + - '.release-please-manifest.json' pull_request: paths: - '**.md' - 'docs/**' - 'mkdocs.yml' - 'LICENSE' + - '.release-please-manifest.json' jobs: test: name: Test diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c472b931ea61..2b21d714153f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -6,6 +6,7 @@ on: - 'docs/**' - 'mkdocs.yml' - 'LICENSE' + - '.release-please-manifest.json' ## don't run tests for release-please PRs merge_group: env: GO_VERSION: '1.22' From bc3741ae2c68cdd00fc0aef7e51985568b2eb78a Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 18 Jun 2024 05:20:38 +0700 Subject: [PATCH 163/352] feat(misconf): support of selectors for all providers for Rego (#6905) Signed-off-by: nikpivkin --- pkg/iac/providers/provider.go | 7 +++++++ pkg/iac/rego/scanner.go | 38 ++++++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/pkg/iac/providers/provider.go b/pkg/iac/providers/provider.go index cef13ee8f205..46dbf19ec43c 100755 --- a/pkg/iac/providers/provider.go +++ b/pkg/iac/providers/provider.go @@ -26,6 +26,13 @@ const ( CloudStackProvider Provider = "cloudstack" ) +func AllProviders() []Provider { + return []Provider{ + AWSProvider, AzureProvider, DigitalOceanProvider, GitHubProvider, GoogleProvider, + KubernetesProvider, OracleProvider, OpenStackProvider, NifcloudProvider, CloudStackProvider, + } +} + func RuleProviderToString(provider Provider) string { return strings.ToUpper(string(provider)) } diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go index 723f4c02181b..2e0516761a02 100644 --- a/pkg/iac/rego/scanner.go +++ b/pkg/iac/rego/scanner.go @@ -17,12 +17,30 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rego/schemas" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" ) +var checkTypesWithSubtype = map[types.Source]struct{}{ + types.SourceCloud: {}, + types.SourceDefsec: {}, + types.SourceKubernetes: {}, +} + +var supportedProviders = makeSupportedProviders() + +func makeSupportedProviders() map[string]struct{} { + m := make(map[string]struct{}) + for _, p := range providers.AllProviders() { + m[string(p)] = struct{}{} + } + m["kind"] = struct{}{} // kubernetes + return m +} + var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { @@ -295,12 +313,8 @@ func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, } func isPolicyWithSubtype(sourceType types.Source) bool { - for _, s := range []types.Source{types.SourceCloud, types.SourceDefsec, types.SourceKubernetes} { - if sourceType == s { - return true - } - } - return false + _, exists := checkTypesWithSubtype[sourceType] + return exists } func checkSubtype(ii map[string]any, provider string, subTypes []SubType) bool { @@ -311,10 +325,11 @@ func checkSubtype(ii map[string]any, provider string, subTypes []SubType) bool { for _, st := range subTypes { switch services := ii[provider].(type) { case map[string]any: - for service := range services { - if (service == st.Service) && (st.Provider == provider) { - return true - } + if st.Provider != provider { + continue + } + if _, exists := services[st.Service]; exists { + return true } case string: // k8s - logic can be improved if strings.EqualFold(services, st.Group) || @@ -331,8 +346,7 @@ func isPolicyApplicable(staticMetadata *StaticMetadata, inputs ...Input) bool { for _, input := range inputs { if ii, ok := input.Contents.(map[string]any); ok { for provider := range ii { - // TODO(simar): Add other providers - if !strings.Contains(strings.Join([]string{"kind", "aws", "azure"}, ","), provider) { + if _, exists := supportedProviders[provider]; !exists { continue } From ec68c9ab4580d057720179173d58734402c92af4 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 18 Jun 2024 05:29:22 +0700 Subject: [PATCH 164/352] fix(misconf): fix parsing of engine links and frameworks (#6937) --- pkg/iac/rego/metadata.go | 46 ++++++++++++++++++++++++++--------- pkg/iac/rego/metadata_test.go | 39 ++++++++++++++++++++--------- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/pkg/iac/rego/metadata.go b/pkg/iac/rego/metadata.go index dd2c1f104fcf..907b8450bdcb 100644 --- a/pkg/iac/rego/metadata.go +++ b/pkg/iac/rego/metadata.go @@ -90,15 +90,7 @@ func (sm *StaticMetadata) Update(meta map[string]any) error { if raw, ok := meta["url"]; ok { sm.References = append(sm.References, fmt.Sprintf("%s", raw)) } - if raw, ok := meta["frameworks"]; ok { - frameworks, ok := raw.(map[string][]string) - if !ok { - return fmt.Errorf("failed to parse framework metadata: not an object") - } - for fw, sections := range frameworks { - sm.Frameworks[framework.Framework(fw)] = sections - } - } + if raw, ok := meta["related_resources"]; ok { switch relatedResources := raw.(type) { case []map[string]any: @@ -112,6 +104,9 @@ func (sm *StaticMetadata) Update(meta map[string]any) error { } } + if err := sm.updateFrameworks(meta); err != nil { + return fmt.Errorf("failed to update frameworks: %w", err) + } sm.updateAliases(meta) var err error @@ -126,6 +121,28 @@ func (sm *StaticMetadata) Update(meta map[string]any) error { return nil } +func (sm *StaticMetadata) updateFrameworks(meta map[string]any) error { + if raw, ok := meta["frameworks"]; ok { + frameworks, ok := raw.(map[string]any) + if !ok { + return fmt.Errorf("frameworks metadata is not an object, got %T", raw) + } + for fw, rawIDs := range frameworks { + ids, ok := rawIDs.([]any) + if !ok { + return fmt.Errorf("framework ids is not an array, got %T", rawIDs) + } + fr := framework.Framework(fw) + for _, id := range ids { + if str, ok := id.(string); ok { + sm.Frameworks[fr] = append(sm.Frameworks[fr], str) + } + } + } + } + return nil +} + func (sm *StaticMetadata) updateAliases(meta map[string]any) { if raw, ok := meta["aliases"]; ok { if aliases, ok := raw.([]any); ok { @@ -172,8 +189,15 @@ func NewEngineMetadata(schema string, meta map[string]any) (*scan.EngineMetadata if val, ok := sMap["bad_examples"].(string); ok { em.BadExamples = []string{val} } - if val, ok := sMap["links"].(string); ok { - em.Links = []string{val} + switch links := sMap["links"].(type) { + case string: + em.Links = []string{links} + case []any: + for _, v := range links { + if str, ok := v.(string); ok { + em.Links = append(em.Links, str) + } + } } if val, ok := sMap["remediation_markdown"].(string); ok { em.RemediationMarkdown = val diff --git a/pkg/iac/rego/metadata_test.go b/pkg/iac/rego/metadata_test.go index 4ef5a7173062..6b4bb9773a92 100644 --- a/pkg/iac/rego/metadata_test.go +++ b/pkg/iac/rego/metadata_test.go @@ -46,8 +46,8 @@ func Test_UpdateStaticMetadata(t *testing.T) { "severity": "s_n", "library": true, "url": "r_n", - "frameworks": map[string][]string{ - "all": {"aa"}, + "frameworks": map[string]any{ + "all": []any{"aa"}, }, }, )) @@ -137,7 +137,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { }) } -func Test_getEngineMetadata(t *testing.T) { +func Test_NewEngineMetadata(t *testing.T) { inputSchema := map[string]any{ "terraform": map[string]any{ "good_examples": `resource "aws_cloudtrail" "good_example" { @@ -153,8 +153,11 @@ func Test_getEngineMetadata(t *testing.T) { } } }`, + + "links": "https://avd.aquasec.com/avd/183", }, - "cloud_formation": map[string]any{"good_examples": `--- + "cloud_formation": map[string]any{ + "good_examples": `--- Resources: GoodExample: Type: AWS::CloudTrail::Trail @@ -164,15 +167,19 @@ Resources: S3BucketName: "CloudtrailBucket" S3KeyPrefix: "/trailing" TrailName: "Cloudtrail"`, - }} + "links": []any{"https://avd.aquasec.com/avd/183"}, + }, + } var testCases = []struct { schema string - want string + want *scan.EngineMetadata }{ { schema: "terraform", - want: `resource "aws_cloudtrail" "good_example" { + want: &scan.EngineMetadata{ + GoodExamples: []string{ + `resource "aws_cloudtrail" "good_example" { is_multi_region_trail = true event_selector { @@ -185,9 +192,15 @@ Resources: } } }`, + }, + Links: []string{"https://avd.aquasec.com/avd/183"}, + }, }, - {schema: "cloud_formation", - want: `--- + { + schema: "cloud_formation", + want: &scan.EngineMetadata{ + GoodExamples: []string{ + `--- Resources: GoodExample: Type: AWS::CloudTrail::Trail @@ -196,14 +209,18 @@ Resources: IsMultiRegionTrail: true S3BucketName: "CloudtrailBucket" S3KeyPrefix: "/trailing" - TrailName: "Cloudtrail"`}, + TrailName: "Cloudtrail"`, + }, + Links: []string{"https://avd.aquasec.com/avd/183"}, + }, + }, } for _, tc := range testCases { t.Run(tc.schema, func(t *testing.T) { em, err := NewEngineMetadata(tc.schema, inputSchema) require.NoError(t, err) - assert.Equal(t, tc.want, em.GoodExamples[0]) + assert.Equal(t, tc.want, em) }) } } From c3192f061d7e84eaf38df8df7c879dc00b4ca137 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 18 Jun 2024 12:41:29 +0700 Subject: [PATCH 165/352] fix(misconf): handle source prefix to ignore (#6945) Signed-off-by: nikpivkin --- pkg/iac/ignore/parse.go | 4 +- pkg/iac/ignore/rule_test.go | 4 +- .../scanners/cloudformation/parser/parser.go | 2 +- pkg/iac/scanners/terraform/ignore_test.go | 57 +++++++++++++++++++ pkg/iac/scanners/terraform/parser/parser.go | 1 + 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/pkg/iac/ignore/parse.go b/pkg/iac/ignore/parse.go index 889848907548..8a7a940c6b5c 100644 --- a/pkg/iac/ignore/parse.go +++ b/pkg/iac/ignore/parse.go @@ -19,11 +19,11 @@ type RuleSectionParser interface { } // Parse parses the configuration file and returns the Rules -func Parse(src, path string, parsers ...RuleSectionParser) Rules { +func Parse(src, path, sourcePrefix string, parsers ...RuleSectionParser) Rules { var rules Rules for i, line := range strings.Split(src, "\n") { line = strings.TrimSpace(line) - rng := types.NewRange(path, i+1, i+1, "", nil) + rng := types.NewRange(path, i+1, i+1, sourcePrefix, nil) lineIgnores := parseLine(line, rng, parsers) for _, lineIgnore := range lineIgnores { rules = append(rules, lineIgnore) diff --git a/pkg/iac/ignore/rule_test.go b/pkg/iac/ignore/rule_test.go index 7cd4d382a410..619d251eb750 100644 --- a/pkg/iac/ignore/rule_test.go +++ b/pkg/iac/ignore/rule_test.go @@ -239,7 +239,7 @@ test #trivy:ignore:rule-4 for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rules := ignore.Parse(tt.src, filename) + rules := ignore.Parse(tt.src, "", filename) got := rules.Ignore(tt.args.metadata, tt.args.ids, nil) assert.Equal(t, tt.shouldIgnore, got) }) @@ -329,7 +329,7 @@ func TestRules_IgnoreWithCustomIgnorer(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rules := ignore.Parse(tt.src, filename, tt.parser) + rules := ignore.Parse(tt.src, filename, "", tt.parser) got := rules.Ignore(tt.args.metadata, tt.args.ids, tt.args.ignorers) assert.Equal(t, tt.shouldIgnore, got) }) diff --git a/pkg/iac/scanners/cloudformation/parser/parser.go b/pkg/iac/scanners/cloudformation/parser/parser.go index 65bf1440432d..5aa760e19882 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser.go +++ b/pkg/iac/scanners/cloudformation/parser/parser.go @@ -171,7 +171,7 @@ func (p *Parser) ParseFile(ctx context.Context, fsys fs.FS, path string) (fctx * if err := yaml.Unmarshal(content, fctx); err != nil { return nil, NewErrInvalidContent(path, err) } - fctx.Ignores = ignore.Parse(string(content), path) + fctx.Ignores = ignore.Parse(string(content), path, "") case JsonSourceFormat: if err := jfather.Unmarshal(content, fctx); err != nil { return nil, NewErrInvalidContent(path, err) diff --git a/pkg/iac/scanners/terraform/ignore_test.go b/pkg/iac/scanners/terraform/ignore_test.go index 3b0a83428a50..6183469d5296 100644 --- a/pkg/iac/scanners/terraform/ignore_test.go +++ b/pkg/iac/scanners/terraform/ignore_test.go @@ -1,15 +1,19 @@ package terraform import ( + "context" "fmt" "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/severity" "github.com/aquasecurity/trivy/pkg/iac/terraform" ) @@ -748,3 +752,56 @@ func Test_IgnoreInlineByAVDID(t *testing.T) { } } } + +func TestIgnoreRemoteTerraformResource(t *testing.T) { + + fsys := testutil.CreateFS(t, map[string]string{ + "main.tf": `module "bucket" { + source = "git::https://github.com/test/bucket" +}`, + ".terraform/modules/modules.json": `{ + "Modules": [ + { "Key": "", "Source": "", "Dir": "." }, + { + "Key": "bucket", + "Source": "git::https://github.com/test/bucket", + "Dir": ".terraform/modules/bucket" + } + ] +} +`, + ".terraform/modules/bucket/main.tf": ` +# trivy:ignore:test-0001 +resource "aws_s3_bucket" "test" { + bucket = "" +} +`, + }) + + check := `# METADATA +# title: Test +# custom: +# id: test-0001 +# avdid: test-0001 + +package user.test0001 + +deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "" + res := result.new("Empty bucket name!", bucket) +}` + + localScanner := New( + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithEmbeddedLibraries(true), + options.ScannerWithRegoOnly(true), + options.ScannerWithPolicyNamespaces("user"), + options.ScannerWithPolicyReader(strings.NewReader(check)), + ScannerWithDownloadsAllowed(false), + ScannerWithSkipCachedModules(true), + ) + results, err := localScanner.ScanFS(context.TODO(), fsys, ".") + require.NoError(t, err) + assert.Empty(t, results.GetFailed()) +} diff --git a/pkg/iac/scanners/terraform/parser/parser.go b/pkg/iac/scanners/terraform/parser/parser.go index 049be0e02c10..aec5ce0c31d7 100644 --- a/pkg/iac/scanners/terraform/parser/parser.go +++ b/pkg/iac/scanners/terraform/parser/parser.go @@ -301,6 +301,7 @@ func (p *Parser) readBlocks(files []sourceFile) (terraform.Blocks, ignore.Rules, fileIgnores := ignore.Parse( string(file.file.Bytes), file.path, + p.moduleSource, &ignore.StringMatchParser{ SectionKey: "ws", }, From 0af5730cbe56686417389c2fad643c1bdbb33999 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 19 Jun 2024 10:45:56 +0600 Subject: [PATCH 166/352] fix(image): parse `image.inspect.Created` field only for non-empty values (#6948) --- pkg/fanal/image/daemon/image.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/fanal/image/daemon/image.go b/pkg/fanal/image/daemon/image.go index 5d80cb93eee4..d3787cc0abc7 100644 --- a/pkg/fanal/image/daemon/image.go +++ b/pkg/fanal/image/daemon/image.go @@ -110,16 +110,25 @@ func (img *image) ConfigFile() (*v1.ConfigFile, error) { return nil, xerrors.Errorf("unable to get diff IDs: %w", err) } - created, err := time.Parse(time.RFC3339Nano, img.inspect.Created) - if err != nil { - return nil, xerrors.Errorf("failed parsing created %s: %w", img.inspect.Created, err) + var created v1.Time + // `Created` field can be empty. Skip parsing to avoid error. + // cf. https://github.com/moby/moby/blob/8e96db1c328d0467b015768e42a62c0f834970bb/api/types/types.go#L76-L77 + if img.inspect.Created != "" { + var t time.Time + t, err = time.Parse(time.RFC3339Nano, img.inspect.Created) + if err != nil { + return nil, xerrors.Errorf("failed parsing created %s: %w", img.inspect.Created, err) + } + created = v1.Time{ + Time: t, + } } return &v1.ConfigFile{ Architecture: img.inspect.Architecture, Author: img.inspect.Author, Container: img.inspect.Container, - Created: v1.Time{Time: created}, + Created: created, DockerVersion: img.inspect.DockerVersion, Config: img.imageConfig(img.inspect.Config), History: img.history, From eb6d0d9779dbe5c2df46826fcf35c3b6fd8d271f Mon Sep 17 00:00:00 2001 From: Itay Shakury Date: Wed, 19 Jun 2024 07:58:23 +0300 Subject: [PATCH 167/352] ci: correctly handle categories (#6943) --- .github/actions/trivy-triage/helpers.js | 3 ++- .github/actions/trivy-triage/helpers.test.js | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/actions/trivy-triage/helpers.js b/.github/actions/trivy-triage/helpers.js index 121d5b38ffaa..3b477dfad5a6 100644 --- a/.github/actions/trivy-triage/helpers.js +++ b/.github/actions/trivy-triage/helpers.js @@ -5,7 +5,8 @@ module.exports = { const category = discussion.category.name; const body = discussion.body; if (category !== "Ideas") { - consolt.log("skipping discussion with category ${category} and body ${body}"); + console.log(`skipping discussion with category ${category} and body ${body}`); + return []; } const scannerPattern = /### Scanner\n\n(.+)/; const scannerFound = body.match(scannerPattern); diff --git a/.github/actions/trivy-triage/helpers.test.js b/.github/actions/trivy-triage/helpers.test.js index 3ef2ef810124..7db708bcfd3f 100644 --- a/.github/actions/trivy-triage/helpers.test.js +++ b/.github/actions/trivy-triage/helpers.test.js @@ -73,5 +73,15 @@ describe('trivy-triage', async function() { assert(!labels.includes('FilesystemLabel')); assert(!labels.includes('MisconfigurationLabel')); }); + it('process only relevant categories', async function() { + const discussion = { + body: 'hello world', + category: { + name: 'Announcements' + } + }; + const labels = detectDiscussionLabels(discussion, configDiscussionLabels); + assert(labels.length === 0); + }); }); }); From 38b35dd3c804027e7a6e6a9d3c87b7ac333896c5 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:48:23 +0600 Subject: [PATCH 168/352] fix(c): don't skip conan files from `file-patterns` and scan `.conan2` cache dir (#6949) --- docs/docs/coverage/language/c.md | 5 +- pkg/fanal/analyzer/language/c/conan/conan.go | 50 +- .../analyzer/language/c/conan/conan_test.go | 105 ++- .../p/opens464b5c427ce9d/e/conanfile.py | 675 ++++++++++++++++++ .../p/zlib41bd3946e7341/e/conanfile.py | 110 +++ .../c/conan/testdata/happy_v2/release.lock | 10 + 6 files changed, 934 insertions(+), 21 deletions(-) create mode 100644 pkg/fanal/analyzer/language/c/conan/testdata/cacheDir_v2/p/opens464b5c427ce9d/e/conanfile.py create mode 100644 pkg/fanal/analyzer/language/c/conan/testdata/cacheDir_v2/p/zlib41bd3946e7341/e/conanfile.py create mode 100644 pkg/fanal/analyzer/language/c/conan/testdata/happy_v2/release.lock diff --git a/docs/docs/coverage/language/c.md b/docs/docs/coverage/language/c.md index 276340a806bd..b156ccc85510 100644 --- a/docs/docs/coverage/language/c.md +++ b/docs/docs/coverage/language/c.md @@ -23,10 +23,11 @@ In order to detect dependencies, Trivy searches for `conan.lock`[^1]. ### Licenses The Conan lock file doesn't contain any license information. -To obtain licenses we parse the `conanfile.py` files from the [conan cache directory][conan-cache-dir]. +To obtain licenses we parse the `conanfile.py` files from the [conan v1 cache directory][conan-v1-cache-dir] and [conan v2 cache directory][conan-v2-cache-dir]. To correctly detection licenses, ensure that the cache directory contains all dependencies used. -[conan-cache-dir]: https://docs.conan.io/1/mastering/custom_cache.html +[conan-v1-cache-dir]: https://docs.conan.io/1/mastering/custom_cache.html +[conan-v2-cache-dir]: https://docs.conan.io/2/reference/environment.html#conan-home [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies [^1]: The local cache should contain the dependencies used. See [licenses](#licenses). diff --git a/pkg/fanal/analyzer/language/c/conan/conan.go b/pkg/fanal/analyzer/language/c/conan/conan.go index 50252c2bb603..a32591dae7fe 100644 --- a/pkg/fanal/analyzer/language/c/conan/conan.go +++ b/pkg/fanal/analyzer/language/c/conan/conan.go @@ -11,6 +11,7 @@ import ( "sort" "strings" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/c/conan" @@ -44,7 +45,8 @@ func newConanLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, er func (a conanLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { required := func(filePath string, d fs.DirEntry) bool { - return a.Required(filePath, nil) + // we need all file got from `a.Required` function (conan.lock files) and from file-patterns. + return true } licenses, err := licensesFromCache() @@ -85,19 +87,13 @@ func (a conanLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAna } func licensesFromCache() (map[string]string, error) { - required := func(filePath string, d fs.DirEntry) bool { - return filepath.Base(filePath) == "conanfile.py" - } - - // cf. https://docs.conan.io/1/mastering/custom_cache.html - cacheDir := os.Getenv("CONAN_USER_HOME") - if cacheDir == "" { - cacheDir, _ = os.UserHomeDir() + cacheDir, err := detectCacheDir() + if err != nil { + return nil, err } - cacheDir = path.Join(cacheDir, ".conan", "data") - if !fsutils.DirExists(cacheDir) { - return nil, xerrors.Errorf("the Conan cache directory (%s) was not found.", cacheDir) + required := func(filePath string, d fs.DirEntry) bool { + return filepath.Base(filePath) == "conanfile.py" } licenses := make(map[string]string) @@ -154,6 +150,36 @@ func detectAttribute(attributeName, line string) string { return "" } +func detectCacheDir() (string, error) { + home, _ := os.UserHomeDir() + dirs := []string{ + // conan v2 uses `CONAN_HOME` env + // cf. https://docs.conan.io/2/reference/environment.html#conan-home + // `.conan2` dir is omitted for this env + lo.Ternary(os.Getenv("CONAN_HOME") != "", path.Join(os.Getenv("CONAN_HOME"), "p"), ""), + // conan v1 uses `CONAN_USER_HOME` env + // cf. https://docs.conan.io/en/1.64/reference/env_vars.html#conan-user-home + // `.conan` dir is used for this env + lo.Ternary(os.Getenv("CONAN_USER_HOME") != "", path.Join(os.Getenv("CONAN_USER_HOME"), ".conan", "data"), ""), + // `/.conan2` is default directory for conan v2 + // cf. https://docs.conan.io/2/reference/environment.html#conan-home + path.Join(home, ".conan2", "p"), + // `/.conan` is default directory for conan v1 + // cf. https://docs.conan.io/1/mastering/custom_cache.html + path.Join(home, ".conan", "data"), + } + + for _, dir := range dirs { + if dir != "" { + if fsutils.DirExists(dir) { + return dir, nil + } + } + } + + return "", xerrors.Errorf("the Conan cache directory was not found.") +} + func (a conanLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { // Lock file name can be anything // cf. https://docs.conan.io/1/versioning/lockfiles/introduction.html#locking-dependencies diff --git a/pkg/fanal/analyzer/language/c/conan/conan_test.go b/pkg/fanal/analyzer/language/c/conan/conan_test.go index bfc2054ebe66..7f33af4df669 100644 --- a/pkg/fanal/analyzer/language/c/conan/conan_test.go +++ b/pkg/fanal/analyzer/language/c/conan/conan_test.go @@ -16,11 +16,11 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) { tests := []struct { name string dir string - cacheDir string + cacheDir map[string]string want *analyzer.AnalysisResult }{ { - name: "happy path", + name: "happy path V1", dir: "testdata/happy", want: &analyzer.AnalysisResult{ Applications: []types.Application{ @@ -62,9 +62,11 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) { }, }, { - name: "happy path with cache dir", - dir: "testdata/happy", - cacheDir: "testdata/cacheDir", + name: "happy path V1 with cache dir", + dir: "testdata/happy", + cacheDir: map[string]string{ + "CONAN_USER_HOME": "testdata/cacheDir", + }, want: &analyzer.AnalysisResult{ Applications: []types.Application{ { @@ -110,6 +112,92 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) { }, }, }, + { + name: "happy path V2", + dir: "testdata/happy_v2", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Conan, + FilePath: "release.lock", + Packages: types.Packages{ + { + ID: "openssl/3.2.2", + Name: "openssl", + Version: "3.2.2", + Relationship: types.RelationshipUnknown, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + { + ID: "zlib/1.3.1", + Name: "zlib", + Version: "1.3.1", + Relationship: types.RelationshipUnknown, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "happy path V2 with cache dir", + dir: "testdata/happy_v2", + cacheDir: map[string]string{ + "CONAN_HOME": "testdata/cacheDir_v2", + }, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Conan, + FilePath: "release.lock", + Packages: types.Packages{ + + { + ID: "openssl/3.2.2", + Name: "openssl", + Version: "3.2.2", + Relationship: types.RelationshipUnknown, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + Licenses: []string{ + "Apache-2.0", + }, + }, + { + ID: "zlib/1.3.1", + Name: "zlib", + Version: "1.3.1", + Relationship: types.RelationshipUnknown, + Locations: []types.Location{ + { + StartLine: 4, + EndLine: 4, + }, + }, + Licenses: []string{ + "Zlib", + }, + }, + }, + }, + }, + }, + }, { name: "empty file", dir: "testdata/empty", @@ -119,8 +207,11 @@ func Test_conanLockAnalyzer_Analyze(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.cacheDir != "" { - t.Setenv("CONAN_USER_HOME", tt.cacheDir) + if len(tt.cacheDir) > 0 { + for env, path := range tt.cacheDir { + t.Setenv(env, path) + break + } } a, err := newConanLockAnalyzer(analyzer.AnalyzerOptions{}) require.NoError(t, err) diff --git a/pkg/fanal/analyzer/language/c/conan/testdata/cacheDir_v2/p/opens464b5c427ce9d/e/conanfile.py b/pkg/fanal/analyzer/language/c/conan/testdata/cacheDir_v2/p/opens464b5c427ce9d/e/conanfile.py new file mode 100644 index 000000000000..ba393a6668fe --- /dev/null +++ b/pkg/fanal/analyzer/language/c/conan/testdata/cacheDir_v2/p/opens464b5c427ce9d/e/conanfile.py @@ -0,0 +1,675 @@ +from conan import ConanFile +from conan.errors import ConanInvalidConfiguration +from conan.tools.apple import fix_apple_shared_install_name, is_apple_os, XCRun +from conan.tools.build import build_jobs +from conan.tools.files import chdir, copy, get, rename, replace_in_file, rmdir, save +from conan.tools.gnu import AutotoolsToolchain +from conan.tools.layout import basic_layout +from conan.tools.microsoft import is_msvc, msvc_runtime_flag, unix_path + +import fnmatch +import os +import textwrap + +required_conan_version = ">=1.57.0" + + +class OpenSSLConan(ConanFile): + name="openssl" + settings = "os", "arch", "compiler", "build_type" + url = "https://github.com/conan-io/conan-center-index" + homepage = "https://github.com/openssl/openssl" + license="Apache-2.0" + topics = ("ssl", "tls", "encryption", "security") + description = "A toolkit for the Transport Layer Security (TLS) and Secure Sockets Layer (SSL) protocols" + options = { + "shared": [True, False], + "fPIC": [True, False], + "enable_weak_ssl_ciphers": [True, False], + "386": [True, False], + "capieng_dialog": [True, False], + "enable_capieng": [True, False], + "no_aria": [True, False], + "no_asm": [True, False], + "no_async": [True, False], + "no_blake2": [True, False], + "no_bf": [True, False], + "no_camellia": [True, False], + "no_chacha": [True, False], + "no_cms": [True, False], + "no_comp": [True, False], + "no_ct": [True, False], + "no_cast": [True, False], + "no_deprecated": [True, False], + "no_des": [True, False], + "no_dgram": [True, False], + "no_dh": [True, False], + "no_dsa": [True, False], + "no_dso": [True, False], + "no_ec": [True, False], + "no_ecdh": [True, False], + "no_ecdsa": [True, False], + "no_engine": [True, False], + "no_filenames": [True, False], + "no_fips": [True, False], + "no_gost": [True, False], + "no_idea": [True, False], + "no_legacy": [True, False], + "no_md2": [True, False], + "no_md4": [True, False], + "no_mdc2": [True, False], + "no_module": [True, False], + "no_ocsp": [True, False], + "no_pinshared": [True, False], + "no_rc2": [True, False], + "no_rc4": [True, False], + "no_rc5": [True, False], + "no_rfc3779": [True, False], + "no_rmd160": [True, False], + "no_sm2": [True, False], + "no_sm3": [True, False], + "no_sm4": [True, False], + "no_srp": [True, False], + "no_srtp": [True, False], + "no_sse2": [True, False], + "no_ssl": [True, False], + "no_stdio": [True, False], + "no_seed": [True, False], + "no_sock": [True, False], + "no_ssl3": [True, False], + "no_threads": [True, False], + "no_tls1": [True, False], + "no_ts": [True, False], + "no_whirlpool": [True, False], + "no_zlib": [True, False], + "openssldir": [None, "ANY"], + } + default_options = {key: False for key in options.keys()} + default_options["fPIC"] = True + default_options["no_md2"] = True + default_options["openssldir"] = None + + @property + def _settings_build(self): + return getattr(self, "settings_build", self.settings) + + def config_options(self): + if self.settings.os != "Windows": + self.options.rm_safe("capieng_dialog") + self.options.rm_safe("enable_capieng") + else: + self.options.rm_safe("fPIC") + + if self.settings.os == "Emscripten": + self.options.no_asm = True + self.options.no_threads = True + self.options.no_stdio = True + + def configure(self): + if self.options.shared: + self.options.rm_safe("fPIC") + self.settings.rm_safe("compiler.libcxx") + self.settings.rm_safe("compiler.cppstd") + + def requirements(self): + if not self.options.no_zlib: + self.requires("zlib/1.2.13") + + def build_requirements(self): + if self._settings_build.os == "Windows": + if not self.options.no_asm: + self.tool_requires("nasm/2.15.05") + if self._use_nmake: + self.tool_requires("strawberryperl/5.32.1.1") + else: + self.win_bash = True + if not self.conf.get("tools.microsoft.bash:path", check_type=str): + self.tool_requires("msys2/cci.latest") + + def validate(self): + if self.settings.os == "Emscripten": + if not all((self.options.no_asm, self.options.no_threads, self.options.no_stdio)): + raise ConanInvalidConfiguration("os=Emscripten requires openssl:{no_asm,no_threads,no_stdio}=True") + + if self.settings.os == "iOS" and self.options.shared: + raise ConanInvalidConfiguration("OpenSSL 3 does not support building shared libraries for iOS") + + def layout(self): + basic_layout(self, src_folder="src") + + @property + def _is_clangcl(self): + return self.settings.compiler == "clang" and self.settings.os == "Windows" + + @property + def _is_mingw(self): + return self.settings.os == "Windows" and self.settings.compiler == "gcc" + + @property + def _use_nmake(self): + return self._is_clangcl or is_msvc(self) + + def source(self): + get(self, **self.conan_data["sources"][self.version], + destination=self.source_folder, strip_root=True) + + @property + def _target(self): + target = f"conan-{self.settings.build_type}-{self.settings.os}-{self.settings.arch}-{self.settings.compiler}-{self.settings.compiler.version}" + if self._use_nmake: + target = f"VC-{target}" # VC- prefix is important as it's checked by Configure + if self._is_mingw: + target = f"mingw-{target}" + return target + + @property + def _perlasm_scheme(self): + # right now, we need to tweak this for iOS & Android only, as they inherit from generic targets + if self.settings.os in ("iOS", "watchOS", "tvOS"): + return { + "armv7": "ios32", + "armv7s": "ios32", + "armv8": "ios64", + "armv8_32": "ios64", + "armv8.3": "ios64", + "armv7k": "ios32", + }.get(str(self.settings.arch), None) + elif self.settings.os == "Android": + return { + "armv7": "void", + "armv8": "linux64", + "mips": "o32", + "mips64": "64", + "x86": "android", + "x86_64": "elf", + }.get(str(self.settings.arch), None) + return None + + @property + def _asm_target(self): + if self.settings.os in ("Android", "iOS", "watchOS", "tvOS"): + return { + "x86": "x86_asm" if self.settings.os == "Android" else None, + "x86_64": "x86_64_asm" if self.settings.os == "Android" else None, + "armv5el": "armv4_asm", + "armv5hf": "armv4_asm", + "armv6": "armv4_asm", + "armv7": "armv4_asm", + "armv7hf": "armv4_asm", + "armv7s": "armv4_asm", + "armv7k": "armv4_asm", + "armv8": "aarch64_asm", + "armv8_32": "aarch64_asm", + "armv8.3": "aarch64_asm", + "mips": "mips32_asm", + "mips64": "mips64_asm", + "sparc": "sparcv8_asm", + "sparcv9": "sparcv9_asm", + "ia64": "ia64_asm", + "ppc32be": "ppc32_asm", + "ppc32": "ppc32_asm", + "ppc64le": "ppc64_asm", + "ppc64": "ppc64_asm", + "s390": "s390x_asm", + "s390x": "s390x_asm" + }.get(str(self.settings.os), None) + + @property + def _targets(self): + is_cygwin = self.settings.get_safe("os.subsystem") == "cygwin" + return { + "Linux-x86-clang": "linux-x86-clang", + "Linux-x86_64-clang": "linux-x86_64-clang", + "Linux-x86-*": "linux-x86", + "Linux-x86_64-*": "linux-x86_64", + "Linux-armv4-*": "linux-armv4", + "Linux-armv4i-*": "linux-armv4", + "Linux-armv5el-*": "linux-armv4", + "Linux-armv5hf-*": "linux-armv4", + "Linux-armv6-*": "linux-armv4", + "Linux-armv7-*": "linux-armv4", + "Linux-armv7hf-*": "linux-armv4", + "Linux-armv7s-*": "linux-armv4", + "Linux-armv7k-*": "linux-armv4", + "Linux-armv8-*": "linux-aarch64", + "Linux-armv8.3-*": "linux-aarch64", + "Linux-armv8-32-*": "linux-arm64ilp32", + "Linux-mips-*": "linux-mips32", + "Linux-mips64-*": "linux-mips64", + "Linux-ppc32-*": "linux-ppc32", + "Linux-ppc32le-*": "linux-pcc32", + "Linux-ppc32be-*": "linux-ppc32", + "Linux-ppc64-*": "linux-ppc64", + "Linux-ppc64le-*": "linux-ppc64le", + "Linux-pcc64be-*": "linux-pcc64", + "Linux-s390x-*": "linux64-s390x", + "Linux-e2k-*": "linux-generic64", + "Linux-sparc-*": "linux-sparcv8", + "Linux-sparcv9-*": "linux64-sparcv9", + "Linux-*-*": "linux-generic32", + "Macos-x86-*": "darwin-i386-cc", + "Macos-x86_64-*": "darwin64-x86_64-cc", + "Macos-ppc32-*": "darwin-ppc-cc", + "Macos-ppc32be-*": "darwin-ppc-cc", + "Macos-ppc64-*": "darwin64-ppc-cc", + "Macos-ppc64be-*": "darwin64-ppc-cc", + "Macos-armv8-*": "darwin64-arm64-cc", + "Macos-*-*": "darwin-common", + "iOS-x86_64-*": "darwin64-x86_64-cc", + "iOS-*-*": "iphoneos-cross", + "watchOS-*-*": "iphoneos-cross", + "tvOS-*-*": "iphoneos-cross", + # Android targets are very broken, see https://github.com/openssl/openssl/issues/7398 + "Android-armv7-*": "linux-generic32", + "Android-armv7hf-*": "linux-generic32", + "Android-armv8-*": "linux-generic64", + "Android-x86-*": "linux-x86-clang", + "Android-x86_64-*": "linux-x86_64-clang", + "Android-mips-*": "linux-generic32", + "Android-mips64-*": "linux-generic64", + "Android-*-*": "linux-generic32", + "Windows-x86-gcc": "Cygwin-x86" if is_cygwin else "mingw", + "Windows-x86_64-gcc": "Cygwin-x86_64" if is_cygwin else "mingw64", + "Windows-*-gcc": "Cygwin-common" if is_cygwin else "mingw-common", + "Windows-ia64-Visual Studio": "VC-WIN64I", # Itanium + "Windows-x86-Visual Studio": "VC-WIN32", + "Windows-x86_64-Visual Studio": "VC-WIN64A", + "Windows-armv7-Visual Studio": "VC-WIN32-ARM", + "Windows-armv8-Visual Studio": "VC-WIN64-ARM", + "Windows-*-Visual Studio": "VC-noCE-common", + "Windows-ia64-clang": "VC-WIN64I", # Itanium + "Windows-x86-clang": "VC-WIN32", + "Windows-x86_64-clang": "VC-WIN64A", + "Windows-armv7-clang": "VC-WIN32-ARM", + "Windows-armv8-clang": "VC-WIN64-ARM", + "Windows-*-clang": "VC-noCE-common", + "WindowsStore-x86-*": "VC-WIN32-UWP", + "WindowsStore-x86_64-*": "VC-WIN64A-UWP", + "WindowsStore-armv7-*": "VC-WIN32-ARM-UWP", + "WindowsStore-armv8-*": "VC-WIN64-ARM-UWP", + "WindowsStore-*-*": "VC-WIN32-ONECORE", + "WindowsCE-*-*": "VC-CE", + "SunOS-x86-gcc": "solaris-x86-gcc", + "SunOS-x86_64-gcc": "solaris64-x86_64-gcc", + "SunOS-sparc-gcc": "solaris-sparcv8-gcc", + "SunOS-sparcv9-gcc": "solaris64-sparcv9-gcc", + "SunOS-x86-suncc": "solaris-x86-cc", + "SunOS-x86_64-suncc": "solaris64-x86_64-cc", + "SunOS-sparc-suncc": "solaris-sparcv8-cc", + "SunOS-sparcv9-suncc": "solaris64-sparcv9-cc", + "SunOS-*-*": "solaris-common", + "*BSD-x86-*": "BSD-x86", + "*BSD-x86_64-*": "BSD-x86_64", + "*BSD-ia64-*": "BSD-ia64", + "*BSD-sparc-*": "BSD-sparcv8", + "*BSD-sparcv9-*": "BSD-sparcv9", + "*BSD-armv8-*": "BSD-generic64", + "*BSD-mips64-*": "BSD-generic64", + "*BSD-ppc64-*": "BSD-generic64", + "*BSD-ppc64le-*": "BSD-generic64", + "*BSD-ppc64be-*": "BSD-generic64", + "AIX-ppc32-gcc": "aix-gcc", + "AIX-ppc64-gcc": "aix64-gcc", + "AIX-pcc32-*": "aix-cc", + "AIX-ppc64-*": "aix64-cc", + "AIX-*-*": "aix-common", + "*BSD-*-*": "BSD-generic32", + "Emscripten-*-*": "cc", + "Neutrino-*-*": "BASE_unix", + } + + @property + def _ancestor_target(self): + if "CONAN_OPENSSL_CONFIGURATION" in os.environ: + return os.environ["CONAN_OPENSSL_CONFIGURATION"] + compiler = "Visual Studio" if self.settings.compiler == "msvc" else self.settings.compiler + query = f"{self.settings.os}-{self.settings.arch}-{compiler}" + ancestor = next((self._targets[i] for i in self._targets if fnmatch.fnmatch(query, i)), None) + if not ancestor: + raise ConanInvalidConfiguration( + f"Unsupported configuration ({self.settings.os}/{self.settings.arch}/{self.settings.compiler}).\n" + f"Please open an issue at {self.url}.\n" + f"Alternatively, set the CONAN_OPENSSL_CONFIGURATION environment variable into your conan profile." + ) + return ancestor + + def _get_default_openssl_dir(self): + if self.settings.os == "Linux": + return "/etc/ssl" + return os.path.join(self.package_folder, "res") + + @property + def _configure_args(self): + openssldir = self.options.openssldir or self._get_default_openssl_dir() + openssldir = unix_path(self, openssldir) if self.win_bash else openssldir + args = [ + '"%s"' % (self._target), + "shared" if self.options.shared else "no-shared", + "--prefix=/", + "--libdir=lib", + "--openssldir=\"%s\"" % openssldir, + "no-unit-test", + "no-threads" if self.options.no_threads else "threads", + "PERL=%s" % self._perl, + "no-tests", + "--debug" if self.settings.build_type == "Debug" else "--release", + ] + + if self.settings.os == "Android": + args.append(" -D__ANDROID_API__=%s" % str(self.settings.os.api_level)) # see NOTES.ANDROID + if self.settings.os == "Emscripten": + args.append("-D__STDC_NO_ATOMICS__=1") + if self.settings.os == "Windows": + if self.options.enable_capieng: + args.append("enable-capieng") + if self.options.capieng_dialog: + args.append("-DOPENSSL_CAPIENG_DIALOG=1") + else: + args.append("-fPIC" if self.options.get_safe("fPIC", True) else "no-pic") + + args.append("no-fips" if self.options.get_safe("no_fips", True) else "enable-fips") + args.append("no-md2" if self.options.get_safe("no_md2", True) else "enable-md2") + + if self.settings.os == "Neutrino": + args.append("no-asm -lsocket -latomic") + + if not self.options.no_zlib: + zlib_info = self.dependencies["zlib"].cpp_info.aggregated_components() + include_path = zlib_info.includedirs[0] + if self.settings.os == "Windows": + lib_path = "%s/%s.lib" % (zlib_info.libdirs[0], zlib_info.libs[0]) + else: + # Just path, linux will find the right file + lib_path = zlib_info.libdirs[0] + if self._settings_build.os == "Windows": + # clang-cl doesn't like backslashes in #define CFLAGS (builldinf.h -> cversion.c) + include_path = include_path.replace("\\", "/") + lib_path = lib_path.replace("\\", "/") + + if self.dependencies["zlib"].options.shared: + args.append("zlib-dynamic") + else: + args.append("zlib") + + args.extend([ + '--with-zlib-include="%s"' % include_path, + '--with-zlib-lib="%s"' % lib_path + ]) + + for option_name in self.default_options.keys(): + if self.options.get_safe(option_name, False) and option_name not in ("shared", "fPIC", "openssldir", "capieng_dialog", "enable_capieng", "zlib", "no_fips", "no_md2"): + self.output.info(f"Activated option: {option_name}") + args.append(option_name.replace("_", "-")) + return args + + def generate(self): + tc = AutotoolsToolchain(self) + env = tc.environment() + env.define_path("PERL", self._perl) + if self.settings.compiler == "apple-clang": + xcrun = XCRun(self) + env.define_path("CROSS_SDK", os.path.basename(xcrun.sdk_path)) + env.define_path("CROSS_TOP", os.path.dirname(os.path.dirname(xcrun.sdk_path))) + + self._create_targets(tc.cflags, tc.cxxflags, tc.defines, tc.ldflags) + tc.generate(env) + + def _create_targets(self, cflags, cxxflags, defines, ldflags): + config_template = textwrap.dedent("""\ + {targets} = ( + "{target}" => {{ + inherit_from => {ancestor}, + cflags => add("{cflags}"), + cxxflags => add("{cxxflags}"), + {defines} + lflags => add("{lflags}"), + {shared_target} + {shared_cflag} + {shared_extension} + {perlasm_scheme} + }}, + ); + """) + + perlasm_scheme = "" + if self._perlasm_scheme: + perlasm_scheme = 'perlasm_scheme => "%s",' % self._perlasm_scheme + + defines = " ".join(defines) + defines = 'defines => add("%s"),' % defines if defines else "" + targets = "my %targets" + includes = "" + if self.settings.os == "Windows": + includes = includes.replace("\\", "/") # OpenSSL doesn't like backslashes + + if self._asm_target: + ancestor = '[ "%s", asm("%s") ]' % (self._ancestor_target, self._asm_target) + else: + ancestor = '[ "%s" ]' % self._ancestor_target + shared_cflag = "" + shared_extension = "" + shared_target = "" + if self.settings.os == "Neutrino": + if self.options.shared: + shared_extension = 'shared_extension => ".so.\$(SHLIB_VERSION_NUMBER)",' + shared_target = 'shared_target => "gnu-shared",' + if self.options.get_safe("fPIC", True): + shared_cflag = 'shared_cflag => "-fPIC",' + + if self.settings.os in ["iOS", "tvOS", "watchOS"] and self.conf.get("tools.apple:enable_bitcode", check_type=bool): + cflags.append("-fembed-bitcode") + cxxflags.append("-fembed-bitcode") + + config = config_template.format( + targets=targets, + target=self._target, + ancestor=ancestor, + cflags=" ".join(cflags), + cxxflags=" ".join(cxxflags), + defines=defines, + perlasm_scheme=perlasm_scheme, + shared_target=shared_target, + shared_extension=shared_extension, + shared_cflag=shared_cflag, + lflags=" ".join(ldflags) + ) + self.output.info("using target: %s -> %s" % (self._target, self._ancestor_target)) + self.output.info(config) + + save(self, os.path.join(self.source_folder, "Configurations", "20-conan.conf"), config) + + def _run_make(self, targets=None, parallel=True, install=False): + command = [self._make_program] + if install: + command.append(f"DESTDIR={self.package_folder}") + if targets: + command.extend(targets) + if not self._use_nmake: + command.append(("-j%s" % build_jobs(self)) if parallel else "-j1") + self.run(" ".join(command), env="conanbuild") + + @property + def _perl(self): + if self._use_nmake: + return self.dependencies.build["strawberryperl"].conf_info.get("user.strawberryperl:perl", check_type=str) + return "perl" + + def _make(self): + with chdir(self, self.source_folder): + # workaround for clang-cl not producing .pdb files + if self._is_clangcl: + save(self, "ossl_static.pdb", "") + args = " ".join(self._configure_args) + + if self._use_nmake: + self._replace_runtime_in_file(os.path.join("Configurations", "10-main.conf")) + + self.run("{perl} ./Configure {args}".format(perl=self._perl, args=args), env="conanbuild") + if self._use_nmake: + # When `--prefix=/`, the scripts derive `\` without escaping, which + # causes issues on Windows + replace_in_file(self, "Makefile", "INSTALLTOP_dir=\\", "INSTALLTOP_dir=\\\\") + self._run_make() + + def _make_install(self): + with chdir(self, self.source_folder): + self._run_make(targets=["install_sw"], parallel=False, install=True) + + def build(self): + self._make() + self.run(f"{self._perl} {self.source_folder}/configdata.pm --dump") + + @property + def _make_program(self): + return "nmake" if self._use_nmake else "make" + + def _replace_runtime_in_file(self, filename): + runtime = msvc_runtime_flag(self) + for e in ["MDd", "MTd", "MD", "MT"]: + replace_in_file(self, filename, f"/{e} ", f"/{runtime} ", strict=False) + replace_in_file(self, filename, f"/{e}\"", f"/{runtime}\"", strict=False) + + def package(self): + copy(self, "*LICENSE*", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses")) + self._make_install() + if is_apple_os(self): + fix_apple_shared_install_name(self) + + for root, _, files in os.walk(self.package_folder): + for filename in files: + if fnmatch.fnmatch(filename, "*.pdb"): + os.unlink(os.path.join(self.package_folder, root, filename)) + if self._use_nmake: + if self.settings.build_type == "Debug": + with chdir(self, os.path.join(self.package_folder, "lib")): + rename(self, "libssl.lib", "libssld.lib") + rename(self, "libcrypto.lib", "libcryptod.lib") + + if self.options.shared: + libdir = os.path.join(self.package_folder, "lib") + for file in os.listdir(libdir): + if self._is_mingw and file.endswith(".dll.a"): + continue + if file.endswith(".a"): + os.unlink(os.path.join(libdir, file)) + + if not self.options.no_fips: + provdir = os.path.join(self.source_folder, "providers") + modules_dir = os.path.join(self.package_folder, "lib", "ossl-modules") + if self.settings.os == "Macos": + copy(self, "fips.dylib", src=provdir, dst=modules_dir) + elif self.settings.os == "Windows": + copy(self, "fips.dll", src=provdir, dst=modules_dir) + else: + copy(self, "fips.so", src=provdir, dst=modules_dir) + + rmdir(self, os.path.join(self.package_folder, "lib", "pkgconfig")) + + self._create_cmake_module_variables( + os.path.join(self.package_folder, self._module_file_rel_path) + ) + + def _create_cmake_module_variables(self, module_file): + content = textwrap.dedent("""\ + set(OPENSSL_FOUND TRUE) + if(DEFINED OpenSSL_INCLUDE_DIR) + set(OPENSSL_INCLUDE_DIR ${OpenSSL_INCLUDE_DIR}) + endif() + if(DEFINED OpenSSL_Crypto_LIBS) + set(OPENSSL_CRYPTO_LIBRARY ${OpenSSL_Crypto_LIBS}) + set(OPENSSL_CRYPTO_LIBRARIES ${OpenSSL_Crypto_LIBS} + ${OpenSSL_Crypto_DEPENDENCIES} + ${OpenSSL_Crypto_FRAMEWORKS} + ${OpenSSL_Crypto_SYSTEM_LIBS}) + elseif(DEFINED openssl_OpenSSL_Crypto_LIBS_%(config)s) + set(OPENSSL_CRYPTO_LIBRARY ${openssl_OpenSSL_Crypto_LIBS_%(config)s}) + set(OPENSSL_CRYPTO_LIBRARIES ${openssl_OpenSSL_Crypto_LIBS_%(config)s} + ${openssl_OpenSSL_Crypto_DEPENDENCIES_%(config)s} + ${openssl_OpenSSL_Crypto_FRAMEWORKS_%(config)s} + ${openssl_OpenSSL_Crypto_SYSTEM_LIBS_%(config)s}) + endif() + if(DEFINED OpenSSL_SSL_LIBS) + set(OPENSSL_SSL_LIBRARY ${OpenSSL_SSL_LIBS}) + set(OPENSSL_SSL_LIBRARIES ${OpenSSL_SSL_LIBS} + ${OpenSSL_SSL_DEPENDENCIES} + ${OpenSSL_SSL_FRAMEWORKS} + ${OpenSSL_SSL_SYSTEM_LIBS}) + elseif(DEFINED openssl_OpenSSL_SSL_LIBS_%(config)s) + set(OPENSSL_SSL_LIBRARY ${openssl_OpenSSL_SSL_LIBS_%(config)s}) + set(OPENSSL_SSL_LIBRARIES ${openssl_OpenSSL_SSL_LIBS_%(config)s} + ${openssl_OpenSSL_SSL_DEPENDENCIES_%(config)s} + ${openssl_OpenSSL_SSL_FRAMEWORKS_%(config)s} + ${openssl_OpenSSL_SSL_SYSTEM_LIBS_%(config)s}) + endif() + if(DEFINED OpenSSL_LIBRARIES) + set(OPENSSL_LIBRARIES ${OpenSSL_LIBRARIES}) + endif() + if(DEFINED OpenSSL_VERSION) + set(OPENSSL_VERSION ${OpenSSL_VERSION}) + endif() + """% {"config":str(self.settings.build_type).upper()}) + save(self, module_file, content) + + @property + def _module_subfolder(self): + return os.path.join("lib", "cmake") + + @property + def _module_file_rel_path(self): + return os.path.join(self._module_subfolder, + "conan-official-{}-variables.cmake".format(self.name)) + + def package_info(self): + self.cpp_info.set_property("cmake_file_name", "OpenSSL") + self.cpp_info.set_property("cmake_find_mode", "both") + self.cpp_info.set_property("pkg_config_name", "openssl") + self.cpp_info.set_property("cmake_build_modules", [self._module_file_rel_path]) + self.cpp_info.names["cmake_find_package"] = "OpenSSL" + self.cpp_info.names["cmake_find_package_multi"] = "OpenSSL" + self.cpp_info.components["ssl"].builddirs.append(self._module_subfolder) + self.cpp_info.components["ssl"].build_modules["cmake_find_package"] = [self._module_file_rel_path] + self.cpp_info.components["ssl"].set_property("cmake_build_modules", [self._module_file_rel_path]) + self.cpp_info.components["crypto"].builddirs.append(self._module_subfolder) + self.cpp_info.components["crypto"].build_modules["cmake_find_package"] = [self._module_file_rel_path] + self.cpp_info.components["crypto"].set_property("cmake_build_modules", [self._module_file_rel_path]) + + if self._use_nmake: + libsuffix = "d" if self.settings.build_type == "Debug" else "" + self.cpp_info.components["ssl"].libs = ["libssl" + libsuffix] + self.cpp_info.components["crypto"].libs = ["libcrypto" + libsuffix] + else: + self.cpp_info.components["ssl"].libs = ["ssl"] + self.cpp_info.components["crypto"].libs = ["crypto"] + + self.cpp_info.components["ssl"].requires = ["crypto"] + + if not self.options.no_zlib: + self.cpp_info.components["crypto"].requires.append("zlib::zlib") + + if self.settings.os == "Windows": + self.cpp_info.components["crypto"].system_libs.extend(["crypt32", "ws2_32", "advapi32", "user32", "bcrypt"]) + elif self.settings.os == "Linux": + self.cpp_info.components["crypto"].system_libs.extend(["dl", "rt"]) + self.cpp_info.components["ssl"].system_libs.append("dl") + if not self.options.no_threads: + self.cpp_info.components["crypto"].system_libs.append("pthread") + self.cpp_info.components["ssl"].system_libs.append("pthread") + elif self.settings.os == "Neutrino": + self.cpp_info.components["crypto"].system_libs.append("atomic") + self.cpp_info.components["ssl"].system_libs.append("atomic") + + self.cpp_info.components["crypto"].set_property("cmake_target_name", "OpenSSL::Crypto") + self.cpp_info.components["crypto"].set_property("pkg_config_name", "libcrypto") + self.cpp_info.components["ssl"].set_property("cmake_target_name", "OpenSSL::SSL") + self.cpp_info.components["ssl"].set_property("pkg_config_name", "libssl") + self.cpp_info.components["crypto"].names["cmake_find_package"] = "Crypto" + self.cpp_info.components["crypto"].names["cmake_find_package_multi"] = "Crypto" + self.cpp_info.components["ssl"].names["cmake_find_package"] = "SSL" + self.cpp_info.components["ssl"].names["cmake_find_package_multi"] = "SSL" + + openssl_modules_dir = os.path.join(self.package_folder, "lib", "ossl-modules") + self.runenv_info.define_path("OPENSSL_MODULES", openssl_modules_dir) + + # For legacy 1.x downstream consumers, remove once recipe is 2.0 only: + self.env_info.OPENSSL_MODULES = openssl_modules_dir + diff --git a/pkg/fanal/analyzer/language/c/conan/testdata/cacheDir_v2/p/zlib41bd3946e7341/e/conanfile.py b/pkg/fanal/analyzer/language/c/conan/testdata/cacheDir_v2/p/zlib41bd3946e7341/e/conanfile.py new file mode 100644 index 000000000000..ead39ff73661 --- /dev/null +++ b/pkg/fanal/analyzer/language/c/conan/testdata/cacheDir_v2/p/zlib41bd3946e7341/e/conanfile.py @@ -0,0 +1,110 @@ +from conan import ConanFile +from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout +from conan.tools.files import apply_conandata_patches, export_conandata_patches, get, load, replace_in_file, save +from conan.tools.scm import Version +import os + +required_conan_version = ">=1.53.0" + + +class ZlibConan(ConanFile): + license = "Zlib" + name = "zlib" + package_type = "library" + url = "https://github.com/conan-io/conan-center-index" + homepage = "https://zlib.net" + description = ("A Massively Spiffy Yet Delicately Unobtrusive Compression Library " + "(Also Free, Not to Mention Unencumbered by Patents)") + topics = ("zlib", "compression") + + settings = "os", "arch", "compiler", "build_type" + options = { + "shared": [True, False], + "fPIC": [True, False], + } + default_options = { + "shared": False, + "fPIC": True, + } + + @property + def _is_mingw(self): + return self.settings.os == "Windows" and self.settings.compiler == "gcc" + + def export_sources(self): + export_conandata_patches(self) + + def config_options(self): + if self.settings.os == "Windows": + del self.options.fPIC + + def configure(self): + if self.options.shared: + self.options.rm_safe("fPIC") + self.settings.rm_safe("compiler.libcxx") + self.settings.rm_safe("compiler.cppstd") + + def layout(self): + cmake_layout(self, src_folder="src") + + def source(self): + get(self, **self.conan_data["sources"][self.version], + destination=self.source_folder, strip_root=True) + + def generate(self): + tc = CMakeToolchain(self) + tc.variables["SKIP_INSTALL_ALL"] = False + tc.variables["SKIP_INSTALL_LIBRARIES"] = False + tc.variables["SKIP_INSTALL_HEADERS"] = False + tc.variables["SKIP_INSTALL_FILES"] = True + # Correct for misuse of "${CMAKE_INSTALL_PREFIX}/" in CMakeLists.txt + tc.variables["INSTALL_LIB_DIR"] = "lib" + tc.variables["INSTALL_INC_DIR"] = "include" + tc.variables["ZLIB_BUILD_EXAMPLES"] = False + tc.generate() + + def _patch_sources(self): + apply_conandata_patches(self) + + is_apple_clang12 = self.settings.compiler == "apple-clang" and Version(self.settings.compiler.version) >= "12.0" + if not is_apple_clang12: + for filename in ['zconf.h', 'zconf.h.cmakein', 'zconf.h.in']: + filepath = os.path.join(self.source_folder, filename) + replace_in_file(self, filepath, + '#ifdef HAVE_UNISTD_H ' + '/* may be set to #if 1 by ./configure */', + '#if defined(HAVE_UNISTD_H) && (1-HAVE_UNISTD_H-1 != 0)') + replace_in_file(self, filepath, + '#ifdef HAVE_STDARG_H ' + '/* may be set to #if 1 by ./configure */', + '#if defined(HAVE_STDARG_H) && (1-HAVE_STDARG_H-1 != 0)') + + def build(self): + self._patch_sources() + cmake = CMake(self) + cmake.configure() + cmake.build() + + def _extract_license(self): + tmp = load(self, os.path.join(self.source_folder, "zlib.h")) + license_contents = tmp[2:tmp.find("*/", 1)] + return license_contents + + def package(self): + save(self, os.path.join(self.package_folder, "licenses", "LICENSE"), self._extract_license()) + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.set_property("cmake_find_mode", "both") + self.cpp_info.set_property("cmake_file_name", "ZLIB") + self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB") + self.cpp_info.set_property("pkg_config_name", "zlib") + if self.settings.os == "Windows" and not self._is_mingw: + libname = "zdll" if self.options.shared else "zlib" + else: + libname = "z" + self.cpp_info.libs = [libname] + + self.cpp_info.names["cmake_find_package"] = "ZLIB" + self.cpp_info.names["cmake_find_package_multi"] = "ZLIB" diff --git a/pkg/fanal/analyzer/language/c/conan/testdata/happy_v2/release.lock b/pkg/fanal/analyzer/language/c/conan/testdata/happy_v2/release.lock new file mode 100644 index 000000000000..c05aa48b0e3e --- /dev/null +++ b/pkg/fanal/analyzer/language/c/conan/testdata/happy_v2/release.lock @@ -0,0 +1,10 @@ +{ + "version": "0.5", + "requires": [ + "zlib/1.3.1#f52e03ae3d251dec704634230cd806a2%1708593606.497", + "openssl/3.2.2#899583c694f9deccec74dbe0bbc65a15%1717540517.968" + ], + "build_requires": [], + "python_requires": [], + "config_requires": [] +} \ No newline at end of file From 417212e0930aa52a27ebdc1b9370d2943ce0f8fa Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:55:21 +0600 Subject: [PATCH 169/352] fix(cyclonedx): trim non-URL info for `advisory.url` (#6952) --- pkg/sbom/cyclonedx/marshal.go | 16 ++++++++++++++++ pkg/sbom/cyclonedx/marshal_test.go | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pkg/sbom/cyclonedx/marshal.go b/pkg/sbom/cyclonedx/marshal.go index 7f4bb0c3b397..9f5437f2d292 100644 --- a/pkg/sbom/cyclonedx/marshal.go +++ b/pkg/sbom/cyclonedx/marshal.go @@ -3,6 +3,7 @@ package cyclonedx import ( "context" "fmt" + "net/url" "slices" "sort" "strconv" @@ -332,6 +333,10 @@ func (*Marshaler) affects(ref, version string) cdx.Affects { func (*Marshaler) advisories(refs []string) *[]cdx.Advisory { refs = lo.Uniq(refs) advs := lo.FilterMap(refs, func(ref string, _ int) (cdx.Advisory, bool) { + // There are cases when `ref` contains extra info + // But we need to use only URL. + // cf. https://github.com/aquasecurity/trivy/issues/6801 + ref = trimNonUrlInfo(ref) return cdx.Advisory{URL: ref}, ref != "" }) @@ -345,6 +350,17 @@ func (*Marshaler) advisories(refs []string) *[]cdx.Advisory { return &advs } +// trimNonUrlInfo returns first valid URL. +func trimNonUrlInfo(ref string) string { + ss := strings.Split(ref, " ") + for _, s := range ss { + if u, err := url.Parse(s); err == nil && u.Scheme != "" && u.Host != "" { + return s + } + } + return "" +} + func (m *Marshaler) marshalVulnerability(bomRef string, vuln core.Vulnerability) *cdx.Vulnerability { v := &cdx.Vulnerability{ ID: vuln.ID, diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go index d1fc8a455a2a..9209c0c0100c 100644 --- a/pkg/sbom/cyclonedx/marshal_test.go +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -847,8 +847,8 @@ func TestMarshaler_MarshalReport(t *testing.T) { }, }, References: []string{ - "http://www.openwall.com/lists/oss-security/2022/02/11/5", - "https://access.redhat.com/security/cve/CVE-2022-23633", + " extraPrefix http://www.openwall.com/lists/oss-security/2022/02/11/5", + "https://access.redhat.com/security/cve/CVE-2022-23633 (extra suffix)", }, PublishedDate: lo.ToPtr(time.Date(2022, 2, 11, 21, 15, 0, 0, time.UTC)), LastModifiedDate: lo.ToPtr(time.Date(2022, 2, 22, 21, 47, 0, 0, time.UTC)), From 2d85a003b22298d1101f84559f7c6b470f2b3909 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:06:31 +0600 Subject: [PATCH 170/352] fix(purl): add missed os types (#6955) --- integration/testdata/mariner-1.0.json.golden | 4 ++-- pkg/purl/purl.go | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/integration/testdata/mariner-1.0.json.golden b/integration/testdata/mariner-1.0.json.golden index 1d549e1ef188..7325bf74f6e6 100644 --- a/integration/testdata/mariner-1.0.json.golden +++ b/integration/testdata/mariner-1.0.json.golden @@ -42,7 +42,7 @@ "VulnerabilityID": "CVE-2022-0261", "PkgName": "vim", "PkgIdentifier": { - "PURL": "pkg:cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64", + "PURL": "pkg:rpm/cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64\u0026distro=cbl-mariner-1.0.20220122", "UID": "3f08cd76fa5ba73d" }, "InstalledVersion": "8.2.4081-1.cm1", @@ -79,7 +79,7 @@ "VulnerabilityID": "CVE-2022-0158", "PkgName": "vim", "PkgIdentifier": { - "PURL": "pkg:cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64", + "PURL": "pkg:rpm/cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64\u0026distro=cbl-mariner-1.0.20220122", "UID": "3f08cd76fa5ba73d" }, "InstalledVersion": "8.2.4081-1.cm1", diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index 92ce07be9741..e312e40043f7 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -468,13 +468,14 @@ func purlType(t ftypes.TargetType) string { return packageurl.TypePub case ftypes.RustBinary, ftypes.Cargo: return packageurl.TypeCargo - case ftypes.Alpine: + case ftypes.Alpine, ftypes.Chainguard, ftypes.Wolfi: return packageurl.TypeApk case ftypes.Debian, ftypes.Ubuntu: return packageurl.TypeDebian case ftypes.RedHat, ftypes.CentOS, ftypes.Rocky, ftypes.Alma, ftypes.Amazon, ftypes.Fedora, ftypes.Oracle, ftypes.OpenSUSE, - ftypes.OpenSUSELeap, ftypes.OpenSUSETumbleweed, ftypes.SLES, ftypes.Photon: + ftypes.OpenSUSELeap, ftypes.OpenSUSETumbleweed, ftypes.SLES, ftypes.Photon, + ftypes.CBLMariner: return packageurl.TypeRPM case TypeOCI: return packageurl.TypeOCI From 1f8fca1fc77b989bb4e3ba820b297464dbdd825f Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:47:42 +0600 Subject: [PATCH 171/352] feat(java): add support for `maven-metadata.xml` files for remote snapshot repositories. (#6950) --- pkg/dependency/parser/java/pom/metadata.go | 17 ++++ pkg/dependency/parser/java/pom/parse.go | 80 ++++++++++++++++++- pkg/dependency/parser/java/pom/parse_test.go | 61 +++++++++++++- ... => example-dependency-1.2.3-SNAPSHOT.pom} | 0 ...e-dependency-2.17.0-20240312.035235-10.pom | 23 ++++++ .../2.17.0-SNAPSHOT/maven-metadata.xml | 35 ++++++++ .../snapshot/with-maven-metadata/pom.xml | 20 +++++ 7 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 pkg/dependency/parser/java/pom/metadata.go rename pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/1.2.3-SNAPSHOT/{example-dependency-1.2.3.pom => example-dependency-1.2.3-SNAPSHOT.pom} (100%) create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.17.0-SNAPSHOT/example-dependency-2.17.0-20240312.035235-10.pom create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.17.0-SNAPSHOT/maven-metadata.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/snapshot/with-maven-metadata/pom.xml diff --git a/pkg/dependency/parser/java/pom/metadata.go b/pkg/dependency/parser/java/pom/metadata.go new file mode 100644 index 000000000000..0a35e9e4f556 --- /dev/null +++ b/pkg/dependency/parser/java/pom/metadata.go @@ -0,0 +1,17 @@ +package pom + +type Metadata struct { + GroupId string `xml:"groupId"` + ArtifactId string `xml:"artifactId"` + Versioning Versioning `xml:"versioning"` + Version string `xml:"version"` +} + +type Versioning struct { + SnapshotVersions []SnapshotVersion `xml:"snapshotVersions>snapshotVersion"` +} + +type SnapshotVersion struct { + Extension string `xml:"extension"` + Value string `xml:"value"` +} diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index bf8df2ad1c0a..141ebbee0f7c 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -14,6 +14,7 @@ import ( multierror "github.com/hashicorp/go-multierror" "github.com/samber/lo" + "golang.org/x/exp/slices" "golang.org/x/net/html/charset" "golang.org/x/xerrors" @@ -48,6 +49,12 @@ func WithReleaseRemoteRepos(repos []string) option { } } +func WithSnapshotRemoteRepos(repos []string) option { + return func(opts *options) { + opts.snapshotRemoteRepos = repos + } +} + type Parser struct { logger *log.Logger rootPath string @@ -648,7 +655,18 @@ func (p *Parser) fetchPOMFromRemoteRepositories(paths []string, snapshot bool) ( // try all remoteRepositories for _, repo := range remoteRepos { - fetched, err := p.fetchPOMFromRemoteRepository(repo, paths) + repoPaths := slices.Clone(paths) // Clone slice to avoid overwriting last element of `paths` + if snapshot { + pomFileName, err := p.fetchPomFileNameFromMavenMetadata(repo, repoPaths) + if err != nil { + return nil, xerrors.Errorf("fetch maven-metadata.xml error: %w", err) + } + // Use file name from `maven-metadata.xml` if it exists + if pomFileName != "" { + repoPaths[len(repoPaths)-1] = pomFileName + } + } + fetched, err := p.fetchPOMFromRemoteRepository(repo, repoPaths) if err != nil { return nil, xerrors.Errorf("fetch repository error: %w", err) } else if fetched == nil { @@ -659,7 +677,7 @@ func (p *Parser) fetchPOMFromRemoteRepositories(paths []string, snapshot bool) ( return nil, xerrors.Errorf("the POM was not found in remote remoteRepositories") } -func (p *Parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom, error) { +func (p *Parser) remoteRepoRequest(repo string, paths []string) (*http.Request, error) { repoURL, err := url.Parse(repo) if err != nil { p.logger.Error("URL parse error", log.String("repo", repo)) @@ -670,7 +688,6 @@ func (p *Parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom repoURL.Path = path.Join(paths...) logger := p.logger.With(log.String("host", repoURL.Host), log.String("path", repoURL.Path)) - client := &http.Client{} req, err := http.NewRequest("GET", repoURL.String(), http.NoBody) if err != nil { logger.Debug("HTTP request failed") @@ -681,9 +698,54 @@ func (p *Parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom req.SetBasicAuth(repoURL.User.Username(), password) } + return req, nil +} + +// fetchPomFileNameFromMavenMetadata fetches `maven-metadata.xml` file to detect file name of pom file. +func (p *Parser) fetchPomFileNameFromMavenMetadata(repo string, paths []string) (string, error) { + // Overwrite pom file name to `maven-metadata.xml` + mavenMetadataPaths := slices.Clone(paths[:len(paths)-1]) // Clone slice to avoid shadow overwriting last element of `paths` + mavenMetadataPaths = append(mavenMetadataPaths, "maven-metadata.xml") + + req, err := p.remoteRepoRequest(repo, mavenMetadataPaths) + if err != nil { + return "", xerrors.Errorf("unable to create request for maven-metadata.xml file") + } + + client := &http.Client{} resp, err := client.Do(req) if err != nil || resp.StatusCode != http.StatusOK { - logger.Debug("Failed to fetch") + p.logger.Debug("Failed to fetch", log.String("url", req.URL.String())) + return "", nil + } + defer resp.Body.Close() + + mavenMetadata, err := parseMavenMetadata(resp.Body) + if err != nil { + return "", xerrors.Errorf("failed to parse maven-metadata.xml file: %w", err) + } + + var pomFileName string + for _, sv := range mavenMetadata.Versioning.SnapshotVersions { + if sv.Extension == "pom" { + // mavenMetadataPaths[len(mavenMetadataPaths)-3] is always artifactID + pomFileName = fmt.Sprintf("%s-%s.pom", mavenMetadataPaths[len(mavenMetadataPaths)-3], sv.Value) + } + } + + return pomFileName, nil +} + +func (p *Parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom, error) { + req, err := p.remoteRepoRequest(repo, paths) + if err != nil { + return nil, xerrors.Errorf("unable to create request for pom file") + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil || resp.StatusCode != http.StatusOK { + p.logger.Debug("Failed to fetch", log.String("url", req.URL.String())) return nil, nil } defer resp.Body.Close() @@ -709,6 +771,16 @@ func parsePom(r io.Reader) (*pomXML, error) { return parsed, nil } +func parseMavenMetadata(r io.Reader) (*Metadata, error) { + parsed := &Metadata{} + decoder := xml.NewDecoder(r) + decoder.CharsetReader = charset.NewReaderLabel + if err := decoder.Decode(parsed); err != nil { + return nil, xerrors.Errorf("xml decode error: %w", err) + } + return parsed, nil +} + func packageID(name, version string) string { return dependency.ID(ftypes.Pom, name, version) } diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go index 1207f32adcf7..15740d599eb9 100644 --- a/pkg/dependency/parser/java/pom/parse_test.go +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -143,6 +143,13 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipIndirect, + }, }, wantDeps: []ftypes.Dependency{ { @@ -151,6 +158,58 @@ func TestPom_Parse(t *testing.T) { "org.example:example-dependency:1.2.3-SNAPSHOT", }, }, + { + ID: "org.example:example-dependency:1.2.3-SNAPSHOT", + DependsOn: []string{ + "org.example:example-api:2.0.0", + }, + }, + }, + }, + { + name: "snapshot repository with maven-metadata.xml", + inputFile: filepath.Join("testdata", "snapshot", "with-maven-metadata", "pom.xml"), + local: false, + want: []ftypes.Package{ + { + ID: "com.example:happy:1.0.0", + Name: "com.example:happy", + Version: "1.0.0", + Relationship: ftypes.RelationshipRoot, + }, + { + ID: "org.example:example-dependency:2.17.0-SNAPSHOT", + Name: "org.example:example-dependency", + Version: "2.17.0-SNAPSHOT", + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ + { + StartLine: 14, + EndLine: 18, + }, + }, + }, + { + ID: "org.example:example-api:2.0.0", + Name: "org.example:example-api", + Version: "2.0.0", + Licenses: []string{"The Apache Software License, Version 2.0"}, + Relationship: ftypes.RelationshipIndirect, + }, + }, + wantDeps: []ftypes.Dependency{ + { + ID: "com.example:happy:1.0.0", + DependsOn: []string{ + "org.example:example-dependency:2.17.0-SNAPSHOT", + }, + }, + { + ID: "org.example:example-dependency:2.17.0-SNAPSHOT", + DependsOn: []string{ + "org.example:example-api:2.0.0", + }, + }, }, }, { @@ -1404,7 +1463,7 @@ func TestPom_Parse(t *testing.T) { remoteRepos = []string{ts.URL} } - p := pom.NewParser(tt.inputFile, pom.WithReleaseRemoteRepos(remoteRepos), pom.WithOffline(tt.offline)) + p := pom.NewParser(tt.inputFile, pom.WithReleaseRemoteRepos(remoteRepos), pom.WithSnapshotRemoteRepos(remoteRepos), pom.WithOffline(tt.offline)) gotPkgs, gotDeps, err := p.Parse(f) if tt.wantErr != "" { diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/1.2.3-SNAPSHOT/example-dependency-1.2.3.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/1.2.3-SNAPSHOT/example-dependency-1.2.3-SNAPSHOT.pom similarity index 100% rename from pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/1.2.3-SNAPSHOT/example-dependency-1.2.3.pom rename to pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/1.2.3-SNAPSHOT/example-dependency-1.2.3-SNAPSHOT.pom diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.17.0-SNAPSHOT/example-dependency-2.17.0-20240312.035235-10.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.17.0-SNAPSHOT/example-dependency-2.17.0-20240312.035235-10.pom new file mode 100644 index 000000000000..0ecec117f873 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.17.0-SNAPSHOT/example-dependency-2.17.0-20240312.035235-10.pom @@ -0,0 +1,23 @@ + + + + 4.0.0 + + org.example + example-dependency + 2.17.0-SNAPSHOT + + jar + Example API Dependency + The example API + + + + org.example + example-api + 2.0.0 + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.17.0-SNAPSHOT/maven-metadata.xml b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.17.0-SNAPSHOT/maven-metadata.xml new file mode 100644 index 000000000000..258de9db99c5 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.17.0-SNAPSHOT/maven-metadata.xml @@ -0,0 +1,35 @@ + + org.example + example-dependency + + 20240312035235 + + 20240312.035235 + 10 + + + + sources + jar + 2.17.0-20240312.035235-10 + 20240312035235 + + + module + 2.17.0-20240312.035235-10 + 20240312035235 + + + jar + 2.17.0-20240312.035235-10 + 20240312035235 + + + pom + 2.17.0-20240312.035235-10 + 20240312035235 + + + + 2.17.0-SNAPSHOT + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/snapshot/with-maven-metadata/pom.xml b/pkg/dependency/parser/java/pom/testdata/snapshot/with-maven-metadata/pom.xml new file mode 100644 index 000000000000..7e4c2144d417 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/snapshot/with-maven-metadata/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + com.example + happy + 1.0.0 + + happy + Example + + + + + org.example + example-dependency + 2.17.0-SNAPSHOT + + + From f18d035ae13b281c96aa4ed69ca32e507d336e66 Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Wed, 19 Jun 2024 09:46:22 +0100 Subject: [PATCH 172/352] feat(java): add support for sbt projects using sbt-dependency-lock (#6882) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- docs/docs/configuration/reporting.md | 2 + docs/docs/coverage/language/index.md | 1 + docs/docs/coverage/language/java.md | 16 +- integration/repo_test.go | 8 + .../testdata/fixtures/repo/sbt/build.sbt.lock | 29 ++++ integration/testdata/sbt.json.golden | 149 ++++++++++++++++++ pkg/dependency/id.go | 2 +- pkg/dependency/id_test.go | 9 ++ pkg/dependency/parser/sbt/lockfile/parse.go | 84 ++++++++++ .../parser/sbt/lockfile/parse_test.go | 77 +++++++++ .../sbt/lockfile/testdata/empty.sbt.lock | 10 ++ .../sbt/lockfile/testdata/v1_happy.sbt.lock | 73 +++++++++ pkg/detector/library/driver.go | 2 +- pkg/fanal/analyzer/all/import.go | 1 + pkg/fanal/analyzer/const.go | 3 + .../analyzer/language/java/sbt/lockfile.go | 47 ++++++ .../language/java/sbt/lockfile_test.go | 92 +++++++++++ .../java/sbt/testdata/empty/build.sbt.lock | 10 ++ .../java/sbt/testdata/v1/build.sbt.lock | 59 +++++++ pkg/fanal/types/const.go | 2 + pkg/purl/purl.go | 2 +- pkg/purl/purl_test.go | 14 ++ 22 files changed, 687 insertions(+), 5 deletions(-) create mode 100644 integration/testdata/fixtures/repo/sbt/build.sbt.lock create mode 100644 integration/testdata/sbt.json.golden create mode 100644 pkg/dependency/parser/sbt/lockfile/parse.go create mode 100644 pkg/dependency/parser/sbt/lockfile/parse_test.go create mode 100644 pkg/dependency/parser/sbt/lockfile/testdata/empty.sbt.lock create mode 100644 pkg/dependency/parser/sbt/lockfile/testdata/v1_happy.sbt.lock create mode 100644 pkg/fanal/analyzer/language/java/sbt/lockfile.go create mode 100644 pkg/fanal/analyzer/language/java/sbt/lockfile_test.go create mode 100644 pkg/fanal/analyzer/language/java/sbt/testdata/empty/build.sbt.lock create mode 100644 pkg/fanal/analyzer/language/java/sbt/testdata/v1/build.sbt.lock diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index b8b61d34a346..17bf0b864283 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -64,6 +64,7 @@ The following languages are currently supported: | PHP | [composer.lock][composer-lock] | | Java | [pom.xml][pom-xml] | | | [*gradle.lockfile][gradle-lockfile] | +| | [*.sbt.lock][sbt-lockfile] | | Dart | [pubspec.lock][pubspec-lock] | This tree is the reverse of the dependency graph. @@ -447,5 +448,6 @@ $ trivy convert --format table --severity CRITICAL result.json [composer-lock]: ../coverage/language/php.md#composer [pom-xml]: ../coverage/language/java.md#pomxml [gradle-lockfile]: ../coverage/language/java.md#gradlelock +[sbt-lockfile]: ../coverage/language/java.md#sbt [pubspec-lock]: ../coverage/language/dart.md#dart [cargo-binaries]: ../coverage/language/rust.md#binaries \ No newline at end of file diff --git a/docs/docs/coverage/language/index.md b/docs/docs/coverage/language/index.md index eb694bbcc228..b9461b4e013b 100644 --- a/docs/docs/coverage/language/index.md +++ b/docs/docs/coverage/language/index.md @@ -38,6 +38,7 @@ On the other hand, when the target is a post-build artifact, like a container im | [Java](java.md) | JAR/WAR/PAR/EAR[^4] | ✅ | ✅ | - | - | | | pom.xml | - | - | ✅ | ✅ | | | *gradle.lockfile | - | - | ✅ | ✅ | +| | *.sbt.lock | - | - | ✅ | ✅ | | [Go](golang.md) | Binaries built by Go | ✅ | ✅ | - | - | | | go.mod | - | - | ✅ | ✅ | | [Rust](rust.md) | Cargo.lock | ✅ | ✅ | ✅ | ✅ | diff --git a/docs/docs/coverage/language/java.md b/docs/docs/coverage/language/java.md index 87db939ea288..bb90366c1772 100644 --- a/docs/docs/coverage/language/java.md +++ b/docs/docs/coverage/language/java.md @@ -1,5 +1,5 @@ # Java -Trivy supports three types of Java scanning: `JAR/WAR/PAR/EAR`, `pom.xml` and `*gradle.lockfile` files. +Trivy supports four types of Java scanning: `JAR/WAR/PAR/EAR`, `pom.xml`, `*gradle.lockfile` and `*.sbt.lock` files. Each artifact supports the following scanners: @@ -8,6 +8,7 @@ Each artifact supports the following scanners: | JAR/WAR/PAR/EAR | ✓ | ✓ | - | | pom.xml | ✓ | ✓ | ✓ | | *gradle.lockfile | ✓ | ✓ | ✓ | +| *.sbt.lock | ✓ | ✓ | - | The following table provides an outline of the features Trivy offers. @@ -16,6 +17,7 @@ The following table provides an outline of the features Trivy offers. | JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | | pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | | *gradle.lockfile | - | Exclude | ✓ | ✓ | +| *.sbt.lock | - | Exclude | - | ✓ | These may be enabled or disabled depending on the target. See [here](./index.md) for the detail. @@ -94,6 +96,15 @@ Trity also can detect licenses for dependencies. Make sure that you have cache[^8] directory to find licenses from `*.pom` dependency files. + +## SBT + +`build.sbt.lock` files only contain information about used dependencies. This requires a lockfile generated using the +[sbt-dependency-lock][sbt-dependency-lock] plugin. + +!!!note + All necessary files are checked locally. SBT file scanning doesn't require internet access. + [^1]: Uses maven repository to get information about dependencies. Internet access required. [^2]: It means `*.jar`, `*.war`, `*.par` and `*.ear` file [^3]: `ArtifactID`, `GroupID` and `Version` @@ -106,4 +117,5 @@ Make sure that you have cache[^8] directory to find licenses from `*.pom` depend [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies [maven-invoker-plugin]: https://maven.apache.org/plugins/maven-invoker-plugin/usage.html [maven-central]: https://repo.maven.apache.org/maven2/ -[maven-pom-repos]: https://maven.apache.org/settings.html#repositories \ No newline at end of file +[maven-pom-repos]: https://maven.apache.org/settings.html#repositories +[sbt-dependency-lock]: https://stringbean.github.io/sbt-dependency-lock diff --git a/integration/repo_test.go b/integration/repo_test.go index e11e5a21a8b4..49e342d1a91a 100644 --- a/integration/repo_test.go +++ b/integration/repo_test.go @@ -153,6 +153,14 @@ func TestRepository(t *testing.T) { }, golden: "testdata/gradle.json.golden", }, + { + name: "sbt", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/sbt", + }, + golden: "testdata/sbt.json.golden", + }, { name: "conan", args: args{ diff --git a/integration/testdata/fixtures/repo/sbt/build.sbt.lock b/integration/testdata/fixtures/repo/sbt/build.sbt.lock new file mode 100644 index 000000000000..33bcdbee245e --- /dev/null +++ b/integration/testdata/fixtures/repo/sbt/build.sbt.lock @@ -0,0 +1,29 @@ +{ + "lockVersion" : 1, + "timestamp" : "2024-06-06T11:03:09.964557Z", + "configurations" : [ + "compile", + "optional", + "provided", + "runtime", + "test" + ], + "dependencies" : [ + { + "org" : "com.fasterxml.jackson.core", + "name" : "jackson-databind", + "version" : "2.9.1", + "artifacts" : [ + { + "name" : "jackson-databind.jar", + "hash" : "sha1:716da1830a2043f18882fc036ec26eb32cbe5aff" + } + ], + "configurations" : [ + "compile", + "runtime", + "test" + ] + } + ] +} \ No newline at end of file diff --git a/integration/testdata/sbt.json.golden b/integration/testdata/sbt.json.golden new file mode 100644 index 000000000000..94bf111fd221 --- /dev/null +++ b/integration/testdata/sbt.json.golden @@ -0,0 +1,149 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/sbt", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "build.sbt.lock", + "Class": "lang-pkgs", + "Type": "sbt", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2020-9548", + "PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1", + "PkgName": "com.fasterxml.jackson.core:jackson-databind", + "PkgIdentifier": { + "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "UID": "9ccd2eb3e03373ff" + }, + "InstalledVersion": "2.9.1", + "FixedVersion": "2.9.10.4", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-9548", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Maven", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven" + }, + "Title": "jackson-databind: Serialization gadgets in anteros-core", + "Description": "FasterXML jackson-databind 2.x before 2.9.10.4 mishandles the interaction between serialization gadgets and typing, related to br.com.anteros.dbcp.AnterosDBCPConfig (aka anteros-core).", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-502" + ], + "VendorSeverity": { + "ghsa": 4, + "nvd": 4, + "redhat": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 6.8, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 8.1 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2020-9548", + "https://github.com/FasterXML/jackson-databind/issues/2634", + "https://github.com/advisories/GHSA-p43x-xfjf-5jhr", + "https://lists.apache.org/thread.html/r35d30db00440ef63b791c4b7f7acb036e14d4a23afa2a249cb66c0fd@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/r9464a40d25c3ba1a55622db72f113eb494a889656962d098c70c5bb1@%3Cdev.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/r98c9b6e4c9e17792e2cd1ec3e4aa20b61a791939046d3f10888176bb@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rb6fecb5e96a6d61e175ff49f33f2713798dd05cf03067c169d195596@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rd5a4457be4623038c3989294429bc063eec433a2e55995d81591e2ca@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rdd49ab9565bec436a896bc00c4b9fc9dce1598e106c318524fbdfec6@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rdd4df698d5d8e635144d2994922bf0842e933809eae259521f3b5097@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rf1bbc0ea4a9f014cf94df9a12a6477d24a27f52741dbc87f2fd52ff2@%3Cissues.geode.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2020/03/msg00008.html", + "https://medium.com/@cowtowncoder/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062", + "https://nvd.nist.gov/vuln/detail/CVE-2020-9548", + "https://security.netapp.com/advisory/ntap-20200904-0006/", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/security-alerts/cpuoct2021.html" + ], + "PublishedDate": "2020-03-02T04:15:00Z", + "LastModifiedDate": "2021-12-02T21:23:00Z" + }, + { + "VulnerabilityID": "CVE-2021-20190", + "PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1", + "PkgName": "com.fasterxml.jackson.core:jackson-databind", + "PkgIdentifier": { + "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "UID": "9ccd2eb3e03373ff" + }, + "InstalledVersion": "2.9.1", + "FixedVersion": "2.9.10.7", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-20190", + "DataSource": { + "ID": "glad", + "Name": "GitLab Advisory Database Community", + "URL": "https://gitlab.com/gitlab-org/advisories-community" + }, + "Title": "jackson-databind: mishandles the interaction between serialization gadgets and typing, related to javax.swing", + "Description": "A flaw was found in jackson-databind before 2.9.10.7. FasterXML mishandles the interaction between serialization gadgets and typing. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-502" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 3, + "redhat": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:P/A:C", + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 8.3, + "V3Score": 8.1 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 8.1 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2021-20190", + "https://bugzilla.redhat.com/show_bug.cgi?id=1916633", + "https://github.com/FasterXML/jackson-databind/commit/7dbf51bf78d157098074a20bd9da39bd48c18e4a", + "https://github.com/FasterXML/jackson-databind/issues/2854", + "https://github.com/advisories/GHSA-5949-rw7g-wx7w", + "https://lists.apache.org/thread.html/r380e9257bacb8551ee6fcf2c59890ae9477b2c78e553fa9ea08e9d9a@%3Ccommits.nifi.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2021/04/msg00025.html", + "https://nvd.nist.gov/vuln/detail/CVE-2021-20190", + "https://security.netapp.com/advisory/ntap-20210219-0008/" + ], + "PublishedDate": "2021-01-19T17:15:00Z", + "LastModifiedDate": "2021-07-20T23:15:00Z" + } + ] + } + ] +} diff --git a/pkg/dependency/id.go b/pkg/dependency/id.go index d40289cedc6a..8f01bf23123a 100644 --- a/pkg/dependency/id.go +++ b/pkg/dependency/id.go @@ -25,7 +25,7 @@ func ID(ltype types.LangType, name, version string) string { if !strings.HasPrefix(version, "v") { version = "v" + version } - case types.Jar, types.Pom, types.Gradle: + case types.Jar, types.Pom, types.Gradle, types.Sbt: sep = ":" } return name + sep + version diff --git a/pkg/dependency/id_test.go b/pkg/dependency/id_test.go index 68e380e6c651..18359f771e7b 100644 --- a/pkg/dependency/id_test.go +++ b/pkg/dependency/id_test.go @@ -47,6 +47,15 @@ func TestID(t *testing.T) { }, want: "test:1.0.0", }, + { + name: "sbt", + args: args{ + ltype: types.Sbt, + name: "test", + version: "1.0.0", + }, + want: "test:1.0.0", + }, { name: "pip", args: args{ diff --git a/pkg/dependency/parser/sbt/lockfile/parse.go b/pkg/dependency/parser/sbt/lockfile/parse.go new file mode 100644 index 000000000000..3b5b1865903d --- /dev/null +++ b/pkg/dependency/parser/sbt/lockfile/parse.go @@ -0,0 +1,84 @@ +package lockfile + +import ( + "io" + "slices" + "sort" + + "github.com/liamg/jfather" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +// lockfile format defined at: https://stringbean.github.io/sbt-dependency-lock/file-formats/version-1.html +type sbtLockfile struct { + Version int `json:"lockVersion"` + Dependencies []sbtLockfileDependency `json:"dependencies"` +} + +type sbtLockfileDependency struct { + Organization string `json:"org"` + Name string `json:"name"` + Version string `json:"version"` + Configurations []string `json:"configurations"` + StartLine int + EndLine int +} + +type Parser struct{} + +func NewParser() *Parser { + return &Parser{} +} + +func (Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { + var lockfile sbtLockfile + input, err := io.ReadAll(r) + + if err != nil { + return nil, nil, xerrors.Errorf("failed to read sbt lockfile: %w", err) + } + if err := jfather.Unmarshal(input, &lockfile); err != nil { + return nil, nil, xerrors.Errorf("JSON decoding failed: %w", err) + } + + var libraries ftypes.Packages + + for _, dep := range lockfile.Dependencies { + if slices.ContainsFunc(dep.Configurations, isIncludedConfig) { + name := dep.Organization + ":" + dep.Name + libraries = append(libraries, ftypes.Package{ + ID: dependency.ID(ftypes.Sbt, name, dep.Version), + Name: name, + Version: dep.Version, + Locations: []ftypes.Location{ + { + StartLine: dep.StartLine, + EndLine: dep.EndLine, + }, + }, + }) + } + } + + sort.Sort(libraries) + return libraries, nil, nil +} + +// UnmarshalJSONWithMetadata needed to detect start and end lines of deps +func (t *sbtLockfileDependency) UnmarshalJSONWithMetadata(node jfather.Node) error { + if err := node.Decode(&t); err != nil { + return err + } + // Decode func will overwrite line numbers if we save them first + t.StartLine = node.Range().Start.Line + t.EndLine = node.Range().End.Line + return nil +} + +func isIncludedConfig(config string) bool { + return config == "compile" || config == "runtime" +} diff --git a/pkg/dependency/parser/sbt/lockfile/parse_test.go b/pkg/dependency/parser/sbt/lockfile/parse_test.go new file mode 100644 index 000000000000..a11e7b8aedb2 --- /dev/null +++ b/pkg/dependency/parser/sbt/lockfile/parse_test.go @@ -0,0 +1,77 @@ +package lockfile + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestParser_Parse(t *testing.T) { + tests := []struct { + name string + inputFile string + want []ftypes.Package + }{ + { + name: "v1 happy path", + inputFile: "testdata/v1_happy.sbt.lock", + want: []ftypes.Package{ + { + ID: "org.apache.commons:commons-lang3:3.9", + Name: "org.apache.commons:commons-lang3", + Version: "3.9", + Locations: []ftypes.Location{ + { + StartLine: 10, + EndLine: 25, + }, + }, + }, + { + ID: "org.scala-lang:scala-library:2.12.10", + Name: "org.scala-lang:scala-library", + Version: "2.12.10", + Locations: []ftypes.Location{ + { + StartLine: 26, + EndLine: 41, + }, + }, + }, + { + ID: "org.typelevel:cats-core_2.12:2.9.0", + Name: "org.typelevel:cats-core_2.12", + Version: "2.9.0", + Locations: []ftypes.Location{ + { + StartLine: 42, + EndLine: 57, + }, + }, + }, + }, + }, + { + name: "empty", + inputFile: "testdata/empty.sbt.lock", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + parser := NewParser() + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + + libs, _, err := parser.Parse(f) + require.NoError(t, err) + + assert.Equal(t, tt.want, libs) + }) + } +} diff --git a/pkg/dependency/parser/sbt/lockfile/testdata/empty.sbt.lock b/pkg/dependency/parser/sbt/lockfile/testdata/empty.sbt.lock new file mode 100644 index 000000000000..6125547882da --- /dev/null +++ b/pkg/dependency/parser/sbt/lockfile/testdata/empty.sbt.lock @@ -0,0 +1,10 @@ +{ + "lockVersion": 1, + "timestamp": "2024-06-05T13:41:10.992Z", + "configurations": [ + "compile", + "runtime", + "test" + ], + "dependencies": [] +} \ No newline at end of file diff --git a/pkg/dependency/parser/sbt/lockfile/testdata/v1_happy.sbt.lock b/pkg/dependency/parser/sbt/lockfile/testdata/v1_happy.sbt.lock new file mode 100644 index 000000000000..0dcba8ba09c6 --- /dev/null +++ b/pkg/dependency/parser/sbt/lockfile/testdata/v1_happy.sbt.lock @@ -0,0 +1,73 @@ +{ + "lockVersion": 1, + "timestamp": "2024-06-05T13:41:10.992Z", + "configurations": [ + "compile", + "runtime", + "test" + ], + "dependencies": [ + { + "org": "org.apache.commons", + "name": "commons-lang3", + "version": "3.9", + "artifacts": [ + { + "name": "commons-lang3.jar", + "hash": "sha1:0122c7cee69b53ed4a7681c03d4ee4c0e2765da5" + } + ], + "configurations": [ + "test", + "compile", + "runtime" + ] + }, + { + "org": "org.scala-lang", + "name": "scala-library", + "version": "2.12.10", + "artifacts": [ + { + "name": "scala-library.jar", + "hash": "sha1:3509860bc2e5b3da001ed45aca94ffbe5694dbda" + } + ], + "configurations": [ + "test", + "compile", + "runtime" + ] + }, + { + "org" : "org.typelevel", + "name" : "cats-core_2.12", + "version" : "2.9.0", + "artifacts" : [ + { + "name" : "cats-core_2.12.jar", + "hash" : "sha1:844f21541d1809008586fbc1172dc02c96476639" + } + ], + "configurations" : [ + "compile", + "runtime", + "test" + ] + }, + { + "org" : "org.scalatest", + "name" : "scalatest-core_2.13", + "version" : "3.2.15", + "artifacts" : [ + { + "name" : "scalatest-core_2.13.jar", + "hash" : "sha1:231d1f4049a9fa4bd65c17b806a58180b9f4abe1" + } + ], + "configurations" : [ + "test" + ] + } + ] +} \ No newline at end of file diff --git a/pkg/detector/library/driver.go b/pkg/detector/library/driver.go index f78932b13442..af91c30c786f 100644 --- a/pkg/detector/library/driver.go +++ b/pkg/detector/library/driver.go @@ -39,7 +39,7 @@ func NewDriver(libType ftypes.LangType) (Driver, bool) { case ftypes.GoBinary, ftypes.GoModule: ecosystem = vulnerability.Go comparer = compare.GenericComparer{} - case ftypes.Jar, ftypes.Pom, ftypes.Gradle: + case ftypes.Jar, ftypes.Pom, ftypes.Gradle, ftypes.Sbt: ecosystem = vulnerability.Maven comparer = maven.Comparer{} case ftypes.Npm, ftypes.Yarn, ftypes.Pnpm, ftypes.NodePkg, ftypes.JavaScript: diff --git a/pkg/fanal/analyzer/all/import.go b/pkg/fanal/analyzer/all/import.go index a5b0d05298a1..1849bcebf682 100644 --- a/pkg/fanal/analyzer/all/import.go +++ b/pkg/fanal/analyzer/all/import.go @@ -20,6 +20,7 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/gradle" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/jar" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/pom" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/sbt" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/julia/pkg" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/npm" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/pkg" diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 99ac3cf4bee5..82bf3bb12296 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -55,6 +55,7 @@ const ( TypeJar Type = "jar" TypePom Type = "pom" TypeGradleLock Type = "gradle-lockfile" + TypeSbtLock Type = "sbt-lockfile" // Node.js TypeNpmPkgLock Type = "npm" @@ -173,6 +174,7 @@ var ( TypeJar, TypePom, TypeGradleLock, + TypeSbtLock, TypeNpmPkgLock, TypeNodePkg, TypeYarn, @@ -210,6 +212,7 @@ var ( TypePom, TypeConanLock, TypeGradleLock, + TypeSbtLock, TypeCocoaPods, TypeSwift, TypePubSpecLock, diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile.go b/pkg/fanal/analyzer/language/java/sbt/lockfile.go new file mode 100644 index 000000000000..4d1d17cb5d34 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile.go @@ -0,0 +1,47 @@ +package sbt + +import ( + "context" + "os" + "path/filepath" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/sbt/lockfile" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&sbtDependencyLockAnalyzer{}) +} + +const version = 1 + +// sbtDependencyLockAnalyzer analyzes '*.sbt.lock' +type sbtDependencyLockAnalyzer struct{} + +func (a sbtDependencyLockAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + parser := lockfile.NewParser() + + res, err := language.Analyze(types.Sbt, input.FilePath, input.Content, parser) + + if err != nil { + return nil, xerrors.Errorf("%s parse error: %w", input.FilePath, err) + } + + return res, nil +} + +func (a sbtDependencyLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return types.SbtLock == filepath.Base(filePath) +} + +func (a sbtDependencyLockAnalyzer) Type() analyzer.Type { + return analyzer.TypeSbtLock +} + +func (a sbtDependencyLockAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go b/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go new file mode 100644 index 000000000000..0469d9156a19 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go @@ -0,0 +1,92 @@ +package sbt + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_sbtDependencyLockAnalyzer(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + }{ + { + name: "v1 lockfile", + inputFile: "testdata/v1/build.sbt.lock", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Sbt, + FilePath: "testdata/v1/build.sbt.lock", + Packages: types.Packages{ + { + ID: "org.apache.commons:commons-lang3:3.9", + Name: "org.apache.commons:commons-lang3", + Version: "3.9", + Locations: []types.Location{ + { + StartLine: 10, + EndLine: 25, + }, + }, + }, + { + ID: "org.scala-lang:scala-library:2.12.10", + Name: "org.scala-lang:scala-library", + Version: "2.12.10", + Locations: []types.Location{ + { + StartLine: 26, + EndLine: 41, + }, + }, + }, + { + ID: "org.typelevel:cats-core_2.12:2.9.0", + Name: "org.typelevel:cats-core_2.12", + Version: "2.9.0", + Locations: []types.Location{ + { + StartLine: 42, + EndLine: 57, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "empty lockfile", + inputFile: "testdata/empty/build.sbt.lock", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + + a := sbtDependencyLockAnalyzer{} + ctx := context.Background() + + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/java/sbt/testdata/empty/build.sbt.lock b/pkg/fanal/analyzer/language/java/sbt/testdata/empty/build.sbt.lock new file mode 100644 index 000000000000..6125547882da --- /dev/null +++ b/pkg/fanal/analyzer/language/java/sbt/testdata/empty/build.sbt.lock @@ -0,0 +1,10 @@ +{ + "lockVersion": 1, + "timestamp": "2024-06-05T13:41:10.992Z", + "configurations": [ + "compile", + "runtime", + "test" + ], + "dependencies": [] +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/java/sbt/testdata/v1/build.sbt.lock b/pkg/fanal/analyzer/language/java/sbt/testdata/v1/build.sbt.lock new file mode 100644 index 000000000000..26b5ef401aeb --- /dev/null +++ b/pkg/fanal/analyzer/language/java/sbt/testdata/v1/build.sbt.lock @@ -0,0 +1,59 @@ +{ + "lockVersion": 1, + "timestamp": "2024-06-05T13:41:10.992Z", + "configurations": [ + "compile", + "runtime", + "test" + ], + "dependencies": [ + { + "org": "org.apache.commons", + "name": "commons-lang3", + "version": "3.9", + "artifacts": [ + { + "name": "commons-lang3.jar", + "hash": "sha1:0122c7cee69b53ed4a7681c03d4ee4c0e2765da5" + } + ], + "configurations": [ + "test", + "compile", + "runtime" + ] + }, + { + "org": "org.scala-lang", + "name": "scala-library", + "version": "2.12.10", + "artifacts": [ + { + "name": "scala-library.jar", + "hash": "sha1:3509860bc2e5b3da001ed45aca94ffbe5694dbda" + } + ], + "configurations": [ + "test", + "compile", + "runtime" + ] + }, + { + "org" : "org.typelevel", + "name" : "cats-core_2.12", + "version" : "2.9.0", + "artifacts" : [ + { + "name" : "cats-core_2.12.jar", + "hash" : "sha1:844f21541d1809008586fbc1172dc02c96476639" + } + ], + "configurations" : [ + "compile", + "runtime", + "test" + ] + } + ] +} \ No newline at end of file diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index 6874b8a40b06..9ad8c1a2c57b 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -63,6 +63,7 @@ const ( Jar LangType = "jar" Pom LangType = "pom" Gradle LangType = "gradle" + Sbt LangType = "sbt" GoBinary LangType = "gobinary" GoModule LangType = "gomod" JavaScript LangType = "javascript" @@ -114,6 +115,7 @@ const ( GoSum = "go.sum" MavenPom = "pom.xml" + SbtLock = "build.sbt.lock" NpmPkg = "package.json" NpmPkgLock = "package-lock.json" diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index e312e40043f7..1ccf39c8530a 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -442,7 +442,7 @@ func parseJulia(pkgName, pkgUUID string) (string, string, packageurl.Qualifiers) func purlType(t ftypes.TargetType) string { switch t { - case ftypes.Jar, ftypes.Pom, ftypes.Gradle: + case ftypes.Jar, ftypes.Pom, ftypes.Gradle, ftypes.Sbt: return packageurl.TypeMaven case ftypes.Bundler, ftypes.GemSpec: return packageurl.TypeGem diff --git a/pkg/purl/purl_test.go b/pkg/purl/purl_test.go index ddcfc98222e6..25e9e7829d7b 100644 --- a/pkg/purl/purl_test.go +++ b/pkg/purl/purl_test.go @@ -51,6 +51,20 @@ func TestNewPackageURL(t *testing.T) { Version: "5.3.14", }, }, + { + name: "sbt package", + typ: ftypes.Sbt, + pkg: ftypes.Package{ + Name: "org.typelevel:cats-core_2.12", + Version: "2.9.0", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.typelevel", + Name: "cats-core_2.12", + Version: "2.9.0", + }, + }, { name: "yarn package", typ: ftypes.Yarn, From 5ee4e9d30ea814f60fd5705361cabf2e83a47a78 Mon Sep 17 00:00:00 2001 From: Dirk Mueller Date: Wed, 19 Jun 2024 12:09:25 +0200 Subject: [PATCH 173/352] fix(suse): Add SLES 15.6 and Leap 15.6 (#6964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dirk Müller --- pkg/detector/ospkg/suse/suse.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/detector/ospkg/suse/suse.go b/pkg/detector/ospkg/suse/suse.go index a5ccade5c813..eb2fed82cda0 100644 --- a/pkg/detector/ospkg/suse/suse.go +++ b/pkg/detector/ospkg/suse/suse.go @@ -39,9 +39,10 @@ var ( "15.2": time.Date(2021, 12, 31, 23, 59, 59, 0, time.UTC), "15.3": time.Date(2022, 12, 31, 23, 59, 59, 0, time.UTC), "15.4": time.Date(2023, 12, 31, 23, 59, 59, 0, time.UTC), - "15.5": time.Date(2028, 12, 31, 23, 59, 59, 0, time.UTC), + "15.5": time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC), + "15.6": time.Date(2031, 7, 31, 23, 59, 59, 0, time.UTC), // 6 months after SLES 15 SP7 release - // "15.6": time.Date(2028, 12, 31, 23, 59, 59, 0, time.UTC), + // "15.7": time.Date(2031, 7, 31, 23, 59, 59, 0, time.UTC), } opensuseEolDates = map[string]time.Time{ @@ -55,6 +56,7 @@ var ( "15.3": time.Date(2022, 11, 30, 23, 59, 59, 0, time.UTC), "15.4": time.Date(2023, 11, 30, 23, 59, 59, 0, time.UTC), "15.5": time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC), + "15.6": time.Date(2025, 12, 31, 23, 59, 59, 0, time.UTC), } ) From f144e912d34234f00b5a13b7a11a0019fa978b27 Mon Sep 17 00:00:00 2001 From: Charles Oxyer <46503080+charlesoxyer@users.noreply.github.com> Date: Wed, 19 Jun 2024 03:30:55 -0700 Subject: [PATCH 174/352] feat: Add local ImageID to SARIF metadata (#6522) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- integration/testdata/alpine-310.sarif.golden | 1 + pkg/report/sarif.go | 1 + pkg/report/sarif_test.go | 2 ++ 3 files changed, 4 insertions(+) diff --git a/integration/testdata/alpine-310.sarif.golden b/integration/testdata/alpine-310.sarif.golden index 535bd2d09f71..a875ba35fecf 100644 --- a/integration/testdata/alpine-310.sarif.golden +++ b/integration/testdata/alpine-310.sarif.golden @@ -184,6 +184,7 @@ } }, "properties": { + "imageID": "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", "imageName": "testdata/fixtures/images/alpine-310.tar.gz", "repoDigests": null, "repoTags": null diff --git a/pkg/report/sarif.go b/pkg/report/sarif.go index ae84b8ff987f..a94dcccb2c9b 100644 --- a/pkg/report/sarif.go +++ b/pkg/report/sarif.go @@ -137,6 +137,7 @@ func (sw *SarifWriter) Write(ctx context.Context, report types.Report) error { "imageName": report.ArtifactName, "repoTags": report.Metadata.RepoTags, "repoDigests": report.Metadata.RepoDigests, + "imageID": report.Metadata.ImageID, } } if sw.Target != "" { diff --git a/pkg/report/sarif_test.go b/pkg/report/sarif_test.go index 14b5b6027a3b..9ce3363cc321 100644 --- a/pkg/report/sarif_test.go +++ b/pkg/report/sarif_test.go @@ -31,6 +31,7 @@ func TestReportWriter_Sarif(t *testing.T) { ArtifactName: "debian:9", ArtifactType: artifact.TypeContainerImage, Metadata: types.Metadata{ + ImageID: "sha256:7640c3f9e75002deb419d5e32738eeff82cf2b3edca3781b4fe1f1f626d11b20", RepoTags: []string{ "debian:9", }, @@ -177,6 +178,7 @@ func TestReportWriter_Sarif(t *testing.T) { PropertyBag: sarif.PropertyBag{ Properties: map[string]any{ "imageName": "debian:9", + "imageID": "sha256:7640c3f9e75002deb419d5e32738eeff82cf2b3edca3781b4fe1f1f626d11b20", "repoDigests": []any{"debian@sha256:a8cc1744bbdd5266678e3e8b3e6387e45c053218438897e86876f2eb104e5534"}, "repoTags": []any{"debian:9"}, }, From dfe757e37aa7fe3d623afc5e312e74bf78b043c4 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:48:31 +0600 Subject: [PATCH 175/352] refactor: add warning if severity not from vendor (or NVD or GH) is used (#6726) Signed-off-by: knqyf263 Co-authored-by: Teppei Fukuda --- docs/docs/scanner/vulnerability.md | 39 +++++++++++++++++++++++++++++- pkg/vulnerability/vulnerability.go | 26 ++++++++++++-------- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md index ee76a8e6844c..57cb6d79c1c3 100644 --- a/docs/docs/scanner/vulnerability.md +++ b/docs/docs/scanner/vulnerability.md @@ -66,7 +66,44 @@ If the data source does not provide a severity, the severity is determined based | 7.0-8.9 | High | | 9.0-10.0 | Critical | -If the CVSS score is also not provided, it falls back to [NVD][nvd], and if NVD does not have severity, it will be UNKNOWN. +If the CVSS score is also not provided, it falls back to [NVD][nvd]. + +NVD and some vendors may delay severity analysis, while other vendors, such as Red Hat, are able to quickly evaluate and announce the severity of vulnerabilities. +To avoid marking too many vulnerabilities as "UNKNOWN" severity, Trivy uses severity ratings from other vendors when the NVD information is not yet available. +The order of preference for vendor severity data can be found [here](https://github.com/aquasecurity/trivy-db/blob/79d0fbd1e246f3c77eef4b9826fe4bf65940b221/pkg/vulnsrc/vulnerability/vulnerability.go#L17-L19). + +You can reference `SeveritySource` in the [JSON reporting format](../configuration/reporting.md#json) to see from where the severity is taken for a given vulnerability. + +```shell +"SeveritySource": "debian", +``` + + +In addition, you can see all the vendor severity ratings. + +```json +"VendorSeverity": { + "amazon": 2, + "cbl-mariner": 4, + "ghsa": 4, + "nvd": 4, + "photon": 4, + "redhat": 2, + "ubuntu": 2 +} +``` + +Here is the severity mapping in Trivy: + +| Number | Severity | +|:------:|----------| +| 0 | Unknown | +| 1 | Low | +| 2 | Medium | +| 3 | High | +| 4 | Critical | + +If no vendor has a severity, the `UNKNOWN` severity will be used. ### Unfixed Vulnerabilities The unfixed/unfixable vulnerabilities mean that the patch has not yet been provided on their distribution. diff --git a/pkg/vulnerability/vulnerability.go b/pkg/vulnerability/vulnerability.go index 8e27ee4733fc..a77c93a87a1a 100644 --- a/pkg/vulnerability/vulnerability.go +++ b/pkg/vulnerability/vulnerability.go @@ -2,8 +2,10 @@ package vulnerability import ( "strings" + "sync" "github.com/google/wire" + "github.com/samber/lo" "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" @@ -46,6 +48,12 @@ var SuperSet = wire.NewSet( NewClient, ) +// Show warning if we use severity from another vendor +// cf. https://github.com/aquasecurity/trivy/issues/6714 +var onceWarn = sync.OnceFunc(func() { + log.Warn("Using severities from other vendors for some vulnerabilities. Read https://aquasecurity.github.io/trivy/latest/docs/scanner/vulnerability/#severity-selection for details.") +}) + // Client manipulates vulnerabilities type Client struct { dbc db.Operation @@ -77,13 +85,10 @@ func (c Client) FillInfo(vulns []types.DetectedVulnerability) { } // Detect the data source - var source dbTypes.SourceID - if vulns[i].DataSource != nil { - source = vulns[i].DataSource.ID - } + dataSource := lo.FromPtr(vulns[i].DataSource) - // Select the severity according to the detected source. - severity, severitySource := c.getVendorSeverity(vulnID, &vuln, source) + // Select the severity according to the detected sourceID. + severity, severitySource := c.getVendorSeverity(vulnID, &vuln, dataSource) // The vendor might provide package-specific severity like Debian. // For example, CVE-2015-2328 in Debian has "unimportant" for mongodb and "low" for pcre3. @@ -105,13 +110,13 @@ func (c Client) FillInfo(vulns []types.DetectedVulnerability) { vulns[i].Severity = severity vulns[i].SeveritySource = severitySource - vulns[i].PrimaryURL = c.getPrimaryURL(vulnID, vuln.References, source) + vulns[i].PrimaryURL = c.getPrimaryURL(vulnID, vuln.References, dataSource.ID) } } -func (c Client) getVendorSeverity(vulnID string, vuln *dbTypes.Vulnerability, source dbTypes.SourceID) (string, dbTypes.SourceID) { - if vs, ok := vuln.VendorSeverity[source]; ok { - return vs.String(), source +func (c Client) getVendorSeverity(vulnID string, vuln *dbTypes.Vulnerability, dataSource dbTypes.DataSource) (string, dbTypes.SourceID) { + if vs, ok := vuln.VendorSeverity[dataSource.ID]; ok { + return vs.String(), dataSource.ID } // use severity from GitHub for all GHSA-xxx vulnerabilities @@ -130,6 +135,7 @@ func (c Client) getVendorSeverity(vulnID string, vuln *dbTypes.Vulnerability, so return dbTypes.SeverityUnknown.String(), "" } + onceWarn() return vuln.Severity, "" } From 983ac15f22d36a95bca57a18cad21c5efdb27caf Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 20 Jun 2024 06:48:08 +0400 Subject: [PATCH 176/352] ci: add depguard (#6963) Signed-off-by: knqyf263 --- .golangci.yaml | 11 +++++++++ go.mod | 2 +- pkg/cloud/aws/commands/run.go | 2 +- pkg/commands/artifact/run.go | 2 +- pkg/compliance/spec/compliance.go | 4 ++-- pkg/compliance/spec/mapper.go | 2 +- pkg/dependency/parser/c/conan/parse.go | 2 +- pkg/dependency/parser/golang/mod/parse.go | 3 +-- pkg/dependency/parser/java/pom/artifact.go | 2 +- pkg/dependency/parser/java/pom/parse.go | 2 +- pkg/dependency/parser/julia/manifest/parse.go | 4 ++-- pkg/dependency/parser/nodejs/npm/parse.go | 4 ++-- pkg/dependency/parser/nodejs/pnpm/parse.go | 3 +-- pkg/dependency/parser/php/composer/parse.go | 4 ++-- pkg/dependency/parser/ruby/bundler/parse.go | 4 ++-- .../parser/swift/cocoapods/parse.go | 4 ++-- pkg/dependency/parser/utils/utils.go | 5 ++-- pkg/detector/ospkg/redhat/redhat.go | 6 ++--- pkg/downloader/download.go | 2 +- pkg/fanal/analyzer/analyzer.go | 2 +- pkg/fanal/analyzer/config_analyzer.go | 2 +- pkg/fanal/analyzer/imgconf/apk/apk.go | 4 ++-- .../analyzer/language/dart/pub/pubspec.go | 3 +-- .../analyzer/language/dotnet/nuget/nuget.go | 2 +- pkg/fanal/analyzer/language/golang/mod/mod.go | 5 ++-- pkg/fanal/analyzer/language/julia/pkg/pkg.go | 5 ++-- .../analyzer/language/nodejs/yarn/yarn.go | 3 +-- .../language/php/composer/composer.go | 2 +- .../analyzer/language/rust/cargo/cargo.go | 6 ++--- pkg/fanal/analyzer/licensing/license.go | 2 +- pkg/fanal/analyzer/os/alpine/alpine.go | 2 +- pkg/fanal/analyzer/os/release/release.go | 3 +-- pkg/fanal/analyzer/os/ubuntu/esm.go | 2 +- pkg/fanal/analyzer/os/ubuntu/ubuntu.go | 2 +- pkg/fanal/analyzer/pkg/apk/apk.go | 2 +- pkg/fanal/analyzer/pkg/dpkg/copyright.go | 2 +- pkg/fanal/analyzer/pkg/dpkg/dpkg.go | 2 +- pkg/fanal/analyzer/pkg/rpm/rpm.go | 2 +- pkg/fanal/analyzer/pkg/rpm/rpmqa.go | 2 +- pkg/fanal/analyzer/repo/apk/apk.go | 2 +- pkg/fanal/analyzer/secret/secret.go | 24 +++++++++++++++---- pkg/fanal/artifact/image/image.go | 2 +- pkg/fanal/artifact/image/remote_sbom.go | 2 +- pkg/fanal/handler/handler.go | 2 +- pkg/fanal/handler/sysfile/filter.go | 3 +-- pkg/fanal/handler/unpackaged/unpackaged.go | 2 +- pkg/fanal/secret/scanner.go | 2 +- pkg/fanal/walker/fs_test.go | 2 +- pkg/fanal/walker/vm.go | 2 +- pkg/flag/options.go | 2 +- pkg/flag/report_flags.go | 2 +- .../cloudformation/aws/ec2/security_group.go | 4 ++-- .../adapters/terraform/google/iam/adapt.go | 8 +++---- pkg/iac/scanners/azure/value.go | 11 ++++++--- .../scanners/terraform/parser/evaluator.go | 15 +++++++++--- .../scanners/terraform/parser/modules_test.go | 13 ++++++---- pkg/k8s/report/report.go | 6 ++--- pkg/k8s/report/summary.go | 2 +- pkg/k8s/scanner/scanner_test.go | 4 ++-- pkg/licensing/expression/types.go | 3 +-- pkg/licensing/scanner.go | 2 +- pkg/mapfs/fs.go | 2 +- pkg/module/module.go | 2 +- pkg/rekor/client.go | 2 +- pkg/report/table/table.go | 2 +- pkg/report/table/vulnerability.go | 7 +++--- pkg/result/filter.go | 5 ++-- pkg/sbom/io/decode.go | 4 ++-- pkg/sbom/spdx/marshal.go | 2 +- pkg/scanner/local/scan.go | 2 +- pkg/types/target.go | 2 +- pkg/utils/fsutils/fs.go | 2 +- pkg/x/path/path.go | 3 +-- 73 files changed, 154 insertions(+), 119 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 83dae8d06e61..17efa8050e11 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,4 +1,14 @@ linters-settings: + depguard: + rules: + main: + list-mode: lax + deny: + # Cannot use gomodguard, which examines go.mod, as "golang.org/x/exp/slices" is not a module and doesn't appear in go.mod. + - pkg: "golang.org/x/exp/slices" + desc: "Use 'slices' instead" + - pkg: "golang.org/x/exp/maps" + desc: "Use 'maps' or 'github.com/samber/lo' instead" dupl: threshold: 100 errcheck: @@ -81,6 +91,7 @@ linters: disable-all: true enable: - bodyclose + - depguard - gci - goconst - gocritic diff --git a/go.mod b/go.mod index 97bc846086a5..1feb4197a695 100644 --- a/go.mod +++ b/go.mod @@ -119,7 +119,7 @@ require ( github.com/zclconf/go-cty-yaml v1.0.3 go.etcd.io/bbolt v1.3.10 golang.org/x/crypto v0.24.0 - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/mod v0.17.0 golang.org/x/net v0.26.0 golang.org/x/sync v0.7.0 diff --git a/pkg/cloud/aws/commands/run.go b/pkg/cloud/aws/commands/run.go index 708d9314f94f..b9a1bbb2bfce 100644 --- a/pkg/cloud/aws/commands/run.go +++ b/pkg/cloud/aws/commands/run.go @@ -3,11 +3,11 @@ package commands import ( "context" "errors" + "slices" "sort" "strings" "github.com/aws/aws-sdk-go-v2/service/sts" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy-aws/pkg/errs" diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index afdbf2ee6966..30058ff6c4cb 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -4,11 +4,11 @@ import ( "context" "errors" "fmt" + "slices" "github.com/hashicorp/go-multierror" "github.com/samber/lo" "github.com/spf13/viper" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/go-version/pkg/semver" diff --git a/pkg/compliance/spec/compliance.go b/pkg/compliance/spec/compliance.go index bc91b7f664fe..7b0b4f6cffdd 100644 --- a/pkg/compliance/spec/compliance.go +++ b/pkg/compliance/spec/compliance.go @@ -5,7 +5,7 @@ import ( "os" "strings" - "golang.org/x/exp/maps" + "github.com/samber/lo" "golang.org/x/xerrors" "gopkg.in/yaml.v3" @@ -39,7 +39,7 @@ func (cs *ComplianceSpec) Scanners() (types.Scanners, error) { scannerTypes[scannerType] = struct{}{} } } - return maps.Keys(scannerTypes), nil + return lo.Keys(scannerTypes), nil } // CheckIDs return list of compliance check IDs diff --git a/pkg/compliance/spec/mapper.go b/pkg/compliance/spec/mapper.go index 2efa488bff54..6fa5d2bbd45a 100644 --- a/pkg/compliance/spec/mapper.go +++ b/pkg/compliance/spec/mapper.go @@ -1,7 +1,7 @@ package spec import ( - "golang.org/x/exp/slices" + "slices" "github.com/aquasecurity/trivy/pkg/types" ) diff --git a/pkg/dependency/parser/c/conan/parse.go b/pkg/dependency/parser/c/conan/parse.go index 7661131ec8b9..14da9358fb8a 100644 --- a/pkg/dependency/parser/c/conan/parse.go +++ b/pkg/dependency/parser/c/conan/parse.go @@ -2,11 +2,11 @@ package conan import ( "io" + "slices" "strings" "github.com/liamg/jfather" "github.com/samber/lo" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" diff --git a/pkg/dependency/parser/golang/mod/parse.go b/pkg/dependency/parser/golang/mod/parse.go index fa5116f19bfa..508da6911521 100644 --- a/pkg/dependency/parser/golang/mod/parse.go +++ b/pkg/dependency/parser/golang/mod/parse.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/samber/lo" - "golang.org/x/exp/maps" "golang.org/x/mod/modfile" "golang.org/x/xerrors" @@ -148,7 +147,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc } } - return maps.Values(pkgs), nil, nil + return lo.Values(pkgs), nil, nil } // Check if the Go version is less than 1.17 diff --git a/pkg/dependency/parser/java/pom/artifact.go b/pkg/dependency/parser/java/pom/artifact.go index a99ff8569357..b2e97efb229b 100644 --- a/pkg/dependency/parser/java/pom/artifact.go +++ b/pkg/dependency/parser/java/pom/artifact.go @@ -4,10 +4,10 @@ import ( "fmt" "os" "regexp" + "slices" "strings" "github.com/samber/lo" - "golang.org/x/exp/slices" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index 141ebbee0f7c..e905196b6fff 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -9,12 +9,12 @@ import ( "os" "path" "path/filepath" + "slices" "sort" "strings" multierror "github.com/hashicorp/go-multierror" "github.com/samber/lo" - "golang.org/x/exp/slices" "golang.org/x/net/html/charset" "golang.org/x/xerrors" diff --git a/pkg/dependency/parser/julia/manifest/parse.go b/pkg/dependency/parser/julia/manifest/parse.go index 13d1cb208bcb..aa0112a17e67 100644 --- a/pkg/dependency/parser/julia/manifest/parse.go +++ b/pkg/dependency/parser/julia/manifest/parse.go @@ -5,7 +5,7 @@ import ( "sort" "github.com/BurntSushi/toml" - "golang.org/x/exp/maps" + "github.com/samber/lo" "golang.org/x/xerrors" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -156,7 +156,7 @@ func decodeDependency(man *primitiveManifest, dep primitiveDependency, metadata var possibleDepsMap map[string]string err = metadata.PrimitiveDecode(dep.Dependencies, &possibleDepsMap) if err == nil { - possibleUuids := maps.Values(possibleDepsMap) + possibleUuids := lo.Values(possibleDepsMap) sort.Strings(possibleUuids) dep.DependsOn = possibleUuids return dep, nil diff --git a/pkg/dependency/parser/nodejs/npm/parse.go b/pkg/dependency/parser/nodejs/npm/parse.go index 6e99cdd1bfcb..05ce6301ff09 100644 --- a/pkg/dependency/parser/nodejs/npm/parse.go +++ b/pkg/dependency/parser/nodejs/npm/parse.go @@ -3,6 +3,7 @@ package npm import ( "fmt" "io" + "maps" "path" "slices" "sort" @@ -10,7 +11,6 @@ import ( "github.com/liamg/jfather" "github.com/samber/lo" - "golang.org/x/exp/maps" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" @@ -186,7 +186,7 @@ func (p *Parser) parseV2(packages map[string]Package) ([]ftypes.Package, []ftype } - return maps.Values(pkgs), deps + return lo.Values(pkgs), deps } // for local package npm uses links. e.g.: diff --git a/pkg/dependency/parser/nodejs/pnpm/parse.go b/pkg/dependency/parser/nodejs/pnpm/parse.go index bbe6c5a57aab..8ccf9de0ae9a 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/samber/lo" - "golang.org/x/exp/maps" "golang.org/x/xerrors" "gopkg.in/yaml.v3" @@ -216,7 +215,7 @@ func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependen } } - return maps.Values(resolvedPkgs), maps.Values(resolvedDeps) + return lo.Values(resolvedPkgs), lo.Values(resolvedDeps) } // markRootPkgs sets `Dev` to false for non dev dependency. diff --git a/pkg/dependency/parser/php/composer/parse.go b/pkg/dependency/parser/php/composer/parse.go index c95901686ba0..af99ceaf8be2 100644 --- a/pkg/dependency/parser/php/composer/parse.go +++ b/pkg/dependency/parser/php/composer/parse.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/liamg/jfather" - "golang.org/x/exp/maps" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" @@ -98,7 +98,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc }) } - pkgSlice := maps.Values(pkgs) + pkgSlice := lo.Values(pkgs) sort.Sort(ftypes.Packages(pkgSlice)) sort.Sort(deps) diff --git a/pkg/dependency/parser/ruby/bundler/parse.go b/pkg/dependency/parser/ruby/bundler/parse.go index 89f3a9ab4ab8..12dff78bc7c0 100644 --- a/pkg/dependency/parser/ruby/bundler/parse.go +++ b/pkg/dependency/parser/ruby/bundler/parse.go @@ -5,7 +5,7 @@ import ( "sort" "strings" - "golang.org/x/exp/maps" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" @@ -103,7 +103,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc return nil, nil, xerrors.Errorf("scan error: %w", err) } - pkgSlice := maps.Values(pkgs) + pkgSlice := lo.Values(pkgs) sort.Sort(ftypes.Packages(pkgSlice)) return pkgSlice, deps, nil } diff --git a/pkg/dependency/parser/swift/cocoapods/parse.go b/pkg/dependency/parser/swift/cocoapods/parse.go index 27438c86e17f..2d946d417447 100644 --- a/pkg/dependency/parser/swift/cocoapods/parse.go +++ b/pkg/dependency/parser/swift/cocoapods/parse.go @@ -4,7 +4,7 @@ import ( "sort" "strings" - "golang.org/x/exp/maps" + "github.com/samber/lo" "golang.org/x/xerrors" "gopkg.in/yaml.v3" @@ -86,7 +86,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc } sort.Sort(deps) - return utils.UniquePackages(maps.Values(parsedDeps)), deps, nil + return utils.UniquePackages(lo.Values(parsedDeps)), deps, nil } func parseDep(dep string) (ftypes.Package, error) { diff --git a/pkg/dependency/parser/utils/utils.go b/pkg/dependency/parser/utils/utils.go index f22e994a7cb0..ce2aff36976b 100644 --- a/pkg/dependency/parser/utils/utils.go +++ b/pkg/dependency/parser/utils/utils.go @@ -2,9 +2,10 @@ package utils import ( "fmt" + "maps" "sort" - "golang.org/x/exp/maps" + "github.com/samber/lo" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) @@ -48,7 +49,7 @@ func UniquePackages(pkgs []ftypes.Package) []ftypes.Package { } } } - pkgSlice := maps.Values(unique) + pkgSlice := lo.Values(unique) sort.Sort(ftypes.Packages(pkgSlice)) return pkgSlice diff --git a/pkg/detector/ospkg/redhat/redhat.go b/pkg/detector/ospkg/redhat/redhat.go index 277fa6203424..d8d9e0052920 100644 --- a/pkg/detector/ospkg/redhat/redhat.go +++ b/pkg/detector/ospkg/redhat/redhat.go @@ -3,13 +3,13 @@ package redhat import ( "context" "fmt" + "slices" "sort" "strings" "time" version "github.com/knqyf263/go-rpm-version" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" + "github.com/samber/lo" "golang.org/x/xerrors" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" @@ -176,7 +176,7 @@ func (s *Scanner) detect(osVer string, pkg ftypes.Package) ([]types.DetectedVuln } } - vulns := maps.Values(uniqVulns) + vulns := lo.Values(uniqVulns) sort.Slice(vulns, func(i, j int) bool { return vulns[i].VulnerabilityID < vulns[j].VulnerabilityID }) diff --git a/pkg/downloader/download.go b/pkg/downloader/download.go index 0c9388248a6d..9b554b6a4eb8 100644 --- a/pkg/downloader/download.go +++ b/pkg/downloader/download.go @@ -2,10 +2,10 @@ package downloader import ( "context" + "maps" "os" getter "github.com/hashicorp/go-getter" - "golang.org/x/exp/maps" "golang.org/x/xerrors" ) diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go index 56bb518f1b73..d6defb45aae5 100644 --- a/pkg/fanal/analyzer/analyzer.go +++ b/pkg/fanal/analyzer/analyzer.go @@ -6,12 +6,12 @@ import ( "io/fs" "os" "regexp" + "slices" "sort" "strings" "sync" "github.com/samber/lo" - "golang.org/x/exp/slices" "golang.org/x/sync/semaphore" "golang.org/x/xerrors" diff --git a/pkg/fanal/analyzer/config_analyzer.go b/pkg/fanal/analyzer/config_analyzer.go index c5682694a5c3..cddff6412e6a 100644 --- a/pkg/fanal/analyzer/config_analyzer.go +++ b/pkg/fanal/analyzer/config_analyzer.go @@ -2,9 +2,9 @@ package analyzer import ( "context" + "slices" v1 "github.com/google/go-containerregistry/pkg/v1" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/analyzer/imgconf/apk/apk.go b/pkg/fanal/analyzer/imgconf/apk/apk.go index 430dc766d161..794eb9797e5d 100644 --- a/pkg/fanal/analyzer/imgconf/apk/apk.go +++ b/pkg/fanal/analyzer/imgconf/apk/apk.go @@ -13,7 +13,7 @@ import ( "time" v1 "github.com/google/go-containerregistry/pkg/v1" - "golang.org/x/exp/maps" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -138,7 +138,7 @@ func (a alpineCmdAnalyzer) parseConfig(apkIndexArchive *apkIndex, config *v1.Con } } - return maps.Values(uniqPkgs) + return lo.Values(uniqPkgs) } func (a alpineCmdAnalyzer) parseCommand(command string, envs map[string]string) (pkgs []string) { diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec.go b/pkg/fanal/analyzer/language/dart/pub/pubspec.go index 9f7fc02b26d7..fc1ebc9bc586 100644 --- a/pkg/fanal/analyzer/language/dart/pub/pubspec.go +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec.go @@ -10,7 +10,6 @@ import ( "sort" "github.com/samber/lo" - "golang.org/x/exp/maps" "golang.org/x/xerrors" "gopkg.in/yaml.v3" @@ -166,7 +165,7 @@ func parsePubSpecYaml(r io.Reader) (string, []string, error) { // pubspec.yaml uses version ranges // save only dependencies names - dependsOn := maps.Keys(spec.Dependencies) + dependsOn := lo.Keys(spec.Dependencies) return dependency.ID(types.Pub, spec.Name, spec.Version), dependsOn, nil } diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go index 2e24610719e4..fa0cc486def1 100644 --- a/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go @@ -7,9 +7,9 @@ import ( "io/fs" "os" "path/filepath" + "slices" "sort" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/config" diff --git a/pkg/fanal/analyzer/language/golang/mod/mod.go b/pkg/fanal/analyzer/language/golang/mod/mod.go index f97d9bed5add..96d40ba1c954 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod.go @@ -10,11 +10,10 @@ import ( "os" "path/filepath" "regexp" + "slices" "unicode" "github.com/samber/lo" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" @@ -262,7 +261,7 @@ func mergeGoSum(gomod, gosum *types.Application) { uniq[lib.Name] = lib } - gomod.Packages = maps.Values(uniq) + gomod.Packages = lo.Values(uniq) } func findLicense(dir string, classifierConfidenceLevel float64) ([]string, error) { diff --git a/pkg/fanal/analyzer/language/julia/pkg/pkg.go b/pkg/fanal/analyzer/language/julia/pkg/pkg.go index c2b9fda035e3..4e69cb43c326 100644 --- a/pkg/fanal/analyzer/language/julia/pkg/pkg.go +++ b/pkg/fanal/analyzer/language/julia/pkg/pkg.go @@ -7,12 +7,11 @@ import ( "io/fs" "os" "path/filepath" + "slices" "sort" "github.com/BurntSushi/toml" "github.com/samber/lo" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" "golang.org/x/xerrors" julia "github.com/aquasecurity/trivy/pkg/dependency/parser/julia/manifest" @@ -167,7 +166,7 @@ func walkDependencies(directDeps map[string]string, allPackages types.Packages, walkIndirectDependencies(pkg, pkgsByID, visited) } - return maps.Values(visited) + return lo.Values(visited) } // Marks all indirect dependencies as indirect. Starts from `rootPkg`. Visited deps are added to `visited`. diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go index 086f5fe7f615..70d0d1ee8951 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go @@ -15,7 +15,6 @@ import ( "github.com/hashicorp/go-multierror" "github.com/samber/lo" - "golang.org/x/exp/maps" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" @@ -186,7 +185,7 @@ func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.App // If the same package is found in both prod and dev dependencies, use the one in prod. pkgs = lo.Assign(devPkgs, pkgs) - pkgSlice := maps.Values(pkgs) + pkgSlice := lo.Values(pkgs) sort.Sort(types.Packages(pkgSlice)) // Save packages diff --git a/pkg/fanal/analyzer/language/php/composer/composer.go b/pkg/fanal/analyzer/language/php/composer/composer.go index 5e726168a5ae..1c0e14a1881a 100644 --- a/pkg/fanal/analyzer/language/php/composer/composer.go +++ b/pkg/fanal/analyzer/language/php/composer/composer.go @@ -8,10 +8,10 @@ import ( "io/fs" "os" "path/filepath" + "slices" "sort" "strings" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/php/composer" diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo.go b/pkg/fanal/analyzer/language/rust/cargo/cargo.go index 88c8a005595e..bd54273552c6 100644 --- a/pkg/fanal/analyzer/language/rust/cargo/cargo.go +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo.go @@ -6,15 +6,15 @@ import ( "fmt" "io" "io/fs" + "maps" "os" "path" "path/filepath" + "slices" "sort" "github.com/BurntSushi/toml" "github.com/samber/lo" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/go-version/pkg/semver" @@ -148,7 +148,7 @@ func (a cargoAnalyzer) removeDevDependencies(fsys fs.FS, dir string, app *types. a.walkIndirectDependencies(pkg, pkgIDs, pkgs) } - pkgSlice := maps.Values(pkgs) + pkgSlice := lo.Values(pkgs) sort.Sort(types.Packages(pkgSlice)) // Save only prod packages diff --git a/pkg/fanal/analyzer/licensing/license.go b/pkg/fanal/analyzer/licensing/license.go index 42872b1c8474..ceef1d90cecc 100644 --- a/pkg/fanal/analyzer/licensing/license.go +++ b/pkg/fanal/analyzer/licensing/license.go @@ -6,9 +6,9 @@ import ( "math" "os" "path/filepath" + "slices" "strings" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" diff --git a/pkg/fanal/analyzer/os/alpine/alpine.go b/pkg/fanal/analyzer/os/alpine/alpine.go index 0caa5189b8f5..da3d2da00d89 100644 --- a/pkg/fanal/analyzer/os/alpine/alpine.go +++ b/pkg/fanal/analyzer/os/alpine/alpine.go @@ -4,8 +4,8 @@ import ( "bufio" "context" "os" + "slices" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" diff --git a/pkg/fanal/analyzer/os/release/release.go b/pkg/fanal/analyzer/os/release/release.go index d4b959c3a9b3..229c13c932aa 100644 --- a/pkg/fanal/analyzer/os/release/release.go +++ b/pkg/fanal/analyzer/os/release/release.go @@ -4,10 +4,9 @@ import ( "bufio" "context" "os" + "slices" "strings" - "golang.org/x/exp/slices" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" ) diff --git a/pkg/fanal/analyzer/os/ubuntu/esm.go b/pkg/fanal/analyzer/os/ubuntu/esm.go index 9f1dd08f9c6d..d6fa38d30c16 100644 --- a/pkg/fanal/analyzer/os/ubuntu/esm.go +++ b/pkg/fanal/analyzer/os/ubuntu/esm.go @@ -4,8 +4,8 @@ import ( "context" "encoding/json" "os" + "slices" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" diff --git a/pkg/fanal/analyzer/os/ubuntu/ubuntu.go b/pkg/fanal/analyzer/os/ubuntu/ubuntu.go index 2fff3ac8339f..75a24756365d 100644 --- a/pkg/fanal/analyzer/os/ubuntu/ubuntu.go +++ b/pkg/fanal/analyzer/os/ubuntu/ubuntu.go @@ -4,9 +4,9 @@ import ( "bufio" "context" "os" + "slices" "strings" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" diff --git a/pkg/fanal/analyzer/pkg/apk/apk.go b/pkg/fanal/analyzer/pkg/apk/apk.go index bb2007470b1b..9ce13e4b013d 100644 --- a/pkg/fanal/analyzer/pkg/apk/apk.go +++ b/pkg/fanal/analyzer/pkg/apk/apk.go @@ -8,12 +8,12 @@ import ( "fmt" "os" "path" + "slices" "sort" "strings" apkVersion "github.com/knqyf263/go-apk-version" "github.com/samber/lo" - "golang.org/x/exp/slices" "github.com/aquasecurity/trivy/pkg/digest" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" diff --git a/pkg/fanal/analyzer/pkg/dpkg/copyright.go b/pkg/fanal/analyzer/pkg/dpkg/copyright.go index 1f50088f86b7..ac98c2e404c3 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/copyright.go +++ b/pkg/fanal/analyzer/pkg/dpkg/copyright.go @@ -7,10 +7,10 @@ import ( "os" "path" "regexp" + "slices" "strings" "github.com/samber/lo" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" diff --git a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go index a83592e82523..1d8435ecb686 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go +++ b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go @@ -11,12 +11,12 @@ import ( "os" "path/filepath" "regexp" + "slices" "sort" "strings" debVersion "github.com/knqyf263/go-deb-version" "github.com/samber/lo" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/digest" diff --git a/pkg/fanal/analyzer/pkg/rpm/rpm.go b/pkg/fanal/analyzer/pkg/rpm/rpm.go index 70d5b9dcd26a..70d4de217418 100644 --- a/pkg/fanal/analyzer/pkg/rpm/rpm.go +++ b/pkg/fanal/analyzer/pkg/rpm/rpm.go @@ -6,12 +6,12 @@ import ( "io" "os" "path/filepath" + "slices" "sort" "strings" rpmdb "github.com/knqyf263/go-rpmdb/pkg" "github.com/samber/lo" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/digest" diff --git a/pkg/fanal/analyzer/pkg/rpm/rpmqa.go b/pkg/fanal/analyzer/pkg/rpm/rpmqa.go index 83b06f16823b..55f4feaa9232 100644 --- a/pkg/fanal/analyzer/pkg/rpm/rpmqa.go +++ b/pkg/fanal/analyzer/pkg/rpm/rpmqa.go @@ -4,9 +4,9 @@ import ( "bufio" "context" "os" + "slices" "strings" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" diff --git a/pkg/fanal/analyzer/repo/apk/apk.go b/pkg/fanal/analyzer/repo/apk/apk.go index 454710d6841b..cdbc23d5e076 100644 --- a/pkg/fanal/analyzer/repo/apk/apk.go +++ b/pkg/fanal/analyzer/repo/apk/apk.go @@ -5,8 +5,8 @@ import ( "context" "os" "regexp" + "slices" - "golang.org/x/exp/slices" "golang.org/x/xerrors" ver "github.com/aquasecurity/go-version/pkg/version" diff --git a/pkg/fanal/analyzer/secret/secret.go b/pkg/fanal/analyzer/secret/secret.go index bbce32af326e..cad32e00ad8f 100644 --- a/pkg/fanal/analyzer/secret/secret.go +++ b/pkg/fanal/analyzer/secret/secret.go @@ -7,10 +7,10 @@ import ( "io" "os" "path/filepath" + "slices" "strings" "github.com/samber/lo" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -34,10 +34,26 @@ var ( "Pipfile.lock", "Gemfile.lock", } - skipDirs = []string{".git", "node_modules"} + skipDirs = []string{ + ".git", + "node_modules", + } skipExts = []string{ - ".jpg", ".png", ".gif", ".doc", ".pdf", ".bin", ".svg", ".socket", ".deb", ".rpm", - ".zip", ".gz", ".gzip", ".tar", ".pyc", + ".jpg", + ".png", + ".gif", + ".doc", + ".pdf", + ".bin", + ".svg", + ".socket", + ".deb", + ".rpm", + ".zip", + ".gz", + ".gzip", + ".tar", + ".pyc", } ) diff --git a/pkg/fanal/artifact/image/image.go b/pkg/fanal/artifact/image/image.go index 08b61de1b228..c4491eaa90ab 100644 --- a/pkg/fanal/artifact/image/image.go +++ b/pkg/fanal/artifact/image/image.go @@ -6,12 +6,12 @@ import ( "io" "os" "reflect" + "slices" "strings" "sync" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/samber/lo" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" diff --git a/pkg/fanal/artifact/image/remote_sbom.go b/pkg/fanal/artifact/image/remote_sbom.go index 8a386546c07a..37303a9f7b05 100644 --- a/pkg/fanal/artifact/image/remote_sbom.go +++ b/pkg/fanal/artifact/image/remote_sbom.go @@ -6,11 +6,11 @@ import ( "fmt" "os" "path/filepath" + "slices" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/samber/lo" - "golang.org/x/exp/slices" "golang.org/x/xerrors" sbomatt "github.com/aquasecurity/trivy/pkg/attestation/sbom" diff --git a/pkg/fanal/handler/handler.go b/pkg/fanal/handler/handler.go index 10ce085b3547..84629b66e242 100644 --- a/pkg/fanal/handler/handler.go +++ b/pkg/fanal/handler/handler.go @@ -2,9 +2,9 @@ package handler import ( "context" + "slices" "sort" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" diff --git a/pkg/fanal/handler/sysfile/filter.go b/pkg/fanal/handler/sysfile/filter.go index 5222049c0d16..cbe1e84f18d4 100644 --- a/pkg/fanal/handler/sysfile/filter.go +++ b/pkg/fanal/handler/sysfile/filter.go @@ -2,10 +2,9 @@ package nodejs import ( "context" + "slices" "strings" - "golang.org/x/exp/slices" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/handler" diff --git a/pkg/fanal/handler/unpackaged/unpackaged.go b/pkg/fanal/handler/unpackaged/unpackaged.go index 119cae3e7dad..ed380de49cb0 100644 --- a/pkg/fanal/handler/unpackaged/unpackaged.go +++ b/pkg/fanal/handler/unpackaged/unpackaged.go @@ -4,8 +4,8 @@ import ( "bytes" "context" "errors" + "slices" - "golang.org/x/exp/slices" "golang.org/x/xerrors" sbomatt "github.com/aquasecurity/trivy/pkg/attestation/sbom" diff --git a/pkg/fanal/secret/scanner.go b/pkg/fanal/secret/scanner.go index 51ac0db707a8..cc022bb82db4 100644 --- a/pkg/fanal/secret/scanner.go +++ b/pkg/fanal/secret/scanner.go @@ -5,12 +5,12 @@ import ( "errors" "os" "regexp" + "slices" "sort" "strings" "sync" "github.com/samber/lo" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "gopkg.in/yaml.v3" diff --git a/pkg/fanal/walker/fs_test.go b/pkg/fanal/walker/fs_test.go index 2b7b2117afb1..6eec99571073 100644 --- a/pkg/fanal/walker/fs_test.go +++ b/pkg/fanal/walker/fs_test.go @@ -6,12 +6,12 @@ import ( "os" "path/filepath" "runtime" + "slices" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/exp/slices" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/walker" diff --git a/pkg/fanal/walker/vm.go b/pkg/fanal/walker/vm.go index 6ff32564cc0d..5d7336f1623c 100644 --- a/pkg/fanal/walker/vm.go +++ b/pkg/fanal/walker/vm.go @@ -5,13 +5,13 @@ import ( "io" "io/fs" "path/filepath" + "slices" "strings" "github.com/masahiro331/go-disk" "github.com/masahiro331/go-disk/gpt" "github.com/masahiro331/go-disk/mbr" "github.com/masahiro331/go-disk/types" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/vm/filesystem" diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 69b3585226cc..3777bed507fb 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "slices" "strings" "sync" "time" @@ -14,7 +15,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go index d359f1f1b5b6..ce833cc1b13e 100644 --- a/pkg/flag/report_flags.go +++ b/pkg/flag/report_flags.go @@ -1,11 +1,11 @@ package flag import ( + "slices" "strings" "github.com/mattn/go-shellwords" "github.com/samber/lo" - "golang.org/x/exp/slices" "golang.org/x/xerrors" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go index 6de47f302682..6770062affb2 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go @@ -1,7 +1,7 @@ package ec2 import ( - "golang.org/x/exp/maps" + "github.com/samber/lo" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" @@ -43,7 +43,7 @@ func getSecurityGroups(ctx parser.FileContext) []ec2.SecurityGroup { } if len(mGroups) > 0 { - return maps.Values(mGroups) + return lo.Values(mGroups) } return nil } diff --git a/pkg/iac/adapters/terraform/google/iam/adapt.go b/pkg/iac/adapters/terraform/google/iam/adapt.go index e63f9f272d7d..1dcc8a4cec68 100644 --- a/pkg/iac/adapters/terraform/google/iam/adapt.go +++ b/pkg/iac/adapters/terraform/google/iam/adapt.go @@ -1,7 +1,7 @@ package iam import ( - "golang.org/x/exp/maps" + "github.com/samber/lo" "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" "github.com/aquasecurity/trivy/pkg/iac/terraform" @@ -36,9 +36,9 @@ func (a *adapter) Adapt() iam.IAM { func (a *adapter) buildIAMOutput() iam.IAM { return iam.IAM{ - Organizations: fromPtrSlice(maps.Values(a.orgs)), - Folders: fromPtrSlice(maps.Values(a.folders)), - Projects: fromPtrSlice(maps.Values(a.projects)), + Organizations: fromPtrSlice(lo.Values(a.orgs)), + Folders: fromPtrSlice(lo.Values(a.folders)), + Projects: fromPtrSlice(lo.Values(a.projects)), WorkloadIdentityPoolProviders: a.workloadIdentityPoolProviders, } } diff --git a/pkg/iac/scanners/azure/value.go b/pkg/iac/scanners/azure/value.go index c511517b0a57..0adc02b84268 100644 --- a/pkg/iac/scanners/azure/value.go +++ b/pkg/iac/scanners/azure/value.go @@ -1,11 +1,10 @@ package azure import ( + "slices" "strings" "time" - "golang.org/x/exp/slices" - armjson2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser/armjson" "github.com/aquasecurity/trivy/pkg/iac/types" ) @@ -249,7 +248,13 @@ func (v Value) AsBoolValue(defaultValue bool, metadata types.Metadata) types.Boo v.Resolve() if v.Kind == KindString { possibleValue := strings.ToLower(v.rLit.(string)) - if slices.Contains([]string{"true", "1", "yes", "on", "enabled"}, possibleValue) { + if slices.Contains([]string{ + "true", + "1", + "yes", + "on", + "enabled", + }, possibleValue) { return types.Bool(true, metadata) } } diff --git a/pkg/iac/scanners/terraform/parser/evaluator.go b/pkg/iac/scanners/terraform/parser/evaluator.go index b93104f442cc..bf3e4f4ffc13 100644 --- a/pkg/iac/scanners/terraform/parser/evaluator.go +++ b/pkg/iac/scanners/terraform/parser/evaluator.go @@ -5,13 +5,13 @@ import ( "errors" "io/fs" "reflect" + "slices" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/ext/typeexpr" "github.com/samber/lo" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" - "golang.org/x/exp/slices" "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/ignore" @@ -273,7 +273,12 @@ func (e *evaluator) expandDynamicBlock(b *terraform.Block) { } func isBlockSupportsForEachMetaArgument(block *terraform.Block) bool { - return slices.Contains([]string{"module", "resource", "data", "dynamic"}, block.Type()) + return slices.Contains([]string{ + "module", + "resource", + "data", + "dynamic", + }, block.Type()) } func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool) terraform.Blocks { @@ -357,7 +362,11 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool } func isBlockSupportsCountMetaArgument(block *terraform.Block) bool { - return slices.Contains([]string{"module", "resource", "data"}, block.Type()) + return slices.Contains([]string{ + "module", + "resource", + "data", + }, block.Type()) } func (e *evaluator) expandBlockCounts(blocks terraform.Blocks) terraform.Blocks { diff --git a/pkg/iac/scanners/terraform/parser/modules_test.go b/pkg/iac/scanners/terraform/parser/modules_test.go index 404a1effcfb1..29dcaa7c39af 100644 --- a/pkg/iac/scanners/terraform/parser/modules_test.go +++ b/pkg/iac/scanners/terraform/parser/modules_test.go @@ -8,7 +8,6 @@ import ( "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/exp/maps" "github.com/aquasecurity/trivy/internal/testutil" ) @@ -43,7 +42,10 @@ module "this" { source = "../modules/s3" }`, }, - expected: []string{"code", "code/example"}, + expected: []string{ + "code", + "code/example", + }, }, { name: "without module block", @@ -51,7 +53,10 @@ module "this" { "code/infra1/main.tf": `resource "test" "this" {}`, "code/infra2/main.tf": `resource "test" "this" {}`, }, - expected: []string{"code/infra1", "code/infra2"}, + expected: []string{ + "code/infra1", + "code/infra2", + }, }, } @@ -60,7 +65,7 @@ module "this" { fsys := testutil.CreateFS(t, tt.files) parser := New(fsys, "", OptionStopOnHCLError(true)) - modules := lo.Map(maps.Keys(tt.files), func(p string, _ int) string { + modules := lo.Map(lo.Keys(tt.files), func(p string, _ int) string { return path.Dir(p) }) diff --git a/pkg/k8s/report/report.go b/pkg/k8s/report/report.go index 1db95394514e..e71c218bf864 100644 --- a/pkg/k8s/report/report.go +++ b/pkg/k8s/report/report.go @@ -4,10 +4,10 @@ import ( "errors" "fmt" "io" + "slices" "strings" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" + "github.com/samber/lo" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" @@ -114,7 +114,7 @@ func (r Report) consolidate() ConsolidatedReport { index[key] = v } - consolidated.Findings = maps.Values(index) + consolidated.Findings = lo.Values(index) return consolidated } diff --git a/pkg/k8s/report/summary.go b/pkg/k8s/report/summary.go index a637a55cbd0d..9a3d6ee39371 100644 --- a/pkg/k8s/report/summary.go +++ b/pkg/k8s/report/summary.go @@ -3,11 +3,11 @@ package report import ( "fmt" "io" + "slices" "sort" "strconv" "strings" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/table" diff --git a/pkg/k8s/scanner/scanner_test.go b/pkg/k8s/scanner/scanner_test.go index c17b0e622ca2..3de4f0429ef5 100644 --- a/pkg/k8s/scanner/scanner_test.go +++ b/pkg/k8s/scanner/scanner_test.go @@ -6,9 +6,9 @@ import ( "testing" "github.com/package-url/packageurl-go" + "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/exp/maps" "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" cmd "github.com/aquasecurity/trivy/pkg/commands/artifact" @@ -284,7 +284,7 @@ func TestScanner_Scan(t *testing.T) { got, err := scanner.Scan(ctx, tt.artifacts) require.NoError(t, err) - gotComponents := maps.Values(got.BOM.Components()) + gotComponents := lo.Values(got.BOM.Components()) require.Equal(t, len(tt.wantComponents), len(gotComponents)) sort.Slice(gotComponents, func(i, j int) bool { diff --git a/pkg/licensing/expression/types.go b/pkg/licensing/expression/types.go index f5315f4ffb0f..f344c610ab4d 100644 --- a/pkg/licensing/expression/types.go +++ b/pkg/licensing/expression/types.go @@ -2,8 +2,7 @@ package expression import ( "fmt" - - "golang.org/x/exp/slices" + "slices" "github.com/aquasecurity/trivy/pkg/licensing" ) diff --git a/pkg/licensing/scanner.go b/pkg/licensing/scanner.go index b246a05b3a01..100358a5af6c 100644 --- a/pkg/licensing/scanner.go +++ b/pkg/licensing/scanner.go @@ -1,7 +1,7 @@ package licensing import ( - "golang.org/x/exp/slices" + "slices" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/mapfs/fs.go b/pkg/mapfs/fs.go index c809f394b6fd..4cba59263c37 100644 --- a/pkg/mapfs/fs.go +++ b/pkg/mapfs/fs.go @@ -6,10 +6,10 @@ import ( "io/fs" "os" "path/filepath" + "slices" "strings" "time" - "golang.org/x/exp/slices" "golang.org/x/xerrors" xsync "github.com/aquasecurity/trivy/pkg/x/sync" diff --git a/pkg/module/module.go b/pkg/module/module.go index a37790941f79..69eb35df2c0b 100644 --- a/pkg/module/module.go +++ b/pkg/module/module.go @@ -8,13 +8,13 @@ import ( "os" "path/filepath" "regexp" + "slices" "sync" "github.com/samber/lo" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" wasi "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" diff --git a/pkg/rekor/client.go b/pkg/rekor/client.go index d7b0a35dd781..d748166d6d7f 100644 --- a/pkg/rekor/client.go +++ b/pkg/rekor/client.go @@ -3,6 +3,7 @@ package rekor import ( "context" "net/url" + "slices" httptransport "github.com/go-openapi/runtime/client" "github.com/go-openapi/strfmt" @@ -10,7 +11,6 @@ import ( eclient "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/client/index" "github.com/sigstore/rekor/pkg/generated/models" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/log" diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 51ee946e3814..8bfa75922013 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -6,10 +6,10 @@ import ( "io" "os" "runtime" + "slices" "strings" "github.com/fatih/color" - "golang.org/x/exp/slices" "github.com/aquasecurity/table" "github.com/aquasecurity/tml" diff --git a/pkg/report/table/vulnerability.go b/pkg/report/table/vulnerability.go index 562f667c8498..03435fd92b96 100644 --- a/pkg/report/table/vulnerability.go +++ b/pkg/report/table/vulnerability.go @@ -4,14 +4,13 @@ import ( "bytes" "fmt" "path/filepath" + "slices" "sort" "strings" "sync" "github.com/samber/lo" "github.com/xlab/treeprint" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" "github.com/aquasecurity/table" "github.com/aquasecurity/tml" @@ -288,7 +287,7 @@ func addParents(topItem treeprint.Tree, pkg ftypes.Package, parentMap map[string } // Omitted - rootIDs := lo.Filter(maps.Keys(roots), func(pkgID string, _ int) bool { + rootIDs := lo.Filter(lo.Keys(roots), func(pkgID string, _ int) bool { _, ok := seen[pkgID] return !ok }) @@ -338,7 +337,7 @@ func findAncestor(pkgID string, parentMap map[string]ftypes.Packages, seen map[s } } } - return maps.Keys(ancestors) + return lo.Keys(ancestors) } var jarExtensions = []string{ diff --git a/pkg/result/filter.go b/pkg/result/filter.go index 936ad8272b12..7d4ead524ccc 100644 --- a/pkg/result/filter.go +++ b/pkg/result/filter.go @@ -5,12 +5,11 @@ import ( "fmt" "os" "path/filepath" + "slices" "sort" "github.com/open-policy-agent/opa/rego" "github.com/samber/lo" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" "golang.org/x/xerrors" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" @@ -135,7 +134,7 @@ func filterVulnerabilities(result *types.Result, severities []string, ignoreStat } // Override the detected vulnerabilities - result.Vulnerabilities = maps.Values(uniqVulns) + result.Vulnerabilities = lo.Values(uniqVulns) if len(result.Vulnerabilities) == 0 { result.Vulnerabilities = nil } diff --git a/pkg/sbom/io/decode.go b/pkg/sbom/io/decode.go index 917684962d20..707ed9a4c8dc 100644 --- a/pkg/sbom/io/decode.go +++ b/pkg/sbom/io/decode.go @@ -10,7 +10,7 @@ import ( debver "github.com/knqyf263/go-deb-version" rpmver "github.com/knqyf263/go-rpm-version" "github.com/package-url/packageurl-go" - "golang.org/x/exp/maps" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency" @@ -363,7 +363,7 @@ func (m *Decoder) addOrphanPkgs(sbom *types.SBOM) error { } if len(osPkgMap) > 1 { - return xerrors.Errorf("multiple types of OS packages in SBOM are not supported (%q)", maps.Keys(osPkgMap)) + return xerrors.Errorf("multiple types of OS packages in SBOM are not supported (%q)", lo.Keys(osPkgMap)) } // Add OS packages only when OS is detected. diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index 5923a5b34800..1c4a84d60109 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -3,6 +3,7 @@ package spdx import ( "context" "fmt" + "slices" "sort" "strings" "time" @@ -13,7 +14,6 @@ import ( "github.com/spdx/tools-golang/spdx" "github.com/spdx/tools-golang/spdx/v2/common" spdxutils "github.com/spdx/tools-golang/utils" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/clock" diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 9a73ad97dbfa..475fc1540086 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -4,13 +4,13 @@ import ( "context" "errors" "fmt" + "slices" "sort" "strings" "sync" "github.com/google/wire" "github.com/samber/lo" - "golang.org/x/exp/slices" "golang.org/x/xerrors" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" diff --git a/pkg/types/target.go b/pkg/types/target.go index 26386b9d2429..bb2dce90fc52 100644 --- a/pkg/types/target.go +++ b/pkg/types/target.go @@ -1,7 +1,7 @@ package types import ( - "golang.org/x/exp/slices" + "slices" ) // VulnType represents vulnerability type diff --git a/pkg/utils/fsutils/fs.go b/pkg/utils/fsutils/fs.go index 6d0502d8cc18..c4f9c75cc9fc 100644 --- a/pkg/utils/fsutils/fs.go +++ b/pkg/utils/fsutils/fs.go @@ -7,8 +7,8 @@ import ( "io/fs" "os" "path/filepath" + "slices" - "golang.org/x/exp/slices" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/log" diff --git a/pkg/x/path/path.go b/pkg/x/path/path.go index 8bf5e335365b..d7c66ecadc8b 100644 --- a/pkg/x/path/path.go +++ b/pkg/x/path/path.go @@ -1,9 +1,8 @@ package path import ( + "slices" "strings" - - "golang.org/x/exp/slices" ) // Contains reports whether the path contains the subpath. From e493fc931a2e55888a84b7db51f3dd6f01028c9b Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 20 Jun 2024 08:51:57 +0400 Subject: [PATCH 177/352] refactor: delete db mock (#6940) Signed-off-by: knqyf263 --- integration/integration_test.go | 3 +- {pkg => internal}/dbtest/db.go | 0 internal/dbtest/fake.go | 84 +++++++++ pkg/commands/operation/operation.go | 2 +- pkg/db/db.go | 34 +--- pkg/db/db_test.go | 98 +++------- pkg/db/mock_operation.go | 126 ------------- pkg/detector/library/driver_test.go | 2 +- pkg/detector/ospkg/alma/alma_test.go | 2 +- pkg/detector/ospkg/alpine/alpine_test.go | 2 +- pkg/detector/ospkg/amazon/amazon_test.go | 2 +- .../ospkg/chainguard/chainguard_test.go | 2 +- pkg/detector/ospkg/debian/debian_test.go | 2 +- pkg/detector/ospkg/mariner/mariner_test.go | 2 +- pkg/detector/ospkg/oracle/oracle_test.go | 2 +- pkg/detector/ospkg/photon/photon_test.go | 2 +- pkg/detector/ospkg/redhat/redhat_test.go | 2 +- pkg/detector/ospkg/rocky/rocky_test.go | 2 +- pkg/detector/ospkg/suse/suse_test.go | 2 +- pkg/detector/ospkg/ubuntu/ubuntu_test.go | 2 +- pkg/detector/ospkg/wolfi/wolfi_test.go | 2 +- pkg/rpc/server/listen.go | 6 +- pkg/rpc/server/listen_test.go | 178 ++++++------------ pkg/rpc/server/testdata/metadata.json | 1 - pkg/rpc/server/testdata/newdb/metadata.json | 1 + .../testdata/{new.db => newdb/trivy.db} | Bin pkg/scanner/local/scan_test.go | 2 +- pkg/vulnerability/vulnerability_test.go | 2 +- 28 files changed, 194 insertions(+), 371 deletions(-) rename {pkg => internal}/dbtest/db.go (100%) create mode 100644 internal/dbtest/fake.go delete mode 100644 pkg/db/mock_operation.go delete mode 100644 pkg/rpc/server/testdata/metadata.json create mode 100644 pkg/rpc/server/testdata/newdb/metadata.json rename pkg/rpc/server/testdata/{new.db => newdb/trivy.db} (100%) diff --git a/integration/integration_test.go b/integration/integration_test.go index aeb91dfe783e..e9d534da3e06 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -28,9 +28,10 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/metadata" + + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/commands" - "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/uuid" diff --git a/pkg/dbtest/db.go b/internal/dbtest/db.go similarity index 100% rename from pkg/dbtest/db.go rename to internal/dbtest/db.go diff --git a/internal/dbtest/fake.go b/internal/dbtest/fake.go new file mode 100644 index 000000000000..9f2484bbf94a --- /dev/null +++ b/internal/dbtest/fake.go @@ -0,0 +1,84 @@ +package dbtest + +import ( + "archive/tar" + "os" + "path/filepath" + "testing" + + v1 "github.com/google/go-containerregistry/pkg/v1" + fakei "github.com/google/go-containerregistry/pkg/v1/fake" + "github.com/google/go-containerregistry/pkg/v1/tarball" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/samber/lo" + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/oci" +) + +const defaultMediaType = "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip" + +type fakeLayer struct { + v1.Layer +} + +func (f fakeLayer) MediaType() (types.MediaType, error) { + return f.Layer.MediaType() +} + +func NewFakeLayer(t *testing.T, input string, mediaType types.MediaType) v1.Layer { + layer, err := tarball.LayerFromFile(input, tarball.WithMediaType(mediaType)) + require.NoError(t, err) + + return fakeLayer{layer} +} + +type FakeDBOptions struct { + MediaType types.MediaType +} + +func NewFakeDB(t *testing.T, dbPath string, opts FakeDBOptions) *oci.Artifact { + mediaType := lo.Ternary(opts.MediaType != "", opts.MediaType, defaultMediaType) + img := new(fakei.FakeImage) + img.LayersReturns([]v1.Layer{NewFakeLayer(t, dbPath, mediaType)}, nil) + img.ManifestReturns(&v1.Manifest{ + Layers: []v1.Descriptor{ + { + MediaType: mediaType, + Size: 100, + Digest: v1.Hash{ + Algorithm: "sha256", + Hex: "aec482bc254b5dd025d3eaf5bb35997d3dba783e394e8f91d5a415963151bfb8", + }, + Annotations: map[string]string{ + "org.opencontainers.image.title": "db.tar.gz", + }, + }, + }, + }, nil) + + // Mock OCI artifact + opt := ftypes.RegistryOptions{ + Insecure: false, + } + art, err := oci.NewArtifact("dummy", true, opt, oci.WithImage(img)) + require.NoError(t, err) + + return art +} + +func ArchiveDir(t *testing.T, dir string) string { + tmpDBPath := filepath.Join(t.TempDir(), "db.tar") + f, err := os.Create(tmpDBPath) + require.NoError(t, err) + defer f.Close() + + tr := tar.NewWriter(f) + defer tr.Close() + + err = tr.AddFS(os.DirFS(dir)) + require.NoError(t, err) + + return tmpDBPath +} diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index 84783ba073ab..e315405b50b9 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -117,7 +117,7 @@ func DownloadDB(ctx context.Context, appVersion, cacheDir string, dbRepository n defer mu.Unlock() client := db.NewClient(cacheDir, quiet, db.WithDBRepository(dbRepository)) - needsUpdate, err := client.NeedsUpdate(appVersion, skipUpdate) + needsUpdate, err := client.NeedsUpdate(ctx, appVersion, skipUpdate) if err != nil { return xerrors.Errorf("database error: %w", err) } diff --git a/pkg/db/db.go b/pkg/db/db.go index 5ac539f203e8..bdb8b34dbb4e 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -9,10 +9,10 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "golang.org/x/xerrors" - "k8s.io/utils/clock" "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/metadata" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/oci" @@ -28,15 +28,8 @@ var ( defaultRepository, _ = name.NewTag(DefaultRepository) ) -// Operation defines the DB operations -type Operation interface { - NeedsUpdate(cliVersion string, skip bool) (need bool, err error) - Download(ctx context.Context, dst string, opt types.RegistryOptions) (err error) -} - type options struct { artifact *oci.Artifact - clock clock.Clock dbRepository name.Reference } @@ -57,13 +50,6 @@ func WithDBRepository(dbRepository name.Reference) Option { } } -// WithClock takes a clock -func WithClock(c clock.Clock) Option { - return func(opts *options) { - opts.clock = c - } -} - // Client implements DB operations type Client struct { *options @@ -76,7 +62,6 @@ type Client struct { // NewClient is the factory method for DB client func NewClient(cacheDir string, quiet bool, opts ...Option) *Client { o := &options{ - clock: clock.RealClock{}, dbRepository: defaultRepository, } @@ -93,7 +78,7 @@ func NewClient(cacheDir string, quiet bool, opts ...Option) *Client { } // NeedsUpdate check is DB needs update -func (c *Client) NeedsUpdate(cliVersion string, skip bool) (bool, error) { +func (c *Client) NeedsUpdate(ctx context.Context, cliVersion string, skip bool) (bool, error) { meta, err := c.metadata.Get() if err != nil { log.Debug("There is no valid metadata file", log.Err(err)) @@ -124,7 +109,7 @@ func (c *Client) NeedsUpdate(cliVersion string, skip bool) (bool, error) { return true, nil } - return !c.isNewDB(meta), nil + return !c.isNewDB(ctx, meta), nil } func (c *Client) validate(meta metadata.Metadata) error { @@ -136,13 +121,14 @@ func (c *Client) validate(meta metadata.Metadata) error { return nil } -func (c *Client) isNewDB(meta metadata.Metadata) bool { - if c.clock.Now().Before(meta.NextUpdate) { +func (c *Client) isNewDB(ctx context.Context, meta metadata.Metadata) bool { + now := clock.Now(ctx) + if now.Before(meta.NextUpdate) { log.Debug("DB update was skipped because the local DB is the latest") return true } - if c.clock.Now().Before(meta.DownloadedAt.Add(time.Hour)) { + if now.Before(meta.DownloadedAt.Add(time.Hour)) { log.Debug("DB update was skipped because the local DB was downloaded during the last hour") return true } @@ -165,13 +151,13 @@ func (c *Client) Download(ctx context.Context, dst string, opt types.RegistryOpt return xerrors.Errorf("database download error: %w", err) } - if err = c.updateDownloadedAt(dst); err != nil { + if err = c.updateDownloadedAt(ctx, dst); err != nil { return xerrors.Errorf("failed to update downloaded_at: %w", err) } return nil } -func (c *Client) updateDownloadedAt(dst string) error { +func (c *Client) updateDownloadedAt(ctx context.Context, dst string) error { log.Debug("Updating database metadata...") // We have to initialize a metadata client here @@ -182,7 +168,7 @@ func (c *Client) updateDownloadedAt(dst string) error { return xerrors.Errorf("unable to get metadata: %w", err) } - meta.DownloadedAt = c.clock.Now().UTC() + meta.DownloadedAt = clock.Now(ctx).UTC() if err = client.Update(meta); err != nil { return xerrors.Errorf("failed to update metadata: %w", err) } diff --git a/pkg/db/db_test.go b/pkg/db/db_test.go index e627a684a696..d7eca907fe32 100644 --- a/pkg/db/db_test.go +++ b/pkg/db/db_test.go @@ -6,39 +6,17 @@ import ( "testing" "time" - v1 "github.com/google/go-containerregistry/pkg/v1" - fakei "github.com/google/go-containerregistry/pkg/v1/fake" - "github.com/google/go-containerregistry/pkg/v1/tarball" - "github.com/google/go-containerregistry/pkg/v1/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "k8s.io/utils/clock" - clocktesting "k8s.io/utils/clock/testing" tdb "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/metadata" + "github.com/aquasecurity/trivy/internal/dbtest" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/db" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/oci" ) -const mediaType = "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip" - -type fakeLayer struct { - v1.Layer -} - -func (f fakeLayer) MediaType() (types.MediaType, error) { - return mediaType, nil -} - -func newFakeLayer(t *testing.T, input string) v1.Layer { - layer, err := tarball.LayerFromFile(input) - require.NoError(t, err) - - return fakeLayer{layer} -} - func TestClient_NeedsUpdate(t *testing.T) { timeNextUpdateDay1 := time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC) timeNextUpdateDay2 := time.Date(2019, 10, 2, 0, 0, 0, 0, time.UTC) @@ -46,14 +24,12 @@ func TestClient_NeedsUpdate(t *testing.T) { tests := []struct { name string skip bool - clock clock.Clock metadata metadata.Metadata want bool wantErr string }{ { - name: "happy path", - clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + name: "happy path", metadata: metadata.Metadata{ Version: tdb.SchemaVersion, NextUpdate: timeNextUpdateDay1, @@ -62,13 +38,11 @@ func TestClient_NeedsUpdate(t *testing.T) { }, { name: "happy path for first run", - clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), metadata: metadata.Metadata{}, want: true, }, { - name: "happy path with old schema version", - clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + name: "happy path with old schema version", metadata: metadata.Metadata{ Version: 0, NextUpdate: timeNextUpdateDay1, @@ -76,8 +50,7 @@ func TestClient_NeedsUpdate(t *testing.T) { want: true, }, { - name: "happy path with --skip-update", - clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + name: "happy path with --skip-update", metadata: metadata.Metadata{ Version: tdb.SchemaVersion, NextUpdate: timeNextUpdateDay1, @@ -86,8 +59,7 @@ func TestClient_NeedsUpdate(t *testing.T) { want: false, }, { - name: "skip downloading DB", - clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + name: "skip downloading DB", metadata: metadata.Metadata{ Version: tdb.SchemaVersion, NextUpdate: timeNextUpdateDay2, @@ -95,8 +67,7 @@ func TestClient_NeedsUpdate(t *testing.T) { want: false, }, { - name: "newer schema version", - clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + name: "newer schema version", metadata: metadata.Metadata{ Version: tdb.SchemaVersion + 1, NextUpdate: timeNextUpdateDay2, @@ -106,14 +77,12 @@ func TestClient_NeedsUpdate(t *testing.T) { }, { name: "--skip-update on the first run", - clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), metadata: metadata.Metadata{}, skip: true, wantErr: "--skip-update cannot be specified on the first run", }, { - name: "--skip-update with different schema version", - clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + name: "--skip-update with different schema version", metadata: metadata.Metadata{ Version: 0, NextUpdate: timeNextUpdateDay1, @@ -123,8 +92,7 @@ func TestClient_NeedsUpdate(t *testing.T) { 0, tdb.SchemaVersion), }, { - name: "happy with old DownloadedAt", - clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + name: "happy with old DownloadedAt", metadata: metadata.Metadata{ Version: tdb.SchemaVersion, NextUpdate: timeNextUpdateDay1, @@ -133,8 +101,7 @@ func TestClient_NeedsUpdate(t *testing.T) { want: true, }, { - name: "skip downloading DB with recent DownloadedAt", - clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)), + name: "skip downloading DB with recent DownloadedAt", metadata: metadata.Metadata{ Version: tdb.SchemaVersion, NextUpdate: timeNextUpdateDay1, @@ -153,8 +120,11 @@ func TestClient_NeedsUpdate(t *testing.T) { require.NoError(t, err) } - client := db.NewClient(cacheDir, true, db.WithClock(tt.clock)) - needsUpdate, err := client.NeedsUpdate("test", tt.skip) + // Set a fake time + ctx := clock.With(context.Background(), time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)) + + client := db.NewClient(cacheDir, true) + needsUpdate, err := client.NeedsUpdate(ctx, "test", tt.skip) switch { case tt.wantErr != "": @@ -170,7 +140,6 @@ func TestClient_NeedsUpdate(t *testing.T) { } func TestClient_Download(t *testing.T) { - timeDownloadedAt := clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)) tests := []struct { name string @@ -197,39 +166,18 @@ func TestClient_Download(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cacheDir := t.TempDir() + // Set a fake time + ctx := clock.With(context.Background(), time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)) - // Mock image - img := new(fakei.FakeImage) - img.LayersReturns([]v1.Layer{newFakeLayer(t, tt.input)}, nil) - img.ManifestReturns(&v1.Manifest{ - Layers: []v1.Descriptor{ - { - MediaType: "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip", - Size: 100, - Digest: v1.Hash{ - Algorithm: "sha256", - Hex: "aec482bc254b5dd025d3eaf5bb35997d3dba783e394e8f91d5a415963151bfb8", - }, - Annotations: map[string]string{ - "org.opencontainers.image.title": "db.tar.gz", - }, - }, - }, - }, nil) - - // Mock OCI artifact - opt := ftypes.RegistryOptions{ - Insecure: false, - } - art, err := oci.NewArtifact("db", true, opt, oci.WithImage(img)) - require.NoError(t, err) + // Fake DB + art := dbtest.NewFakeDB(t, tt.input, dbtest.FakeDBOptions{}) - client := db.NewClient(cacheDir, true, db.WithOCIArtifact(art), db.WithClock(timeDownloadedAt)) - err = client.Download(context.Background(), cacheDir, opt) + cacheDir := t.TempDir() + client := db.NewClient(cacheDir, true, db.WithOCIArtifact(art)) + err := client.Download(ctx, cacheDir, ftypes.RegistryOptions{}) if tt.wantErr != "" { require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr) + assert.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) diff --git a/pkg/db/mock_operation.go b/pkg/db/mock_operation.go deleted file mode 100644 index b5a879fb5afc..000000000000 --- a/pkg/db/mock_operation.go +++ /dev/null @@ -1,126 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package db - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - - "github.com/aquasecurity/trivy/pkg/fanal/types" -) - -// MockOperation is an autogenerated mock type for the Operation type -type MockOperation struct { - mock.Mock -} - -type OperationDownloadArgs struct { - Ctx context.Context - CtxAnything bool - Dst string - DstAnything bool -} - -type OperationDownloadReturns struct { - Err error -} - -type OperationDownloadExpectation struct { - Args OperationDownloadArgs - Returns OperationDownloadReturns -} - -func (_m *MockOperation) ApplyDownloadExpectation(e OperationDownloadExpectation) { - var args []interface{} - if e.Args.CtxAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.Ctx) - } - if e.Args.DstAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.Dst) - } - _m.On("Download", args...).Return(e.Returns.Err) -} - -func (_m *MockOperation) ApplyDownloadExpectations(expectations []OperationDownloadExpectation) { - for _, e := range expectations { - _m.ApplyDownloadExpectation(e) - } -} - -// Download provides a mock function with given fields: ctx, dst -func (_m *MockOperation) Download(ctx context.Context, dst string, opt types.RegistryOptions) error { - ret := _m.Called(ctx, dst, opt) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, dst) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -type OperationNeedsUpdateArgs struct { - CliVersion string - CliVersionAnything bool - Skip bool - SkipAnything bool -} - -type OperationNeedsUpdateReturns struct { - Need bool - Err error -} - -type OperationNeedsUpdateExpectation struct { - Args OperationNeedsUpdateArgs - Returns OperationNeedsUpdateReturns -} - -func (_m *MockOperation) ApplyNeedsUpdateExpectation(e OperationNeedsUpdateExpectation) { - var args []interface{} - if e.Args.CliVersionAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.CliVersion) - } - if e.Args.SkipAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.Skip) - } - _m.On("NeedsUpdate", args...).Return(e.Returns.Need, e.Returns.Err) -} - -func (_m *MockOperation) ApplyNeedsUpdateExpectations(expectations []OperationNeedsUpdateExpectation) { - for _, e := range expectations { - _m.ApplyNeedsUpdateExpectation(e) - } -} - -// NeedsUpdate provides a mock function with given fields: cliVersion, skip -func (_m *MockOperation) NeedsUpdate(cliVersion string, skip bool) (bool, error) { - ret := _m.Called(cliVersion, skip) - - var r0 bool - if rf, ok := ret.Get(0).(func(string, bool) bool); ok { - r0 = rf(cliVersion, skip) - } else { - r0 = ret.Get(0).(bool) - } - - var r1 error - if rf, ok := ret.Get(1).(func(string, bool) error); ok { - r1 = rf(cliVersion, skip) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} diff --git a/pkg/detector/library/driver_test.go b/pkg/detector/library/driver_test.go index e6722e841e8e..10c3ad304f29 100644 --- a/pkg/detector/library/driver_test.go +++ b/pkg/detector/library/driver_test.go @@ -9,7 +9,7 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" - "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/detector/library" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/detector/ospkg/alma/alma_test.go b/pkg/detector/ospkg/alma/alma_test.go index 9c5f3023563a..c736f4b12c74 100644 --- a/pkg/detector/ospkg/alma/alma_test.go +++ b/pkg/detector/ospkg/alma/alma_test.go @@ -11,8 +11,8 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/alma" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/detector/ospkg/alpine/alpine_test.go b/pkg/detector/ospkg/alpine/alpine_test.go index 4ea5d59024f8..9dad87c4b50d 100644 --- a/pkg/detector/ospkg/alpine/alpine_test.go +++ b/pkg/detector/ospkg/alpine/alpine_test.go @@ -12,8 +12,8 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/alpine" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/detector/ospkg/amazon/amazon_test.go b/pkg/detector/ospkg/amazon/amazon_test.go index 3ac7b55d3ad0..98adad931959 100644 --- a/pkg/detector/ospkg/amazon/amazon_test.go +++ b/pkg/detector/ospkg/amazon/amazon_test.go @@ -11,8 +11,8 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/amazon" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/detector/ospkg/chainguard/chainguard_test.go b/pkg/detector/ospkg/chainguard/chainguard_test.go index af6e81afcc84..0c49077aee3e 100644 --- a/pkg/detector/ospkg/chainguard/chainguard_test.go +++ b/pkg/detector/ospkg/chainguard/chainguard_test.go @@ -10,7 +10,7 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" - "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/chainguard" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/detector/ospkg/debian/debian_test.go b/pkg/detector/ospkg/debian/debian_test.go index fa0e334eff33..790067c293ff 100644 --- a/pkg/detector/ospkg/debian/debian_test.go +++ b/pkg/detector/ospkg/debian/debian_test.go @@ -12,8 +12,8 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/debian" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/detector/ospkg/mariner/mariner_test.go b/pkg/detector/ospkg/mariner/mariner_test.go index 7c7410f28234..6e1ee9a37583 100644 --- a/pkg/detector/ospkg/mariner/mariner_test.go +++ b/pkg/detector/ospkg/mariner/mariner_test.go @@ -9,7 +9,7 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" - "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/mariner" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/detector/ospkg/oracle/oracle_test.go b/pkg/detector/ospkg/oracle/oracle_test.go index 7e5e9b6a7f7b..6fdc73a90e6a 100644 --- a/pkg/detector/ospkg/oracle/oracle_test.go +++ b/pkg/detector/ospkg/oracle/oracle_test.go @@ -11,8 +11,8 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/dbtest" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" ) diff --git a/pkg/detector/ospkg/photon/photon_test.go b/pkg/detector/ospkg/photon/photon_test.go index ffa978adc612..8d68ea08680e 100644 --- a/pkg/detector/ospkg/photon/photon_test.go +++ b/pkg/detector/ospkg/photon/photon_test.go @@ -11,8 +11,8 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/photon" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/detector/ospkg/redhat/redhat_test.go b/pkg/detector/ospkg/redhat/redhat_test.go index 17a6a8768554..a1a62ac7666e 100644 --- a/pkg/detector/ospkg/redhat/redhat_test.go +++ b/pkg/detector/ospkg/redhat/redhat_test.go @@ -11,8 +11,8 @@ import ( dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/redhat" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" diff --git a/pkg/detector/ospkg/rocky/rocky_test.go b/pkg/detector/ospkg/rocky/rocky_test.go index e61c69076cce..ce5ad893bfc2 100644 --- a/pkg/detector/ospkg/rocky/rocky_test.go +++ b/pkg/detector/ospkg/rocky/rocky_test.go @@ -11,8 +11,8 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/rocky" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/detector/ospkg/suse/suse_test.go b/pkg/detector/ospkg/suse/suse_test.go index 663e502e717c..011fc3332b6a 100644 --- a/pkg/detector/ospkg/suse/suse_test.go +++ b/pkg/detector/ospkg/suse/suse_test.go @@ -11,8 +11,8 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/suse" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/detector/ospkg/ubuntu/ubuntu_test.go b/pkg/detector/ospkg/ubuntu/ubuntu_test.go index 750d49d4bd5f..044ea69b1056 100644 --- a/pkg/detector/ospkg/ubuntu/ubuntu_test.go +++ b/pkg/detector/ospkg/ubuntu/ubuntu_test.go @@ -12,8 +12,8 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/ubuntu" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/detector/ospkg/wolfi/wolfi_test.go b/pkg/detector/ospkg/wolfi/wolfi_test.go index 8df2c2ab0870..019d6627dc7c 100644 --- a/pkg/detector/ospkg/wolfi/wolfi_test.go +++ b/pkg/detector/ospkg/wolfi/wolfi_test.go @@ -10,7 +10,7 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" - "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/detector/ospkg/wolfi" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/rpc/server/listen.go b/pkg/rpc/server/listen.go index 802afe68ae3d..78edb5a21876 100644 --- a/pkg/rpc/server/listen.go +++ b/pkg/rpc/server/listen.go @@ -128,17 +128,17 @@ func withToken(base http.Handler, token, tokenHeader string) http.Handler { } type dbWorker struct { - dbClient dbc.Operation + dbClient *dbc.Client } -func newDBWorker(dbClient dbc.Operation) dbWorker { +func newDBWorker(dbClient *dbc.Client) dbWorker { return dbWorker{dbClient: dbClient} } func (w dbWorker) update(ctx context.Context, appVersion, cacheDir string, skipDBUpdate bool, dbUpdateWg, requestWg *sync.WaitGroup, opt types.RegistryOptions) error { log.Debug("Check for DB update...") - needsUpdate, err := w.dbClient.NeedsUpdate(appVersion, skipDBUpdate) + needsUpdate, err := w.dbClient.NeedsUpdate(ctx, appVersion, skipDBUpdate) if err != nil { return xerrors.Errorf("failed to check if db needs an update") } else if !needsUpdate { diff --git a/pkg/rpc/server/listen_test.go b/pkg/rpc/server/listen_test.go index e1ffaba23878..02cf8d0c59fd 100644 --- a/pkg/rpc/server/listen_test.go +++ b/pkg/rpc/server/listen_test.go @@ -5,166 +5,102 @@ import ( "encoding/json" "net/http" "net/http/httptest" - "os" "path" "sync" "testing" "time" + "github.com/google/go-containerregistry/pkg/v1/types" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "golang.org/x/xerrors" - "github.com/aquasecurity/trivy-db/pkg/db" + trivydb "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/metadata" - dbFile "github.com/aquasecurity/trivy/pkg/db" + "github.com/aquasecurity/trivy/internal/dbtest" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/db" "github.com/aquasecurity/trivy/pkg/fanal/cache" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/policy" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" "github.com/aquasecurity/trivy/pkg/version" rpcCache "github.com/aquasecurity/trivy/rpc/cache" ) func Test_dbWorker_update(t *testing.T) { - timeNextUpdate := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC) - timeUpdateAt := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC) - - type needsUpdateInput struct { - appVersion string - skip bool - } - type needsUpdateOutput struct { - needsUpdate bool - err error - } - type needsUpdate struct { - input needsUpdateInput - output needsUpdateOutput + cachedMetadata := metadata.Metadata{ + Version: db.SchemaVersion, + NextUpdate: time.Date(2020, 10, 2, 0, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(2020, 10, 1, 0, 0, 0, 0, time.UTC), + DownloadedAt: time.Date(2020, 10, 1, 1, 0, 0, 0, time.UTC), } - type download struct { - call bool - err error - } - - type args struct { - appVersion string - } tests := []struct { - name string - needsUpdate needsUpdate - download download - args args - want metadata.Metadata - wantErr string + name string + now time.Time + skipUpdate bool + layerMediaType types.MediaType + want metadata.Metadata + wantErr string }{ { - name: "happy path", - needsUpdate: needsUpdate{ - input: needsUpdateInput{ - appVersion: "1", - skip: false, - }, - output: needsUpdateOutput{needsUpdate: true}, - }, - download: download{ - call: true, - }, - args: args{appVersion: "1"}, + name: "update needed", + now: time.Date(2021, 10, 1, 0, 0, 0, 0, time.UTC), + skipUpdate: false, want: metadata.Metadata{ - Version: 1, - NextUpdate: timeNextUpdate, - UpdatedAt: timeUpdateAt, + Version: db.SchemaVersion, + NextUpdate: time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC), + UpdatedAt: time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC), + DownloadedAt: time.Date(2021, 10, 1, 0, 0, 0, 0, time.UTC), }, }, { - name: "not update", - needsUpdate: needsUpdate{ - input: needsUpdateInput{ - appVersion: "1", - skip: false, - }, - output: needsUpdateOutput{needsUpdate: false}, - }, - args: args{appVersion: "1"}, + name: "not update needed", + now: time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC), + skipUpdate: false, + want: cachedMetadata, }, { - name: "skip update", - needsUpdate: needsUpdate{ - input: needsUpdateInput{ - appVersion: "1", - skip: true, - }, - output: needsUpdateOutput{needsUpdate: false}, - }, - args: args{appVersion: "1"}, + name: "skip update", + now: time.Date(2021, 10, 1, 0, 0, 0, 0, time.UTC), + skipUpdate: true, + want: cachedMetadata, }, { - name: "NeedsUpdate returns an error", - needsUpdate: needsUpdate{ - input: needsUpdateInput{ - appVersion: "1", - skip: false, - }, - output: needsUpdateOutput{err: xerrors.New("fail")}, - }, - args: args{appVersion: "1"}, - wantErr: "failed to check if db needs an update", - }, - { - name: "Download returns an error", - needsUpdate: needsUpdate{ - input: needsUpdateInput{ - appVersion: "1", - skip: false, - }, - output: needsUpdateOutput{needsUpdate: true}, - }, - download: download{ - call: true, - err: xerrors.New("fail"), - }, - args: args{appVersion: "1"}, - wantErr: "failed DB hot update", + name: "Download returns an error", + now: time.Date(2021, 10, 1, 0, 0, 0, 0, time.UTC), + skipUpdate: false, + layerMediaType: types.MediaType("unknown"), + wantErr: "failed DB hot update", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cacheDir := t.TempDir() - require.NoError(t, db.Init(cacheDir), tt.name) - - mockDBClient := new(dbFile.MockOperation) - mockDBClient.On("NeedsUpdate", - tt.needsUpdate.input.appVersion, tt.needsUpdate.input.skip).Return( - tt.needsUpdate.output.needsUpdate, tt.needsUpdate.output.err) - - defer func() { _ = db.Close() }() + // Initialize the cache + meta := metadata.NewClient(cacheDir) + err := meta.Update(cachedMetadata) + require.NoError(t, err) - if tt.download.call { - mockDBClient.On("Download", mock.Anything, mock.Anything, mock.Anything).Run( - func(args mock.Arguments) { - // fake download: copy testdata/new.db to tmpDir/db/trivy.db - tmpDir := args.String(1) - err := os.MkdirAll(db.Dir(tmpDir), 0744) - require.NoError(t, err) + err = trivydb.Init(cacheDir) + require.NoError(t, err) - _, err = fsutils.CopyFile("testdata/new.db", db.Path(tmpDir)) - require.NoError(t, err) + defer func() { _ = trivydb.Close() }() - // fake download: copy testdata/metadata.json to tmpDir/db/metadata.json - _, err = fsutils.CopyFile("testdata/metadata.json", metadata.Path(tmpDir)) - require.NoError(t, err) - }).Return(tt.download.err) - } + // Set a fake time + ctx := clock.With(context.Background(), tt.now) - w := newDBWorker(mockDBClient) + // Set a fake DB + dbPath := dbtest.ArchiveDir(t, "testdata/newdb") + art := dbtest.NewFakeDB(t, dbPath, dbtest.FakeDBOptions{ + MediaType: tt.layerMediaType, + }) + client := db.NewClient(cacheDir, true, db.WithOCIArtifact(art)) + w := newDBWorker(client) var dbUpdateWg, requestWg sync.WaitGroup - err := w.update(context.Background(), tt.args.appVersion, cacheDir, - tt.needsUpdate.input.skip, &dbUpdateWg, &requestWg, ftypes.RegistryOptions{}) + err = w.update(ctx, "1.2.3", cacheDir, + tt.skipUpdate, &dbUpdateWg, &requestWg, ftypes.RegistryOptions{}) if tt.wantErr != "" { require.Error(t, err, tt.name) assert.Contains(t, err.Error(), tt.wantErr, tt.name) @@ -172,16 +108,10 @@ func Test_dbWorker_update(t *testing.T) { } require.NoError(t, err, tt.name) - if !tt.download.call { - return - } - mc := metadata.NewClient(cacheDir) got, err := mc.Get() require.NoError(t, err, tt.name) assert.Equal(t, tt.want, got, tt.name) - - mockDBClient.AssertExpectations(t) }) } } diff --git a/pkg/rpc/server/testdata/metadata.json b/pkg/rpc/server/testdata/metadata.json deleted file mode 100644 index dfc2957b6295..000000000000 --- a/pkg/rpc/server/testdata/metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"Version":1,"NextUpdate":"3000-01-01T0:00:00.0Z","UpdatedAt":"3000-01-01T0:00:00.0Z"} \ No newline at end of file diff --git a/pkg/rpc/server/testdata/newdb/metadata.json b/pkg/rpc/server/testdata/newdb/metadata.json new file mode 100644 index 000000000000..bb57f21428f4 --- /dev/null +++ b/pkg/rpc/server/testdata/newdb/metadata.json @@ -0,0 +1 @@ +{"Version":2,"NextUpdate":"3000-01-01T0:00:00.0Z","UpdatedAt":"3000-01-01T0:00:00.0Z"} \ No newline at end of file diff --git a/pkg/rpc/server/testdata/new.db b/pkg/rpc/server/testdata/newdb/trivy.db similarity index 100% rename from pkg/rpc/server/testdata/new.db rename to pkg/rpc/server/testdata/newdb/trivy.db diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go index 42a189ba5d68..f0d154a3904d 100644 --- a/pkg/scanner/local/scan_test.go +++ b/pkg/scanner/local/scan_test.go @@ -12,7 +12,7 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/scanner/langpkg" diff --git a/pkg/vulnerability/vulnerability_test.go b/pkg/vulnerability/vulnerability_test.go index 11dac691503e..d377ef6e4723 100644 --- a/pkg/vulnerability/vulnerability_test.go +++ b/pkg/vulnerability/vulnerability_test.go @@ -9,7 +9,7 @@ import ( dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/utils" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" - "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/types" vuln "github.com/aquasecurity/trivy/pkg/vulnerability" ) From 30bcb95350097b322c79e1e16a886a22f3dda42b Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 20 Jun 2024 14:41:43 +0400 Subject: [PATCH 178/352] refactor: use version-specific URLs for documentation references (#6966) Signed-off-by: knqyf263 --- pkg/cloud/aws/commands/run.go | 5 +- pkg/commands/artifact/run.go | 28 ++------- pkg/commands/artifact/run_test.go | 48 --------------- pkg/db/db.go | 4 +- pkg/k8s/commands/run.go | 5 +- pkg/version/app/version.go | 4 +- pkg/version/doc/doc.go | 49 +++++++++++++++ pkg/version/doc/doc_test.go | 96 ++++++++++++++++++++++++++++++ pkg/vulnerability/vulnerability.go | 4 +- 9 files changed, 165 insertions(+), 78 deletions(-) delete mode 100644 pkg/commands/artifact/run_test.go create mode 100644 pkg/version/doc/doc.go create mode 100644 pkg/version/doc/doc_test.go diff --git a/pkg/cloud/aws/commands/run.go b/pkg/cloud/aws/commands/run.go index b9a1bbb2bfce..fac103feaf22 100644 --- a/pkg/cloud/aws/commands/run.go +++ b/pkg/cloud/aws/commands/run.go @@ -3,6 +3,7 @@ package commands import ( "context" "errors" + "fmt" "slices" "sort" "strings" @@ -20,6 +21,7 @@ import ( "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/version/doc" ) var allSupportedServicesFunc = awsScanner.AllSupportedServices @@ -140,7 +142,8 @@ func Run(ctx context.Context, opt flag.Options) error { var err error defer func() { if errors.Is(err, context.DeadlineExceeded) { - log.Warn("Provide a higher timeout value, see https://aquasecurity.github.io/trivy/latest/docs/configuration/") + // e.g. https://aquasecurity.github.io/trivy/latest/docs/configuration/ + log.WarnContext(ctx, fmt.Sprintf("Provide a higher timeout value, see %s", doc.URL("/docs/configuration/", ""))) } }() diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 30058ff6c4cb..b430fdc1efc9 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -11,7 +11,6 @@ import ( "github.com/spf13/viper" "golang.org/x/xerrors" - "github.com/aquasecurity/go-version/pkg/semver" "github.com/aquasecurity/trivy-db/pkg/db" tcache "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/commands/operation" @@ -33,6 +32,7 @@ import ( "github.com/aquasecurity/trivy/pkg/scanner" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/utils/fsutils" + "github.com/aquasecurity/trivy/pkg/version/doc" ) // TargetKind represents what kind of artifact Trivy scans @@ -46,8 +46,6 @@ const ( TargetImageArchive TargetKind = "archive" TargetSBOM TargetKind = "sbom" TargetVM TargetKind = "vm" - - devVersion = "dev" ) var ( @@ -397,7 +395,8 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err defer func() { if errors.Is(err, context.DeadlineExceeded) { - log.Warn("Provide a higher timeout value, see https://aquasecurity.github.io/trivy/latest/docs/configuration/") + // e.g. https://aquasecurity.github.io/trivy/latest/docs/configuration/ + log.WarnContext(ctx, fmt.Sprintf("Provide a higher timeout value, see %s", doc.URL("/docs/configuration/", ""))) } }() @@ -593,10 +592,10 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi // Do not load config file for secret scanning if opts.Scanners.Enabled(types.SecretScanner) { - ver := canonicalVersion(opts.AppVersion) log.Info("Secret scanning is enabled") log.Info("If your scanning is slow, please try '--scanners vuln' to disable secret scanning") - log.Infof("Please see also https://aquasecurity.github.io/trivy/%s/docs/scanner/secret/#recommendation for faster secret detection", ver) + // e.g. https://aquasecurity.github.io/trivy/latest/docs/scanner/secret/#recommendation + log.Infof("Please see also %s for faster secret detection", doc.URL("/docs/scanner/secret/", "recommendation")) } else { opts.SecretConfigPath = "" } @@ -694,20 +693,3 @@ func scan(ctx context.Context, opts flag.Options, initializeScanner InitializeSc } return report, nil } - -func canonicalVersion(ver string) string { - if ver == devVersion { - return ver - } - v, err := semver.Parse(ver) - if err != nil { - return devVersion - } - // Replace pre-release with "dev" - // e.g. v0.34.0-beta1+snapshot-1 - if v.IsPreRelease() || v.Metadata() != "" { - return devVersion - } - // Add "v" prefix and cut a patch number, "0.34.0" => "v0.34" for the url - return fmt.Sprintf("v%d.%d", v.Major(), v.Minor()) -} diff --git a/pkg/commands/artifact/run_test.go b/pkg/commands/artifact/run_test.go deleted file mode 100644 index 02d35a53d44b..000000000000 --- a/pkg/commands/artifact/run_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package artifact - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestCanonicalVersion(t *testing.T) { - tests := []struct { - title string - input string - want string - }{ - { - title: "good way", - input: "0.34.0", - want: "v0.34", - }, - { - title: "version with v - isn't right semver version", - input: "v0.34.0", - want: devVersion, - }, - { - title: "dev version", - input: devVersion, - want: devVersion, - }, - { - title: "pre-release", - input: "v0.34.0-beta1+snapshot-1", - want: devVersion, - }, - { - title: "no version", - input: "", - want: devVersion, - }, - } - - for _, test := range tests { - t.Run(test.title, func(t *testing.T) { - got := canonicalVersion(test.input) - require.Equal(t, test.want, got) - }) - } -} diff --git a/pkg/db/db.go b/pkg/db/db.go index bdb8b34dbb4e..a006404d2c83 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -16,6 +16,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/oci" + "github.com/aquasecurity/trivy/pkg/version/doc" ) const ( @@ -188,7 +189,8 @@ func (c *Client) initOCIArtifact(opt types.RegistryOptions) (*oci.Artifact, erro for _, diagnostic := range terr.Errors { // For better user experience if diagnostic.Code == transport.DeniedErrorCode || diagnostic.Code == transport.UnauthorizedErrorCode { - log.Warn("See https://aquasecurity.github.io/trivy/latest/docs/references/troubleshooting/#db") + // e.g. https://aquasecurity.github.io/trivy/latest/docs/references/troubleshooting/#db + log.Warnf("See %s", doc.URL("/docs/references/troubleshooting/", "db")) break } } diff --git a/pkg/k8s/commands/run.go b/pkg/k8s/commands/run.go index 7c34bb4feb2a..6a20d04aee10 100644 --- a/pkg/k8s/commands/run.go +++ b/pkg/k8s/commands/run.go @@ -3,6 +3,7 @@ package commands import ( "context" "errors" + "fmt" "github.com/spf13/viper" "golang.org/x/xerrors" @@ -18,6 +19,7 @@ import ( "github.com/aquasecurity/trivy/pkg/k8s/scanner" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/version/doc" ) // Run runs a k8s scan @@ -39,7 +41,8 @@ func Run(ctx context.Context, args []string, opts flag.Options) error { defer func() { cancel() if errors.Is(err, context.DeadlineExceeded) { - log.WarnContext(ctx, "Provide a higher timeout value, see https://aquasecurity.github.io/trivy/latest/docs/configuration/") + // e.g. https://aquasecurity.github.io/trivy/latest/docs/configuration + log.WarnContext(ctx, fmt.Sprintf("Provide a higher timeout value, see %s", doc.URL("/docs/configuration/", ""))) } }() opts.K8sVersion = cluster.GetClusterVersion() diff --git a/pkg/version/app/version.go b/pkg/version/app/version.go index 8a7013078c9a..d1c7bdbe7d3d 100644 --- a/pkg/version/app/version.go +++ b/pkg/version/app/version.go @@ -1,8 +1,6 @@ package app -var ( - ver = "dev" -) +var ver = "dev" func Version() string { return ver diff --git a/pkg/version/doc/doc.go b/pkg/version/doc/doc.go new file mode 100644 index 000000000000..c02dc1e7655a --- /dev/null +++ b/pkg/version/doc/doc.go @@ -0,0 +1,49 @@ +package doc + +import ( + "fmt" + "net/url" + "path" + "strings" + + "github.com/aquasecurity/go-version/pkg/semver" + "github.com/aquasecurity/trivy/pkg/version/app" +) + +const devVersion = "dev" + +// BaseURL returns the base URL for the versioned documentation +func BaseURL(ver string) *url.URL { + ver = canonicalVersion(ver) + return &url.URL{ + Scheme: "https", + Host: "aquasecurity.github.io", + Path: path.Join("trivy", ver), + } +} + +// URL returns the URL for the versioned documentation with the given path +func URL(rawPath, fragment string) string { + base := BaseURL(app.Version()) + base.Path = path.Join(base.Path, rawPath) + base.Fragment = fragment + return base.String() +} + +func canonicalVersion(ver string) string { + if ver == devVersion { + return ver + } + ver = strings.TrimPrefix(ver, "v") + v, err := semver.Parse(ver) + if err != nil { + return devVersion + } + // Replace pre-release with "dev" + // e.g. v0.34.0-beta1+snapshot-1 + if v.IsPreRelease() || v.Metadata() != "" { + return devVersion + } + // Add "v" prefix and cut a patch number, "0.34.0" => "v0.34" for the URL + return fmt.Sprintf("v%d.%d", v.Major(), v.Minor()) +} diff --git a/pkg/version/doc/doc_test.go b/pkg/version/doc/doc_test.go new file mode 100644 index 000000000000..3107172962eb --- /dev/null +++ b/pkg/version/doc/doc_test.go @@ -0,0 +1,96 @@ +package doc_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/version/doc" +) + +func TestBaseURL(t *testing.T) { + tests := []struct { + name string + ver string + want string + }{ + { + name: "dev", + ver: "dev", + want: "https://aquasecurity.github.io/trivy/dev", + }, + { + name: "semver", + ver: "0.52.0", + want: "https://aquasecurity.github.io/trivy/v0.52", + }, + { + name: "with v prefix", + ver: "v0.52.0", + want: "https://aquasecurity.github.io/trivy/v0.52", + }, + { + name: "pre-release", + ver: "0.52.0-beta1", + want: "https://aquasecurity.github.io/trivy/dev", + }, + { + name: "non-semver", + ver: "1", + want: "https://aquasecurity.github.io/trivy/dev", + }, + { + name: "empty", + ver: "", + want: "https://aquasecurity.github.io/trivy/dev", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := doc.BaseURL(tt.ver) + require.Equal(t, tt.want, got.String()) + }) + } +} + +func TestURL(t *testing.T) { + tests := []struct { + name string + rawPath string + fragment string + want string + }{ + { + name: "path without slash", + rawPath: "foo", + want: "https://aquasecurity.github.io/trivy/dev/foo", + }, + { + name: "path with leading slash", + rawPath: "/foo", + want: "https://aquasecurity.github.io/trivy/dev/foo", + }, + { + name: "path with slash", + rawPath: "foo/bar", + want: "https://aquasecurity.github.io/trivy/dev/foo/bar", + }, + { + name: "path with fragment", + rawPath: "foo", + fragment: "bar", + want: "https://aquasecurity.github.io/trivy/dev/foo#bar", + }, + { + name: "empty", + rawPath: "", + want: "https://aquasecurity.github.io/trivy/dev", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := doc.URL(tt.rawPath, tt.fragment) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/vulnerability/vulnerability.go b/pkg/vulnerability/vulnerability.go index a77c93a87a1a..6c1e35427a64 100644 --- a/pkg/vulnerability/vulnerability.go +++ b/pkg/vulnerability/vulnerability.go @@ -12,6 +12,7 @@ import ( "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/version/doc" ) var ( @@ -51,7 +52,8 @@ var SuperSet = wire.NewSet( // Show warning if we use severity from another vendor // cf. https://github.com/aquasecurity/trivy/issues/6714 var onceWarn = sync.OnceFunc(func() { - log.Warn("Using severities from other vendors for some vulnerabilities. Read https://aquasecurity.github.io/trivy/latest/docs/scanner/vulnerability/#severity-selection for details.") + // e.g. https://aquasecurity.github.io/trivy/latest/docs/scanner/vulnerability/#severity-selection + log.Warnf("Using severities from other vendors for some vulnerabilities. Read %s for details.", doc.URL("/docs/scanner/vulnerability/", "severity-selection")) }) // Client manipulates vulnerabilities From 6469d37cceb711456680700269aa209194e18b31 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 20 Jun 2024 16:25:23 +0400 Subject: [PATCH 179/352] docs: delete unknown URL (#6972) Signed-off-by: knqyf263 --- docs/getting-started/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 51ea9e207214..f92d23e43997 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -163,7 +163,7 @@ The plugin used by both tools is developped [here](https://github.com/zufardhiya ### Download Binary -1. Download the file for your operating system/architecture from [GitHub Release assets](https://github.com/aquasecurity/trivy/releases/tag/{{ git.tag }}) (`curl -LO https://url.to/trivy.tar.gz`). +1. Download the file for your operating system/architecture from [GitHub Release assets](https://github.com/aquasecurity/trivy/releases/tag/{{ git.tag }}). 2. Unpack the downloaded archive (`tar -xzf ./trivy.tar.gz`). 3. Put the binary somewhere in your `$PATH` (e.g `mv ./trivy /usr/local/bin/`). 4. Make sure the binary has execution bit turned on (`chmod +x ./trivy`). From b58d42dc97c8a000bafccd75e5faa26ce9e84b5b Mon Sep 17 00:00:00 2001 From: simar7 <1254783+simar7@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:56:46 -0600 Subject: [PATCH 180/352] BREAKING(aws): Deprecate `trivy aws` as subcmd in favour of a plugin (#6819) --- .../references/configuration/cli/trivy.md | 1 - .../references/configuration/cli/trivy_aws.md | 127 -- go.mod | 63 +- go.sum | 123 +- integration/aws_cloud_test.go | 78 - mkdocs.yml | 1 - pkg/cloud/aws/cache/cache.go | 132 -- pkg/cloud/aws/commands/run.go | 183 --- pkg/cloud/aws/commands/run_test.go | 1284 ----------------- pkg/cloud/aws/commands/testdata/.trivyignore | 8 - .../aws/commands/testdata/example-spec.yaml | 13 - .../testdata/s3andcloudtrailcache.json | 420 ------ .../aws/commands/testdata/s3onlycache.json | 261 ---- pkg/cloud/aws/config/config.go | 4 +- pkg/cloud/aws/scanner/progress.go | 83 -- pkg/cloud/aws/scanner/scanner.go | 176 --- pkg/cloud/provider.go | 5 - pkg/cloud/report/convert.go | 107 -- pkg/cloud/report/convert_test.go | 242 ---- pkg/cloud/report/report.go | 160 -- pkg/cloud/report/resource.go | 88 -- pkg/cloud/report/resource_test.go | 124 -- pkg/cloud/report/result.go | 35 - pkg/cloud/report/result_test.go | 83 -- pkg/cloud/report/service.go | 85 -- pkg/cloud/report/service_test.go | 420 ------ pkg/commands/app.go | 78 +- 27 files changed, 47 insertions(+), 4337 deletions(-) delete mode 100644 docs/docs/references/configuration/cli/trivy_aws.md delete mode 100644 integration/aws_cloud_test.go delete mode 100644 pkg/cloud/aws/cache/cache.go delete mode 100644 pkg/cloud/aws/commands/run.go delete mode 100644 pkg/cloud/aws/commands/run_test.go delete mode 100644 pkg/cloud/aws/commands/testdata/.trivyignore delete mode 100644 pkg/cloud/aws/commands/testdata/example-spec.yaml delete mode 100644 pkg/cloud/aws/commands/testdata/s3andcloudtrailcache.json delete mode 100644 pkg/cloud/aws/commands/testdata/s3onlycache.json delete mode 100644 pkg/cloud/aws/scanner/progress.go delete mode 100644 pkg/cloud/aws/scanner/scanner.go delete mode 100644 pkg/cloud/provider.go delete mode 100644 pkg/cloud/report/convert.go delete mode 100644 pkg/cloud/report/convert_test.go delete mode 100644 pkg/cloud/report/report.go delete mode 100644 pkg/cloud/report/resource.go delete mode 100644 pkg/cloud/report/resource_test.go delete mode 100644 pkg/cloud/report/result.go delete mode 100644 pkg/cloud/report/result_test.go delete mode 100644 pkg/cloud/report/service.go delete mode 100644 pkg/cloud/report/service_test.go diff --git a/docs/docs/references/configuration/cli/trivy.md b/docs/docs/references/configuration/cli/trivy.md index f3c543a210f9..500a367fff8a 100644 --- a/docs/docs/references/configuration/cli/trivy.md +++ b/docs/docs/references/configuration/cli/trivy.md @@ -43,7 +43,6 @@ trivy [global flags] command [flags] target ### SEE ALSO -* [trivy aws](trivy_aws.md) - [EXPERIMENTAL] Scan AWS account * [trivy config](trivy_config.md) - Scan config files for misconfigurations * [trivy convert](trivy_convert.md) - Convert Trivy JSON report into a different format * [trivy filesystem](trivy_filesystem.md) - Scan local filesystem diff --git a/docs/docs/references/configuration/cli/trivy_aws.md b/docs/docs/references/configuration/cli/trivy_aws.md deleted file mode 100644 index fad5d106bc16..000000000000 --- a/docs/docs/references/configuration/cli/trivy_aws.md +++ /dev/null @@ -1,127 +0,0 @@ -## trivy aws - -[EXPERIMENTAL] Scan AWS account - -### Synopsis - -Scan an AWS account for misconfigurations. Trivy uses the same authentication methods as the AWS CLI. See https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html - -The following services are supported: - -- accessanalyzer -- api-gateway -- athena -- cloudfront -- cloudtrail -- cloudwatch -- codebuild -- documentdb -- dynamodb -- ec2 -- ecr -- ecs -- efs -- eks -- elasticache -- elasticsearch -- elb -- emr -- iam -- kinesis -- kms -- lambda -- mq -- msk -- neptune -- rds -- redshift -- s3 -- sns -- sqs -- ssm -- workspaces - - -``` -trivy aws [flags] -``` - -### Examples - -``` - # basic scanning - $ trivy aws --region us-east-1 - - # limit scan to a single service: - $ trivy aws --region us-east-1 --service s3 - - # limit scan to multiple services: - $ trivy aws --region us-east-1 --service s3 --service ec2 - - # force refresh of cache for fresh results - $ trivy aws --region us-east-1 --update-cache - -``` - -### Options - -``` - --account string The AWS account to scan. It's useful to specify this when reviewing cached results for multiple accounts. - --arn string The AWS ARN to show results for. Useful to filter results once a scan is cached. - --cf-params strings specify paths to override the CloudFormation parameters files - --check-namespaces strings Rego namespaces - --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") - --compliance string compliance report to generate (aws-cis-1.2,aws-cis-1.4) - --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files - --config-data strings specify paths from which data for the Rego checks will be recursively loaded - --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages - --endpoint string AWS Endpoint override - --exit-code int specify exit code when any security issues are found - -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") - --helm-api-versions strings Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. (can specify multiple or separate values with commas: policy/v1/PodDisruptionBudget,apps/v1/Deployment) - --helm-kube-version string Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command. - --helm-set strings specify Helm values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) - --helm-set-file strings specify Helm values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2) - --helm-set-string strings specify Helm string values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) - --helm-values strings specify paths to override the Helm values.yaml files - -h, --help help for aws - --ignore-policy string specify the Rego file path to evaluate each vulnerability - --ignorefile string specify .trivyignore file (default ".trivyignore") - --include-deprecated-checks include deprecated checks - --include-non-failures include successes and exceptions, available with '--scanners misconfig' - --list-all-pkgs output all packages in the JSON report regardless of vulnerability - --max-cache-age duration The maximum age of the cloud cache. Cached data will be required from the cloud provider if it is older than this. (default 24h0m0s) - --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) - -o, --output string output file name - --output-plugin-arg string [EXPERIMENTAL] output plugin arguments - --region string AWS Region to scan - --report string specify a report format for the output (all,summary) (default "all") - --reset-checks-bundle remove checks bundle - --service strings Only scan AWS Service(s) specified with this flag. Can specify multiple services using --service A --service B etc. - -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) - --skip-check-update skip fetching rego check updates - --skip-service strings Skip selected AWS Service(s) specified with this flag. Can specify multiple services using --skip-service A --skip-service B etc. - -t, --template string output template - --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules - --tf-vars strings specify paths to override the Terraform tfvars files - --trace enable more verbose trace output for custom queries - --update-cache Update the cache for the applicable cloud provider instead of using cached results. -``` - -### Options inherited from parent commands - -``` - --cache-dir string cache directory (default "/path/to/cache") - -c, --config string config path (default "trivy.yaml") - -d, --debug debug mode - --generate-default-config write the default config to trivy-default.yaml - --insecure allow insecure server connections - -q, --quiet suppress progress bar and log output - --timeout duration timeout (default 5m0s) - -v, --version show version -``` - -### SEE ALSO - -* [trivy](trivy.md) - Unified security scanner - diff --git a/go.mod b/go.mod index 1feb4197a695..b12cdf30855a 100644 --- a/go.mod +++ b/go.mod @@ -22,23 +22,21 @@ require ( github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d - github.com/aquasecurity/loading v0.0.5 github.com/aquasecurity/table v1.8.0 github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac github.com/aquasecurity/tml v0.6.1 - github.com/aquasecurity/trivy-aws v0.9.1-0.20240607040622-8a7f09cd891f github.com/aquasecurity/trivy-checks v0.11.0 github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7 github.com/aws/aws-sdk-go-v2 v1.27.2 - github.com/aws/aws-sdk-go-v2/config v1.27.18 - github.com/aws/aws-sdk-go-v2/credentials v1.17.18 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.24 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.1 - github.com/aws/aws-sdk-go-v2/service/ecr v1.28.5 - github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 + github.com/aws/aws-sdk-go-v2/config v1.27.15 + github.com/aws/aws-sdk-go-v2/credentials v1.17.15 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.20 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.161.3 + github.com/aws/aws-sdk-go-v2/service/ecr v1.28.2 + github.com/aws/aws-sdk-go-v2/service/s3 v1.54.2 + github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 // indirect github.com/aws/smithy-go v1.20.2 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 @@ -176,46 +174,14 @@ require ( github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.9 // indirect - github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.26.7 // indirect - github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6 // indirect - github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6 // indirect - github.com/aws/aws-sdk-go-v2/service/athena v1.37.3 // indirect - github.com/aws/aws-sdk-go-v2/service/cloudfront v1.36.4 // indirect - github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.35.6 // indirect - github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.32.2 // indirect - github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.30.1 // indirect - github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.5 // indirect - github.com/aws/aws-sdk-go-v2/service/docdb v1.34.4 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 // indirect github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 // indirect - github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6 // indirect - github.com/aws/aws-sdk-go-v2/service/efs v1.28.1 // indirect - github.com/aws/aws-sdk-go-v2/service/eks v1.41.0 // indirect - github.com/aws/aws-sdk-go-v2/service/elasticache v1.34.6 // indirect - github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.26.6 // indirect - github.com/aws/aws-sdk-go-v2/service/elasticsearchservice v1.25.0 // indirect - github.com/aws/aws-sdk-go-v2/service/emr v1.36.0 // indirect - github.com/aws/aws-sdk-go-v2/service/iam v1.28.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.9 // indirect - github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5 // indirect - github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6 // indirect - github.com/aws/aws-sdk-go-v2/service/kms v1.32.1 // indirect - github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6 // indirect - github.com/aws/aws-sdk-go-v2/service/mq v1.20.6 // indirect - github.com/aws/aws-sdk-go-v2/service/neptune v1.28.1 // indirect - github.com/aws/aws-sdk-go-v2/service/rds v1.66.1 // indirect - github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7 // indirect - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sns v1.26.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect - github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/briandowns/spinner v1.23.0 // indirect @@ -287,6 +253,7 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect @@ -383,10 +350,10 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect go.opentelemetry.io/otel v1.27.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect go.opentelemetry.io/otel/metric v1.27.0 // indirect go.opentelemetry.io/otel/sdk v1.27.0 // indirect go.opentelemetry.io/otel/trace v1.27.0 // indirect - go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect diff --git a/go.sum b/go.sum index d47b409f6090..93c0f75b63ea 100644 --- a/go.sum +++ b/go.sum @@ -755,8 +755,6 @@ github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30 github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce/go.mod h1:HXgVzOPvXhVGLJs4ZKO817idqr/xhwsTcj17CLYY74s= -github.com/aquasecurity/go-mock-aws v0.0.0-20240523055201-a4152219967f h1:NRq3oUfkheKgoYPjNUApUtClKaBRcc6KzdcBHqZPrAM= -github.com/aquasecurity/go-mock-aws v0.0.0-20240523055201-a4152219967f/go.mod h1:95xczqqItx1yPSrYG2SQM2gi2lqoYG9i3pLsYKSTpgI= github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 h1:eveqE9ivrt30CJ7dOajOfBavhZ4zPqHcZe/4tKp0alc= github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798/go.mod h1:hxbJZtKlO4P8sZ9nztizR6XLoE33O+BkPmuYQ4ACyz0= github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 h1:vmXNl+HDfqqXgr0uY1UgK1GAhps8nbAAtqHNBcgyf+4= @@ -765,16 +763,12 @@ github.com/aquasecurity/go-version v0.0.0-20201107203531-5e48ac5d022a/go.mod h1: github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU= github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d h1:4zour5Sh9chOg+IqIinIcJ3qtr3cIf8FdFY6aArlXBw= github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d/go.mod h1:1cPOp4BaQZ1G2F5fnw4dFz6pkOyXJI9KTuak8ghIl3U= -github.com/aquasecurity/loading v0.0.5 h1:2iq02sPSSMU+ULFPmk0v0lXnK/eZ2e0dRAj/Dl5TvuM= -github.com/aquasecurity/loading v0.0.5/go.mod h1:NSHeeq1JTDTFuXAe87q4yQ2DX57pXiaQMqq8Zm9HCJA= github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0= github.com/aquasecurity/table v1.8.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw= github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac h1:dy7xjLOAAeCNycqJ3kws4vDFGm8WdeCovkHXf2um5uA= github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac/go.mod h1:nyavBQqxtIkQh99lQE1ssup3i2uIq1+giL7tOSHapYk= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= -github.com/aquasecurity/trivy-aws v0.9.1-0.20240607040622-8a7f09cd891f h1:LS8Xb8Lb0mosGay+hk7hkt8jVc+L8msTdjJCU+ICcS8= -github.com/aquasecurity/trivy-aws v0.9.1-0.20240607040622-8a7f09cd891f/go.mod h1:pfwElhU8kilUmgib1xBw91ZBPJya6EZ1unwvqC0ijh4= github.com/aquasecurity/trivy-checks v0.11.0 h1:hS5gSQyuyIITrY/kCY2AWQMUSwXLpdtbHDPaCs6eSaI= github.com/aquasecurity/trivy-checks v0.11.0/go.mod h1:IAK3eHcKNxIHo/ckxKoHsXmEpUG45/38grW5bBjL9lw= github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTUAkbGqpzt9nJsO24RAdfG+ZSiLFj0G2jO8c= @@ -799,108 +793,44 @@ github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= -github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk= -github.com/aws/aws-sdk-go-v2/config v1.27.18/go.mod h1:0xz6cgdX55+kmppvPm2IaKzIXOheGJhAufacPJaXZ7c= -github.com/aws/aws-sdk-go-v2/credentials v1.17.18 h1:D/ALDWqK4JdY3OFgA2thcPO1c9aYTT5STS/CvnkqY1c= -github.com/aws/aws-sdk-go-v2/credentials v1.17.18/go.mod h1:JuitCWq+F5QGUrmMPsk945rop6bB57jdscu+Glozdnc= +github.com/aws/aws-sdk-go-v2/config v1.27.15 h1:uNnGLZ+DutuNEkuPh6fwqK7LpEiPmzb7MIMA1mNWEUc= +github.com/aws/aws-sdk-go-v2/config v1.27.15/go.mod h1:7j7Kxx9/7kTmL7z4LlhwQe63MYEE5vkVV6nWg4ZAI8M= +github.com/aws/aws-sdk-go-v2/credentials v1.17.15 h1:YDexlvDRCA8ems2T5IP1xkMtOZ1uLJOCJdTr0igs5zo= +github.com/aws/aws-sdk-go-v2/credentials v1.17.15/go.mod h1:vxHggqW6hFNaeNC0WyXS3VdyjcV0a4KMUY4dKJ96buU= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 h1:dDgptDO9dxeFkXy+tEgVkzSClHZje/6JkPW5aZyEvrQ= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5/go.mod h1:gjvE2KBUgUQhcv89jqxrIxH9GaKs1JbZzWejj/DaHGA= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.24 h1:FzNwpVTZDCvm597Ty6mGYvxTolyC1oup0waaKntZI4E= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.24/go.mod h1:wM9NElT/Wn6n3CT1eyVcXtfCy8lSVjjQXfdawQbSShc= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.20 h1:NCM9wYaJCmlIWZSO/JwUEveKf0NCvsSgo9V9BwOAolo= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.20/go.mod h1:dmxIx3qriuepxqZgFeFMitFuftWPB94+MZv/6Btpth4= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 h1:cy8ahBJuhtM8GTTSyOkfy6WVPV1IE+SS5/wfXUYuulw= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9/go.mod h1:CZBXGLaJnEZI6EVNcPd7a6B5IC5cA/GkRWtu9fp3S6Y= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 h1:A4SYk07ef04+vxZToz9LWvAXl9LW0NClpPpMsi31cz0= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9/go.mod h1:5jJcHuwDagxN+ErjQ3PU3ocf6Ylc/p9x+BLO/+X4iXw= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.9 h1:vHyZxoLVOgrI8GqX7OMHLXp4YYoxeEsrjweXKpye+ds= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.9/go.mod h1:z9VXZsWA2BvZNH1dT0ToUYwMu/CR9Skkj/TBX+mceZw= -github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.26.7 h1:rLdKcienXrk+JFX1+DZg160ebG8lIF2nFvnEZL7dnII= -github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.26.7/go.mod h1:cwqaWBOZXu8pqEE1ZC4Sw2ycZLjwKrRP5tOAJFgCbYc= -github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6 h1:ePPaOVn92r5n8Neecdpy93hDmR0PBH6H6b7VQCE5vKE= -github.com/aws/aws-sdk-go-v2/service/apigateway v1.21.6/go.mod h1:P/zwE9uiC6eK/kL3CS60lxTTVC2zAvaS4iW31io41V4= -github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6 h1:bCdxKjM8DpkNJXnOLVx+Hnav0eM4yJK8kof56VvIjMc= -github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.18.6/go.mod h1:zQ6tOYz7oGI7MbLRDBXfo63puDoTroVcVNXWfmRDA1E= -github.com/aws/aws-sdk-go-v2/service/athena v1.37.3 h1:qNLkDi/rOaauOuh33a4MNZjyfxvwIgC5qsDiHPvjDk0= -github.com/aws/aws-sdk-go-v2/service/athena v1.37.3/go.mod h1:MlpC6swcjh1Il80u6XoeY2BTHIZRZWvoXOfaq3rfh8I= -github.com/aws/aws-sdk-go-v2/service/cloudfront v1.36.4 h1:8qjQzwztUVdFJi/wrhPXxRgSbyAKDsnJuduHaw+yP30= -github.com/aws/aws-sdk-go-v2/service/cloudfront v1.36.4/go.mod h1:lHdM6itntBCcjvqxEHDoHkXRicwgY9aoPRptXuMdbgk= -github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.35.6 h1:Yc+avPLGARzp4A9Oi9VRxvlcGqI+0MYIg4tPSupKv2U= -github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.35.6/go.mod h1:zrqdG1b+4AGoTwTMVFzvzY7ARB3GPo4gKRuK8WPEo8w= -github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.32.2 h1:vQfCIHSDouEvbE4EuDrlCGKcrtABEqF3cMt61nGEV4g= -github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.32.2/go.mod h1:3ToKMEhVj+Q+HzZ8Hqin6LdAKtsi3zVXVNUPpQMd+Xk= -github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.30.1 h1:ZMgx58Tqyr8kTSR9zLzX+W933ujDYleOtFedvn0xHg8= -github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.30.1/go.mod h1:4Oeb7n2r/ApBIHphQkprve380p/RpPWBotumd44EDGg= -github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.5 h1:EPnlDd4V2EXywlOPAw/pMUW4PHUgSulKm4zXFU6bixE= -github.com/aws/aws-sdk-go-v2/service/codebuild v1.26.5/go.mod h1:G2JUWf01sbb5/A8qGcM4dqy4nbl4y4IGWmaCDWAvA2Y= -github.com/aws/aws-sdk-go-v2/service/docdb v1.34.4 h1:0hvzmeEwiNthBmi2mpTnZgqFCKUxKoLWaQYzulEnqk4= -github.com/aws/aws-sdk-go-v2/service/docdb v1.34.4/go.mod h1:KSNSbXXGchzkLYCDwq9H9ZfPs2zn0SIVgs7LXsfPlRQ= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8 h1:XKO0BswTDeZMLDBd/b5pCEZGttNXrzRUVtFvp2Ak/Vo= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.26.8/go.mod h1:N5tqZcYMM0N1PN7UQYJNWuGyO886OfnMhf/3MAbqMcI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 h1:/FUtT3xsoHO3cfh+I/kCbcMCN98QZRsiFet/V8QkWSs= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7/go.mod h1:MaCAgWpGooQoCWZnMur97rGn5dp350w2+CeiV5406wE= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.1 h1:0RiDkJO1veM6/FQ+GJcGiIhZgPwXlscX29B0zFE4Ulo= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.1/go.mod h1:gYk1NtyvkH1SxPcndDtfro3lwbiE5t0tW4eRki5YnOQ= -github.com/aws/aws-sdk-go-v2/service/ecr v1.28.5 h1:dvvTFXpWSv9+8lTNPl1EPNZL6BCUV6MgVckEMvXaOgk= -github.com/aws/aws-sdk-go-v2/service/ecr v1.28.5/go.mod h1:Ogt6AOZ/sPBlJZpVFJgOK+jGGREuo8DMjNg+O/7gpjI= -github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6 h1:Sc2mLjyA1R8z2l705AN7Wr7QOlnUxVnGPJeDIVyUSrs= -github.com/aws/aws-sdk-go-v2/service/ecs v1.35.6/go.mod h1:LzHcyOEvaLjbc5e+fP/KmPWBr+h/Ef+EHvnf1Pzo368= -github.com/aws/aws-sdk-go-v2/service/efs v1.28.1 h1:dKtJBzCIew4/VDsYgrx6v140cIpQVoe93kCNniYATtE= -github.com/aws/aws-sdk-go-v2/service/efs v1.28.1/go.mod h1:ha+/WvylFi6dkfF2xfPekJWCNLGuD5PWIFrRRMz3psc= -github.com/aws/aws-sdk-go-v2/service/eks v1.41.0 h1:/bitqsA6wgIS2vgjtHJi1JG3SOTbobs1mCdeJBLOacY= -github.com/aws/aws-sdk-go-v2/service/eks v1.41.0/go.mod h1:GFqWNwDLyuSevADun69Dg5aurANpv8KNrz2vxYPEqmw= -github.com/aws/aws-sdk-go-v2/service/elasticache v1.34.6 h1:Y/5eE9Sc+OBID9pZ4EVFzyQviv1d1RbqB17HRur9ySg= -github.com/aws/aws-sdk-go-v2/service/elasticache v1.34.6/go.mod h1:iPx2i26hgUULkNh1Jk4QzYzzQKd2nXl/rD9Fm5hQ2uk= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.26.6 h1:twI2uRmpbm0KBog3Ay61IqOtNp6+QxKfSA78zftME/o= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.26.6/go.mod h1:Tpt4kC8x1HfYuh2rG/6yXZrxjABETERrUl9IdA/IS98= -github.com/aws/aws-sdk-go-v2/service/elasticsearchservice v1.25.0 h1:LPEsYRsC6r3edPHO8KlZJNW0xxyfLHMXJ466MdHuBbQ= -github.com/aws/aws-sdk-go-v2/service/elasticsearchservice v1.25.0/go.mod h1:CAXUsQvYQVzsXO36npqK3aUlxx2xMSM1Dun3O9jnaEE= -github.com/aws/aws-sdk-go-v2/service/emr v1.36.0 h1:FdeZ7AYOvyL09KH250Ncz4LF4SB1Vo9l7KZzn/LIrgQ= -github.com/aws/aws-sdk-go-v2/service/emr v1.36.0/go.mod h1:Drh6y2qLaw/wnDKTIcdqM2m358MIRXsZ2Bj2tjhVLq0= -github.com/aws/aws-sdk-go-v2/service/iam v1.28.7 h1:FKPRDYZOO0Eur19vWUL1B40Op0j89KQj3kARjrszMK8= -github.com/aws/aws-sdk-go-v2/service/iam v1.28.7/go.mod h1:YzMYyQ7S4twfYzLjwP24G1RAxypozVZeNaG1r2jxRms= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.161.3 h1:l0mvKOGm25yo/Fy+Y/08Cm4aTA4XmnIuq4ppy+shfMI= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.161.3/go.mod h1:iJ2sQeUTkjNp3nL7kE/Bav0xXYhtiRCRP5ZXk4jFhCQ= +github.com/aws/aws-sdk-go-v2/service/ecr v1.28.2 h1:xUpMnRZonKfrHaNLC77IMpWZSUMRRXIi6IU5EhAPsrM= +github.com/aws/aws-sdk-go-v2/service/ecr v1.28.2/go.mod h1:X52zjAVRaXklEU1TE/wO8kyyJSr9cJx9ZsqliWbyRys= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.11 h1:4vt9Sspk59EZyHCAEMaktHKiq0C09noRTQorXD/qV+s= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.11/go.mod h1:5jHR79Tv+Ccq6rwYh+W7Nptmw++WiFafMfR42XhwNl8= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11 h1:e9AVb17H4x5FTE5KWIP5M1Du+9M86pS+Hw0lBUdN8EY= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11/go.mod h1:B90ZQJa36xo0ph9HsoteI1+r8owgQH/U1QNfqZQkj1Q= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 h1:o4T+fKxA3gTMcluBNZZXE9DNaMkJuUL1O3mffCUjoJo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11/go.mod h1:84oZdJ+VjuJKs9v1UTC9NaodRZRseOXCTgku+vQJWR8= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.9 h1:TE2i0A9ErH1YfRSvXfCr2SQwfnqsoJT9nPQ9kj0lkxM= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.9/go.mod h1:9TzXX3MehQNGPwCZ3ka4CpwQsoAMWSF48/b+De9rfVM= -github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5 h1:yCkyZDGahaCaAkdpVx8Te05t6eW2FarBLunVC8S23nU= -github.com/aws/aws-sdk-go-v2/service/kafka v1.28.5/go.mod h1:/KmX+vXMPJGAB56reo95tnsXa6QPNx6qli4L1AmYb7E= -github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6 h1:FO/aIHk86VePDUh/3Q/A5pnvu45miO1GZB8rIq2BUlA= -github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.6/go.mod h1:Sj7qc+P/GOGOPMDn8+B7Cs+WPq1Gk+R6CXRXVhZtWcA= -github.com/aws/aws-sdk-go-v2/service/kms v1.32.1 h1:FARrQLRQXpCFYylIUVF1dRij6YbPCmtwudq9NBk4kFc= -github.com/aws/aws-sdk-go-v2/service/kms v1.32.1/go.mod h1:8lETO9lelSG2B6KMXFh2OwPPqGV6WQM3RqLAEjP1xaU= -github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6 h1:w8lI9zlVwRTL9f4KB9fRThddhRivv+EQQzv2nU8JDQo= -github.com/aws/aws-sdk-go-v2/service/lambda v1.49.6/go.mod h1:0V5z1X/8NA9eQ5cZSz5ZaHU8xA/hId2ZAlsHeO7Jrdk= -github.com/aws/aws-sdk-go-v2/service/mq v1.20.6 h1:n86T5yw0kS6a5nbpkEpDzLPCBXXb35lx3iDkmQWlizA= -github.com/aws/aws-sdk-go-v2/service/mq v1.20.6/go.mod h1:phfKOOpMQhlBv2KE8gF17P82zLcSedA9b7fMSGTLBdQ= -github.com/aws/aws-sdk-go-v2/service/neptune v1.28.1 h1:e+DGEARs5GfHuzDwztENiomdLa0sjs55ub27juoFdt0= -github.com/aws/aws-sdk-go-v2/service/neptune v1.28.1/go.mod h1:jHUFaho5cVpplTDO6bctuLbvnm8F+Xd27RGIJvVTlYI= -github.com/aws/aws-sdk-go-v2/service/rds v1.66.1 h1:TafjIpDW/+l7s+f3EIONaFsNvNfwVH21NkWYrE0hbEE= -github.com/aws/aws-sdk-go-v2/service/rds v1.66.1/go.mod h1:MYzRMSdY70kcS8AFg0aHmk/xj6VAe0UfaCCoLrBWPow= -github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7 h1:k4WaqQ7LHSGrSftCRXTRLv7WaozXu+fZ1jdisQSR2eU= -github.com/aws/aws-sdk-go-v2/service/redshift v1.39.7/go.mod h1:8hU0Ax6q6QA+jrMcWTE0A4YH594MQoWP3EzGO3GH5Dw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1 h1:UAxBuh0/8sFJk1qOkvOKewP5sWeWaTPDknbQz0ZkDm0= -github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1/go.mod h1:hWjsYGjVuqCgfoveVcVFPXIWgz0aByzwaxKlN1StKcM= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 h1:dPCRgAL4WD9tSMaDglRNGOiAtSTjkwNiUW5GDpWFfHA= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0/go.mod h1:4Ae1NCLK6ghmjzd45Tc33GgCKhUWD2ORAlULtMO1Cbs= -github.com/aws/aws-sdk-go-v2/service/sns v1.26.6 h1:w2YwF8889ardGU3Y0qZbJ4Zzh+Q/QqKZ4kwkK7JFvnI= -github.com/aws/aws-sdk-go-v2/service/sns v1.26.6/go.mod h1:IrcbquqMupzndZ20BXxDxjM7XenTRhbwBOetk4+Z5oc= -github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6 h1:UdbDTllc7cmusTTMy1dcTrYKRl4utDEsmKh9ZjvhJCc= -github.com/aws/aws-sdk-go-v2/service/sqs v1.29.6/go.mod h1:mCUv04gd/7g+/HNzDB4X6dzJuygji0ckvB3Lg/TdG5Y= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 h1:gEYM2GSpr4YNWc6hCd5nod4+d4kd9vWIAWrmGuLdlMw= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.11/go.mod h1:gVvwPdPNYehHSP9Rs7q27U1EU+3Or2ZpXvzAYJNh63w= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 h1:iXjh3uaH3vsVcnyZX7MqCoCfcyxIrVE9iOQruRaWPrQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5/go.mod h1:5ZXesEuy/QcO0WUnt+4sDkxhdXRHTu2yG0uCSH8B6os= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 h1:M/1u4HBpwLuMtjlxuI2y6HoVLzF5e2mfxHCg7ZVMYmk= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.12/go.mod h1:kcfd+eTdEi/40FIbLq4Hif3XMXnl5b/+t/KTfLt9xIk= -github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1 h1:pqxn3fcZDgWmo8GMUjlxVBdakcGo0AeUb7mjX33pJIQ= -github.com/aws/aws-sdk-go-v2/service/workspaces v1.38.1/go.mod h1:kP5rUlnqfno/obflnKX4KMBWkoVHLDI8oCka9U0opRo= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 h1:UXqEWQI0n+q0QixzU0yUUQBZXRd5037qdInTIHFTl98= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9/go.mod h1:xP6Gq6fzGZT8w/ZN+XvGMZ2RU1LeEs7b2yUP5DN8NY4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 h1:Wx0rlZoEJR7JwlSZcHnEa7CNjrSIyVxMFWGAaXy4fJY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9/go.mod h1:aVMHdE0aHO3v+f/iw01fmXV/5DbfQ3Bi9nN7nd9bE9Y= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 h1:uO5XR6QGBcmPyo2gxofYJLFkcVQ4izOoGDNenlZhTEk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7/go.mod h1:feeeAYfAcwTReM6vbwjEyDmiGho+YgBhaFULuXDW8kc= +github.com/aws/aws-sdk-go-v2/service/s3 v1.54.2 h1:gYSJhNiOF6J9xaYxu2NFNstoiNELwt0T9w29FxSfN+Y= +github.com/aws/aws-sdk-go-v2/service/s3 v1.54.2/go.mod h1:739CllldowZiPPsDFcJHNF4FXrVxaSGVnZ9Ez9Iz9hc= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 h1:Kv1hwNG6jHC/sxMTe5saMjH6t6ZLkgfvVxyEjfWL1ks= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.8/go.mod h1:c1qtZUWtygI6ZdvKppzCSXsDOq5I4luJPZ0Ud3juFCA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 h1:nWBZ1xHCF+A7vv9sDzJOq4NWIdzFYm0kH7Pr4OjHYsQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2/go.mod h1:9lmoVDVLz/yUZwLaQ676TK02fhCu4+PgRSmMaKR1ozk= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 h1:Qp6Boy0cGDloOE3zI6XhNLNZgjNS8YmiFQFHe71SaW0= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.9/go.mod h1:0Aqn1MnEuitqfsCNyKsdKLhDUOr4txD/g19EfiUqgws= github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -1521,7 +1451,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= diff --git a/integration/aws_cloud_test.go b/integration/aws_cloud_test.go deleted file mode 100644 index 481ce6ca0cf6..000000000000 --- a/integration/aws_cloud_test.go +++ /dev/null @@ -1,78 +0,0 @@ -//go:build integration - -package integration - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/aquasecurity/trivy/internal/testutil" - awscommands "github.com/aquasecurity/trivy/pkg/cloud/aws/commands" - "github.com/aquasecurity/trivy/pkg/flag" -) - -func TestAwsCommandRun(t *testing.T) { - tests := []struct { - name string - options flag.Options - envs map[string]string - wantErr string - }{ - { - name: "fail without region", - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, - }, - envs: map[string]string{ - "AWS_ACCESS_KEY_ID": "test", - "AWS_SECRET_ACCESS_KEY": "test", - }, - wantErr: "aws region is required", - }, - { - name: "fail without creds", - envs: map[string]string{ - "AWS_PROFILE": "non-existent-profile", - }, - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - }, - }, - wantErr: "non-existent-profile", - }, - } - - ctx := context.Background() - - localstackC, addr, err := testutil.SetupLocalStack(ctx, "2.2.0") - require.NoError(t, err) - defer localstackC.Terminate(ctx) - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - tt.options.AWSOptions.Endpoint = addr - tt.options.GlobalOptions.Timeout = time.Minute - - for k, v := range tt.envs { - t.Setenv(k, v) - } - - err := awscommands.Run(context.Background(), tt.options) - - if tt.wantErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr, tt.name) - return - } - require.NoError(t, err) - }) - } - -} diff --git a/mkdocs.yml b/mkdocs.yml index 92bbbb24ac21..042ffe789d6d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -152,7 +152,6 @@ nav: - Configuration: - CLI: - Overview: docs/references/configuration/cli/trivy.md - - AWS: docs/references/configuration/cli/trivy_aws.md - Config: docs/references/configuration/cli/trivy_config.md - Convert: docs/references/configuration/cli/trivy_convert.md - Filesystem: docs/references/configuration/cli/trivy_filesystem.md diff --git a/pkg/cloud/aws/cache/cache.go b/pkg/cloud/aws/cache/cache.go deleted file mode 100644 index 660cb24b443b..000000000000 --- a/pkg/cloud/aws/cache/cache.go +++ /dev/null @@ -1,132 +0,0 @@ -package cache - -import ( - "encoding/json" - "fmt" - "os" - "path" - "path/filepath" - "strings" - "time" - - "github.com/aquasecurity/trivy/pkg/iac/state" -) - -type Cache struct { - path string - accountID string - region string - maxAge time.Duration -} - -const SchemaVersion = 2 - -type CacheData struct { - SchemaVersion int `json:"schema_version"` - State *state.State `json:"state"` - Services map[string]ServiceMetadata `json:"service_metadata"` - Updated time.Time `json:"updated"` -} - -type ServiceMetadata struct { - Name string `json:"name"` - Updated time.Time `json:"updated"` -} - -var ErrCacheNotFound = fmt.Errorf("cache record not found") -var ErrCacheIncompatible = fmt.Errorf("cache record used incomatible schema") -var ErrCacheExpired = fmt.Errorf("cache record expired") - -func New(cacheDir string, maxCacheAge time.Duration, accountID, region string) *Cache { - return &Cache{ - path: path.Join(cacheDir, "cloud", "aws", accountID, strings.ToLower(region), "data.json"), - accountID: accountID, - region: region, - maxAge: maxCacheAge, - } -} - -func (c *Cache) load() (*CacheData, error) { - - m, err := os.Open(c.path) - if err != nil { - return nil, ErrCacheNotFound - } - defer func() { _ = m.Close() }() - - var data CacheData - if err := json.NewDecoder(m).Decode(&data); err != nil { - return nil, err - } - - if data.SchemaVersion != SchemaVersion { - return nil, ErrCacheIncompatible - } - - if time.Since(data.Updated) > c.maxAge { - return nil, ErrCacheExpired - } - - return &data, nil -} - -func (c *Cache) ListServices(required []string) (included, missing []string) { - - data, err := c.load() - if err != nil { - return nil, required - } - - for _, service := range required { - metadata, ok := data.Services[service] - if !ok { - missing = append(missing, service) - continue - } - if time.Since(metadata.Updated) > c.maxAge { - missing = append(missing, service) - continue - } - included = append(included, service) - } - - return included, missing -} - -func (c *Cache) LoadState() (*state.State, error) { - data, err := c.load() - if err != nil { - return nil, err - } - return data.State, nil -} - -func (c *Cache) AddServices(s *state.State, includedServices []string) error { - data := &CacheData{ - SchemaVersion: SchemaVersion, - State: s, - Services: make(map[string]ServiceMetadata), - Updated: time.Now(), - } - - if previous, err := c.load(); err == nil { - data.Services = previous.Services - } - - for _, service := range includedServices { - data.Services[service] = ServiceMetadata{ - Name: service, - Updated: time.Now(), - } - } - - if err := os.MkdirAll(filepath.Dir(c.path), 0700); err != nil { - return err - } - f, err := os.Create(c.path) - if err != nil { - return err - } - defer func() { _ = f.Close() }() - return json.NewEncoder(f).Encode(data) -} diff --git a/pkg/cloud/aws/commands/run.go b/pkg/cloud/aws/commands/run.go deleted file mode 100644 index fac103feaf22..000000000000 --- a/pkg/cloud/aws/commands/run.go +++ /dev/null @@ -1,183 +0,0 @@ -package commands - -import ( - "context" - "errors" - "fmt" - "slices" - "sort" - "strings" - - "github.com/aws/aws-sdk-go-v2/service/sts" - "golang.org/x/xerrors" - - "github.com/aquasecurity/trivy-aws/pkg/errs" - awsScanner "github.com/aquasecurity/trivy-aws/pkg/scanner" - "github.com/aquasecurity/trivy/pkg/cloud" - "github.com/aquasecurity/trivy/pkg/cloud/aws/config" - "github.com/aquasecurity/trivy/pkg/cloud/aws/scanner" - "github.com/aquasecurity/trivy/pkg/cloud/report" - "github.com/aquasecurity/trivy/pkg/commands/operation" - "github.com/aquasecurity/trivy/pkg/flag" - "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/version/doc" -) - -var allSupportedServicesFunc = awsScanner.AllSupportedServices - -func getAccountIDAndRegion(ctx context.Context, region, endpoint string) (string, string, error) { - log.DebugContext(ctx, "Looking for AWS credentials provider...") - - cfg, err := config.LoadDefaultAWSConfig(ctx, region, endpoint) - if err != nil { - return "", "", err - } - - svc := sts.NewFromConfig(cfg) - - log.DebugContext(ctx, "Looking up AWS caller identity...") - result, err := svc.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{}) - if err != nil { - return "", "", xerrors.Errorf("failed to discover AWS caller identity: %w", err) - } - if result.Account == nil { - return "", "", xerrors.Errorf("missing account id for aws account") - } - log.DebugContext(ctx, "Verified AWS credentials for account!", log.String("account", *result.Account)) - return *result.Account, cfg.Region, nil -} - -func validateServicesInput(services, skipServices []string) error { - for _, s := range services { - for _, ss := range skipServices { - if s == ss { - return xerrors.Errorf("service: %s specified to both skip and include", s) - } - } - } - return nil -} - -func processOptions(ctx context.Context, opt *flag.Options) error { - if err := validateServicesInput(opt.Services, opt.SkipServices); err != nil { - return err - } - - // support comma separated services too - var splitServices []string - for _, service := range opt.Services { - splitServices = append(splitServices, strings.Split(service, ",")...) - } - opt.Services = splitServices - - var splitSkipServices []string - for _, skipService := range opt.SkipServices { - splitSkipServices = append(splitSkipServices, strings.Split(skipService, ",")...) - } - opt.SkipServices = splitSkipServices - - if len(opt.Services) != 1 && opt.ARN != "" { - return xerrors.Errorf("you must specify the single --service which the --arn relates to") - } - - if opt.Account == "" || opt.Region == "" { - var err error - opt.Account, opt.Region, err = getAccountIDAndRegion(ctx, opt.Region, opt.Endpoint) - if err != nil { - return err - } - } - - err := filterServices(ctx, opt) - if err != nil { - return err - } - - log.DebugContext(ctx, "Scanning services", log.Any("services", opt.Services)) - return nil -} - -func filterServices(ctx context.Context, opt *flag.Options) error { - switch { - case len(opt.Services) == 0 && len(opt.SkipServices) == 0: - log.DebugContext(ctx, "No service(s) specified, scanning all services...") - opt.Services = allSupportedServicesFunc() - case len(opt.SkipServices) > 0: - log.DebugContext(ctx, "Excluding services", log.Any("services", opt.SkipServices)) - for _, s := range allSupportedServicesFunc() { - if slices.Contains(opt.SkipServices, s) { - continue - } - if !slices.Contains(opt.Services, s) { - opt.Services = append(opt.Services, s) - } - } - case len(opt.Services) > 0: - log.DebugContext(ctx, "Specific services were requested...", - log.String("services", strings.Join(opt.Services, ", "))) - for _, service := range opt.Services { - var found bool - supported := allSupportedServicesFunc() - for _, allowed := range supported { - if allowed == service { - found = true - break - } - } - if !found { - return xerrors.Errorf("service '%s' is not currently supported - supported services are: %s", service, strings.Join(supported, ", ")) - } - } - } - return nil -} - -func Run(ctx context.Context, opt flag.Options) error { - ctx, cancel := context.WithTimeout(ctx, opt.GlobalOptions.Timeout) - defer cancel() - - ctx = log.WithContextPrefix(ctx, "aws") - - var err error - defer func() { - if errors.Is(err, context.DeadlineExceeded) { - // e.g. https://aquasecurity.github.io/trivy/latest/docs/configuration/ - log.WarnContext(ctx, fmt.Sprintf("Provide a higher timeout value, see %s", doc.URL("/docs/configuration/", ""))) - } - }() - - if err := processOptions(ctx, &opt); err != nil { - return err - } - - results, cached, err := scanner.NewScanner().Scan(ctx, opt) - if err != nil { - var aerr errs.AdapterError - if errors.As(err, &aerr) { - for _, e := range aerr.Errors() { - log.WarnContext(ctx, "Adapter error", log.Err(e)) - } - } else { - return xerrors.Errorf("aws scan error: %w", err) - } - } - - log.DebugContext(ctx, "Writing report to output...") - - sort.Slice(results, func(i, j int) bool { - return results[i].Rule().AVDID < results[j].Rule().AVDID - }) - - res := results.GetFailed() - if opt.MisconfOptions.IncludeNonFailures { - res = results - } - - r := report.New(cloud.ProviderAWS, opt.Account, opt.Region, res, opt.Services) - if err := report.Write(ctx, r, opt, cached); err != nil { - return xerrors.Errorf("unable to write results: %w", err) - } - - return operation.Exit(opt, r.Failed(), types.Metadata{}) -} diff --git a/pkg/cloud/aws/commands/run_test.go b/pkg/cloud/aws/commands/run_test.go deleted file mode 100644 index 325df5330bc5..000000000000 --- a/pkg/cloud/aws/commands/run_test.go +++ /dev/null @@ -1,1284 +0,0 @@ -package commands - -import ( - "bytes" - "context" - "os" - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/compliance/spec" - "github.com/aquasecurity/trivy/pkg/flag" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" -) - -const expectedS3ScanResult = `{ - "CreatedAt": "2021-08-25T12:20:30.000000005Z", - "ArtifactName": "12345678", - "ArtifactType": "aws_account", - "Metadata": { - "ImageConfig": { - "architecture": "", - "created": "0001-01-01T00:00:00Z", - "os": "", - "rootfs": { - "type": "", - "diff_ids": null - }, - "config": {} - } - }, - "Results": [ - { - "Target": "arn:aws:s3:::examplebucket", - "Class": "config", - "Type": "cloud", - "MisconfSummary": { - "Successes": 1, - "Failures": 8, - "Exceptions": 0 - }, - "Misconfigurations": [ - { - "Type": "AWS", - "ID": "AVD-AWS-0086", - "AVDID": "AVD-AWS-0086", - "Title": "S3 Access block should block public ACL", - "Description": "S3 buckets should block public ACLs on buckets and any objects they contain. By blocking, PUTs with fail if the object has any public ACL a.", - "Message": "No public access block so not blocking public acls", - "Resolution": "Enable blocking any PUT calls with a public ACL specified", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0086", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0086" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0087", - "AVDID": "AVD-AWS-0087", - "Title": "S3 Access block should block public policy", - "Description": "S3 bucket policy should have block public policy to prevent users from putting a policy that enable public access.", - "Message": "No public access block so not blocking public policies", - "Resolution": "Prevent policies that allow public access being PUT", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0087", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0087" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0088", - "AVDID": "AVD-AWS-0088", - "Title": "Unencrypted S3 bucket.", - "Description": "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.", - "Message": "Bucket does not have encryption enabled", - "Resolution": "Configure bucket encryption", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0088", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0088" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0090", - "AVDID": "AVD-AWS-0090", - "Title": "S3 Data should be versioned", - "Description": "Versioning in Amazon S3 is a means of keeping multiple variants of an object in the same bucket. \nYou can use the S3 Versioning feature to preserve, retrieve, and restore every version of every object stored in your buckets. \nWith versioning you can recover more easily from both unintended user actions and application failures.", - "Message": "Bucket does not have versioning enabled", - "Resolution": "Enable versioning to protect against accidental/malicious removal or modification", - "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0090", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0090" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0091", - "AVDID": "AVD-AWS-0091", - "Title": "S3 Access Block should Ignore Public Acl", - "Description": "S3 buckets should ignore public ACLs on buckets and any objects they contain. By ignoring rather than blocking, PUT calls with public ACLs will still be applied but the ACL will be ignored.", - "Message": "No public access block so not ignoring public acls", - "Resolution": "Enable ignoring the application of public ACLs in PUT calls", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0091", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0091" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0092", - "AVDID": "AVD-AWS-0092", - "Title": "S3 Buckets not publicly accessible through ACL.", - "Description": "Buckets should not have ACLs that allow public access", - "Resolution": "Don't use canned ACLs or switch to private acl", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0092", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0092" - ], - "Status": "PASS", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0093", - "AVDID": "AVD-AWS-0093", - "Title": "S3 Access block should restrict public bucket to limit access", - "Description": "S3 buckets should restrict public policies for the bucket. By enabling, the restrict_public_buckets, only the bucket owner and AWS Services can access if it has a public policy.", - "Message": "No public access block so not restricting public buckets", - "Resolution": "Limit the access to public buckets to only the owner or AWS Services (eg; CloudFront)", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0093", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0093" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0094", - "AVDID": "AVD-AWS-0094", - "Title": "S3 buckets should each define an aws_s3_bucket_public_access_block", - "Description": "The \"block public access\" settings in S3 override individual policies that apply to a given bucket, meaning that all public access can be controlled in one central types for that bucket. It is therefore good practice to define these settings for each bucket in order to clearly define the public access that can be allowed for it.", - "Message": "Bucket does not have a corresponding public access block.", - "Resolution": "Define a aws_s3_bucket_public_access_block for the given bucket to control public access policies", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0094", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0094" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0132", - "AVDID": "AVD-AWS-0132", - "Title": "S3 encryption should use Customer Managed Keys", - "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", - "Message": "Bucket does not encrypt data with a customer managed key.", - "Resolution": "Enable encryption using customer managed keys", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0132" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - } - ] - } - ] -} -` - -const expectedS3ScanResultWithExceptions = `{ - "CreatedAt": "2021-08-25T12:20:30.000000005Z", - "ArtifactName": "12345678", - "ArtifactType": "aws_account", - "Metadata": { - "ImageConfig": { - "architecture": "", - "created": "0001-01-01T00:00:00Z", - "os": "", - "rootfs": { - "type": "", - "diff_ids": null - }, - "config": {} - } - }, - "Results": [ - { - "Target": "arn:aws:s3:::examplebucket", - "Class": "config", - "Type": "cloud", - "MisconfSummary": { - "Successes": 0, - "Failures": 1, - "Exceptions": 8 - }, - "Misconfigurations": [ - { - "Type": "AWS", - "ID": "AVD-AWS-0094", - "AVDID": "AVD-AWS-0094", - "Title": "S3 buckets should each define an aws_s3_bucket_public_access_block", - "Description": "The \"block public access\" settings in S3 override individual policies that apply to a given bucket, meaning that all public access can be controlled in one central types for that bucket. It is therefore good practice to define these settings for each bucket in order to clearly define the public access that can be allowed for it.", - "Message": "Bucket does not have a corresponding public access block.", - "Resolution": "Define a aws_s3_bucket_public_access_block for the given bucket to control public access policies", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0094", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0094" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - } - ] - } - ] -} -` - -const expectedCustomScanResult = `{ - "CreatedAt": "2021-08-25T12:20:30.000000005Z", - "ArtifactName": "12345678", - "ArtifactType": "aws_account", - "Metadata": { - "ImageConfig": { - "architecture": "", - "created": "0001-01-01T00:00:00Z", - "os": "", - "rootfs": { - "type": "", - "diff_ids": null - }, - "config": {} - } - }, - "Results": [ - { - "Target": "", - "Class": "config", - "Type": "cloud", - "MisconfSummary": { - "Successes": 0, - "Failures": 1, - "Exceptions": 0 - }, - "Misconfigurations": [ - { - "Type": "AWS", - "Title": "Bad input data", - "Description": "Just failing rule with input data", - "Message": "Rego check resulted in DENY", - "Namespace": "user.whatever", - "Query": "deny", - "Severity": "LOW", - "References": [ - "" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Provider": "cloud", - "Service": "s3", - "Code": { - "Lines": null - } - } - } - ] - }, - { - "Target": "arn:aws:s3:::examplebucket", - "Class": "config", - "Type": "cloud", - "MisconfSummary": { - "Successes": 1, - "Failures": 8, - "Exceptions": 0 - }, - "Misconfigurations": [ - { - "Type": "AWS", - "ID": "AVD-AWS-0086", - "AVDID": "AVD-AWS-0086", - "Title": "S3 Access block should block public ACL", - "Description": "S3 buckets should block public ACLs on buckets and any objects they contain. By blocking, PUTs with fail if the object has any public ACL a.", - "Message": "No public access block so not blocking public acls", - "Resolution": "Enable blocking any PUT calls with a public ACL specified", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0086", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0086" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0087", - "AVDID": "AVD-AWS-0087", - "Title": "S3 Access block should block public policy", - "Description": "S3 bucket policy should have block public policy to prevent users from putting a policy that enable public access.", - "Message": "No public access block so not blocking public policies", - "Resolution": "Prevent policies that allow public access being PUT", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0087", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0087" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0088", - "AVDID": "AVD-AWS-0088", - "Title": "Unencrypted S3 bucket.", - "Description": "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.", - "Message": "Bucket does not have encryption enabled", - "Resolution": "Configure bucket encryption", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0088", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0088" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0090", - "AVDID": "AVD-AWS-0090", - "Title": "S3 Data should be versioned", - "Description": "Versioning in Amazon S3 is a means of keeping multiple variants of an object in the same bucket. \nYou can use the S3 Versioning feature to preserve, retrieve, and restore every version of every object stored in your buckets. \nWith versioning you can recover more easily from both unintended user actions and application failures.", - "Message": "Bucket does not have versioning enabled", - "Resolution": "Enable versioning to protect against accidental/malicious removal or modification", - "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0090", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0090" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0091", - "AVDID": "AVD-AWS-0091", - "Title": "S3 Access Block should Ignore Public Acl", - "Description": "S3 buckets should ignore public ACLs on buckets and any objects they contain. By ignoring rather than blocking, PUT calls with public ACLs will still be applied but the ACL will be ignored.", - "Message": "No public access block so not ignoring public acls", - "Resolution": "Enable ignoring the application of public ACLs in PUT calls", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0091", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0091" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0092", - "AVDID": "AVD-AWS-0092", - "Title": "S3 Buckets not publicly accessible through ACL.", - "Description": "Buckets should not have ACLs that allow public access", - "Resolution": "Don't use canned ACLs or switch to private acl", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0092", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0092" - ], - "Status": "PASS", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0093", - "AVDID": "AVD-AWS-0093", - "Title": "S3 Access block should restrict public bucket to limit access", - "Description": "S3 buckets should restrict public policies for the bucket. By enabling, the restrict_public_buckets, only the bucket owner and AWS Services can access if it has a public policy.", - "Message": "No public access block so not restricting public buckets", - "Resolution": "Limit the access to public buckets to only the owner or AWS Services (eg; CloudFront)", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0093", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0093" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0094", - "AVDID": "AVD-AWS-0094", - "Title": "S3 buckets should each define an aws_s3_bucket_public_access_block", - "Description": "The \"block public access\" settings in S3 override individual policies that apply to a given bucket, meaning that all public access can be controlled in one central types for that bucket. It is therefore good practice to define these settings for each bucket in order to clearly define the public access that can be allowed for it.", - "Message": "Bucket does not have a corresponding public access block.", - "Resolution": "Define a aws_s3_bucket_public_access_block for the given bucket to control public access policies", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0094", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0094" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0132", - "AVDID": "AVD-AWS-0132", - "Title": "S3 encryption should use Customer Managed Keys", - "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", - "Message": "Bucket does not encrypt data with a customer managed key.", - "Resolution": "Enable encryption using customer managed keys", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0132" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - } - ] - } - ] -} -` - -const expectedS3AndCloudTrailResult = `{ - "CreatedAt": "2021-08-25T12:20:30.000000005Z", - "ArtifactName": "123456789", - "ArtifactType": "aws_account", - "Metadata": { - "ImageConfig": { - "architecture": "", - "created": "0001-01-01T00:00:00Z", - "os": "", - "rootfs": { - "type": "", - "diff_ids": null - }, - "config": {} - } - }, - "Results": [ - { - "Target": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "Class": "config", - "Type": "cloud", - "MisconfSummary": { - "Successes": 1, - "Failures": 3, - "Exceptions": 0 - }, - "Misconfigurations": [ - { - "Type": "AWS", - "ID": "AVD-AWS-0014", - "AVDID": "AVD-AWS-0014", - "Title": "Cloudtrail should be enabled in all regions regardless of where your AWS resources are generally homed", - "Description": "When creating Cloudtrail in the AWS Management Console the trail is configured by default to be multi-region, this isn't the case with the Terraform resource. Cloudtrail should cover the full AWS account to ensure you can track changes in regions you are not actively operting in.", - "Resolution": "Enable Cloudtrail in all regions", - "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0014", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0014" - ], - "Status": "PASS", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "Provider": "aws", - "Service": "cloudtrail", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0015", - "AVDID": "AVD-AWS-0015", - "Title": "CloudTrail should use Customer managed keys to encrypt the logs", - "Description": "Using Customer managed keys provides comprehensive control over cryptographic keys, enabling management of policies, permissions, and rotation, thus enhancing security and compliance measures for sensitive data and systems.", - "Message": "CloudTrail does not use a customer managed key to encrypt the logs.", - "Resolution": "Use Customer managed key", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0015", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0015" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "Provider": "aws", - "Service": "cloudtrail", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0016", - "AVDID": "AVD-AWS-0016", - "Title": "Cloudtrail log validation should be enabled to prevent tampering of log data", - "Description": "Log validation should be activated on Cloudtrail logs to prevent the tampering of the underlying data in the S3 bucket. It is feasible that a rogue actor compromising an AWS account might want to modify the log data to remove trace of their actions.", - "Message": "Trail does not have log validation enabled.", - "Resolution": "Turn on log validation for Cloudtrail", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0016", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0016" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "Provider": "aws", - "Service": "cloudtrail", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0162", - "AVDID": "AVD-AWS-0162", - "Title": "CloudTrail logs should be stored in S3 and also sent to CloudWatch Logs", - "Description": "CloudTrail is a web service that records AWS API calls made in a given account. The recorded information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by the AWS service.\n\nCloudTrail uses Amazon S3 for log file storage and delivery, so log files are stored durably. In addition to capturing CloudTrail logs in a specified Amazon S3 bucket for long-term analysis, you can perform real-time analysis by configuring CloudTrail to send logs to CloudWatch Logs.\n\nFor a trail that is enabled in all Regions in an account, CloudTrail sends log files from all those Regions to a CloudWatch Logs log group.", - "Message": "Trail does not have CloudWatch logging configured", - "Resolution": "Enable logging to CloudWatch", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0162", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0162" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "Provider": "aws", - "Service": "cloudtrail", - "Code": { - "Lines": null - } - } - } - ] - }, - { - "Target": "arn:aws:s3:::examplebucket", - "Class": "config", - "Type": "cloud", - "MisconfSummary": { - "Successes": 1, - "Failures": 8, - "Exceptions": 0 - }, - "Misconfigurations": [ - { - "Type": "AWS", - "ID": "AVD-AWS-0086", - "AVDID": "AVD-AWS-0086", - "Title": "S3 Access block should block public ACL", - "Description": "S3 buckets should block public ACLs on buckets and any objects they contain. By blocking, PUTs with fail if the object has any public ACL a.", - "Message": "No public access block so not blocking public acls", - "Resolution": "Enable blocking any PUT calls with a public ACL specified", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0086", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0086" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0087", - "AVDID": "AVD-AWS-0087", - "Title": "S3 Access block should block public policy", - "Description": "S3 bucket policy should have block public policy to prevent users from putting a policy that enable public access.", - "Message": "No public access block so not blocking public policies", - "Resolution": "Prevent policies that allow public access being PUT", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0087", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0087" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0088", - "AVDID": "AVD-AWS-0088", - "Title": "Unencrypted S3 bucket.", - "Description": "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.", - "Message": "Bucket does not have encryption enabled", - "Resolution": "Configure bucket encryption", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0088", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0088" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0090", - "AVDID": "AVD-AWS-0090", - "Title": "S3 Data should be versioned", - "Description": "Versioning in Amazon S3 is a means of keeping multiple variants of an object in the same bucket. \nYou can use the S3 Versioning feature to preserve, retrieve, and restore every version of every object stored in your buckets. \nWith versioning you can recover more easily from both unintended user actions and application failures.", - "Message": "Bucket does not have versioning enabled", - "Resolution": "Enable versioning to protect against accidental/malicious removal or modification", - "Severity": "MEDIUM", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0090", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0090" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0091", - "AVDID": "AVD-AWS-0091", - "Title": "S3 Access Block should Ignore Public Acl", - "Description": "S3 buckets should ignore public ACLs on buckets and any objects they contain. By ignoring rather than blocking, PUT calls with public ACLs will still be applied but the ACL will be ignored.", - "Message": "No public access block so not ignoring public acls", - "Resolution": "Enable ignoring the application of public ACLs in PUT calls", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0091", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0091" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0092", - "AVDID": "AVD-AWS-0092", - "Title": "S3 Buckets not publicly accessible through ACL.", - "Description": "Buckets should not have ACLs that allow public access", - "Resolution": "Don't use canned ACLs or switch to private acl", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0092", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0092" - ], - "Status": "PASS", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0093", - "AVDID": "AVD-AWS-0093", - "Title": "S3 Access block should restrict public bucket to limit access", - "Description": "S3 buckets should restrict public policies for the bucket. By enabling, the restrict_public_buckets, only the bucket owner and AWS Services can access if it has a public policy.", - "Message": "No public access block so not restricting public buckets", - "Resolution": "Limit the access to public buckets to only the owner or AWS Services (eg; CloudFront)", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0093", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0093" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0094", - "AVDID": "AVD-AWS-0094", - "Title": "S3 buckets should each define an aws_s3_bucket_public_access_block", - "Description": "The \"block public access\" settings in S3 override individual policies that apply to a given bucket, meaning that all public access can be controlled in one central types for that bucket. It is therefore good practice to define these settings for each bucket in order to clearly define the public access that can be allowed for it.", - "Message": "Bucket does not have a corresponding public access block.", - "Resolution": "Define a aws_s3_bucket_public_access_block for the given bucket to control public access policies", - "Severity": "LOW", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0094", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0094" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-0132", - "AVDID": "AVD-AWS-0132", - "Title": "S3 encryption should use Customer Managed Keys", - "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", - "Message": "Bucket does not encrypt data with a customer managed key.", - "Resolution": "Enable encryption using customer managed keys", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-0132" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:::examplebucket", - "Provider": "aws", - "Service": "s3", - "Code": { - "Lines": null - } - } - } - ] - } - ] -} -` - -func Test_Run(t *testing.T) { - regoDir := t.TempDir() - - tests := []struct { - name string - options flag.Options - want string - expectErr bool - cacheContent string - regoPolicy string - allServices []string - inputData string - ignoreFile string - }{ - { - name: "succeed with cached infra", - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - Services: []string{"s3"}, - Account: "12345678", - }, - CloudOptions: flag.CloudOptions{ - MaxCacheAge: time.Hour * 24 * 365 * 100, - }, - MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, - }, - cacheContent: "testdata/s3onlycache.json", - allServices: []string{"s3"}, - want: expectedS3ScanResult, - }, - { - name: "custom rego rule with passed results", - options: flag.Options{ - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - Services: []string{"s3"}, - Account: "12345678", - }, - CloudOptions: flag.CloudOptions{ - MaxCacheAge: time.Hour * 24 * 365 * 100, - }, - RegoOptions: flag.RegoOptions{ - Trace: true, - CheckPaths: []string{ - filepath.Join(regoDir, "policies"), - }, - CheckNamespaces: []string{ - "user", - }, - DataPaths: []string{ - filepath.Join(regoDir, "data"), - }, - SkipCheckUpdate: true, - }, - MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, - }, - regoPolicy: `# METADATA -# title: Bad input data -# description: Just failing rule with input data -# scope: package -# schemas: -# - input: schema["input"] -# custom: -# severity: LOW -# service: s3 -# input: -# selector: -# - type: cloud -package user.whatever -import data.settings.DS123.foo - -deny { - foo == true -} -`, - inputData: `{ - "settings": { - "DS123": { - "foo": true - } - } -}`, - cacheContent: filepath.Join("testdata", "s3onlycache.json"), - allServices: []string{"s3"}, - want: expectedCustomScanResult, - }, - { - name: "compliance report summary", - options: flag.Options{ - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - Services: []string{"s3"}, - Account: "12345678", - }, - CloudOptions: flag.CloudOptions{ - MaxCacheAge: time.Hour * 24 * 365 * 100, - }, - ReportOptions: flag.ReportOptions{ - Compliance: spec.ComplianceSpec{ - Spec: iacTypes.Spec{ - ID: "@testdata/example-spec.yaml", - Title: "my-custom-spec", - Description: "My fancy spec", - Version: "1.2", - Controls: []iacTypes.Control{ - { - ID: "1.1", - Name: "Unencrypted S3 bucket", - Description: "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.", - Checks: []iacTypes.SpecCheck{ - {ID: "AVD-AWS-0088"}, - }, - Severity: "HIGH", - }, - }, - }, - }, - Format: "table", - ReportFormat: "summary", - }, - RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, - }, - cacheContent: "testdata/s3onlycache.json", - allServices: []string{"s3"}, - want: ` -Summary Report for compliance: my-custom-spec -┌─────┬──────────┬───────────────────────┬────────┬────────┐ -│ ID │ Severity │ Control Name │ Status │ Issues │ -├─────┼──────────┼───────────────────────┼────────┼────────┤ -│ 1.1 │ HIGH │ Unencrypted S3 bucket │ FAIL │ 1 │ -└─────┴──────────┴───────────────────────┴────────┴────────┘ -`, - }, - { - name: "scan an unsupported service", - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - Account: "123456789", - Services: []string{"theultimateservice"}, - }, - CloudOptions: flag.CloudOptions{ - MaxCacheAge: time.Hour * 24 * 365 * 100, - }, - MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, - }, - cacheContent: "testdata/s3onlycache.json", - expectErr: true, - }, - { - name: "scan every service", - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - Account: "123456789", - }, - CloudOptions: flag.CloudOptions{ - MaxCacheAge: time.Hour * 24 * 365 * 100, - }, - MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, - }, - cacheContent: "testdata/s3andcloudtrailcache.json", - allServices: []string{ - "s3", - "cloudtrail", - }, - want: expectedS3AndCloudTrailResult, - }, - { - name: "skip certain services and include specific services", - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - Services: []string{"s3"}, - SkipServices: []string{"cloudtrail"}, - Account: "123456789", - }, - CloudOptions: flag.CloudOptions{ - MaxCacheAge: time.Hour * 24 * 365 * 100, - }, - MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, - }, - cacheContent: "testdata/s3andcloudtrailcache.json", - allServices: []string{ - "s3", - "cloudtrail", - }, - // we skip cloudtrail but still expect results from it as it is cached - want: expectedS3AndCloudTrailResult, - }, - { - name: "only skip certain services but scan the rest", - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - SkipServices: []string{ - "cloudtrail", - "iam", - }, - Account: "12345678", - }, - CloudOptions: flag.CloudOptions{ - MaxCacheAge: time.Hour * 24 * 365 * 100, - }, - MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, - }, - allServices: []string{ - "s3", - "cloudtrail", - "iam", - }, - cacheContent: "testdata/s3onlycache.json", - want: expectedS3ScanResult, - }, - { - name: "fail - service specified to both include and exclude", - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - Services: []string{"s3"}, - SkipServices: []string{"s3"}, - Account: "123456789", - }, - CloudOptions: flag.CloudOptions{ - MaxCacheAge: time.Hour * 24 * 365 * 100, - }, - MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, - }, - cacheContent: "testdata/s3andcloudtrailcache.json", - expectErr: true, - }, - { - name: "ignore findings with .trivyignore", - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipCheckUpdate: true}, - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - Services: []string{"s3"}, - Account: "12345678", - }, - CloudOptions: flag.CloudOptions{ - MaxCacheAge: time.Hour * 24 * 365 * 100, - }, - MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, - }, - cacheContent: "testdata/s3onlycache.json", - allServices: []string{"s3"}, - ignoreFile: "testdata/.trivyignore", - want: expectedS3ScanResultWithExceptions, - }, - } - - ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - if test.allServices != nil { - oldAllSupportedServicesFunc := allSupportedServicesFunc - allSupportedServicesFunc = func() []string { - return test.allServices - } - defer func() { - allSupportedServicesFunc = oldAllSupportedServicesFunc - }() - } - - output := bytes.NewBuffer(nil) - test.options.SetOutputWriter(output) - test.options.Debug = true - test.options.GlobalOptions.Timeout = time.Minute - if test.options.Format == "" { - test.options.Format = "json" - } - test.options.Severities = []dbTypes.Severity{ - dbTypes.SeverityUnknown, - dbTypes.SeverityLow, - dbTypes.SeverityMedium, - dbTypes.SeverityHigh, - dbTypes.SeverityCritical, - } - - if test.regoPolicy != "" { - require.NoError(t, os.MkdirAll(filepath.Join(regoDir, "policies"), 0755)) - require.NoError(t, os.WriteFile(filepath.Join(regoDir, "policies", "user.rego"), []byte(test.regoPolicy), 0644)) - } - - if test.inputData != "" { - require.NoError(t, os.MkdirAll(filepath.Join(regoDir, "data"), 0755)) - require.NoError(t, os.WriteFile(filepath.Join(regoDir, "data", "data.json"), []byte(test.inputData), 0644)) - } - - if test.cacheContent != "" { - cacheRoot := t.TempDir() - test.options.CacheDir = cacheRoot - cacheFile := filepath.Join(cacheRoot, "cloud", "aws", test.options.Account, test.options.Region, "data.json") - require.NoError(t, os.MkdirAll(filepath.Dir(cacheFile), 0700)) - - cacheData, err := os.ReadFile(test.cacheContent) - require.NoError(t, err, test.name) - - require.NoError(t, os.WriteFile(cacheFile, cacheData, 0600)) - } - - if test.ignoreFile != "" { - test.options.ReportOptions.IgnoreFile = test.ignoreFile - } - - err := Run(ctx, test.options) - if test.expectErr { - require.Error(t, err) - return - } - require.NoError(t, err) - assert.Equal(t, test.want, output.String()) - }) - } -} diff --git a/pkg/cloud/aws/commands/testdata/.trivyignore b/pkg/cloud/aws/commands/testdata/.trivyignore deleted file mode 100644 index 44ef395ee173..000000000000 --- a/pkg/cloud/aws/commands/testdata/.trivyignore +++ /dev/null @@ -1,8 +0,0 @@ -AVD-AWS-0086 -AVD-AWS-0087 -AVD-AWS-0088 -AVD-AWS-0090 -AVD-AWS-0132 -AVD-AWS-0091 -AVD-AWS-0092 -AVD-AWS-0093 \ No newline at end of file diff --git a/pkg/cloud/aws/commands/testdata/example-spec.yaml b/pkg/cloud/aws/commands/testdata/example-spec.yaml deleted file mode 100644 index 19fbf0a3bf31..000000000000 --- a/pkg/cloud/aws/commands/testdata/example-spec.yaml +++ /dev/null @@ -1,13 +0,0 @@ -spec: - id: "0001" - title: my-custom-spec - description: My fancy spec - version: "1.2" - controls: - - id: "1.1" - name: Unencrypted S3 bucket - description: |- - S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised. - checks: - - id: AVD-AWS-0088 - severity: HIGH \ No newline at end of file diff --git a/pkg/cloud/aws/commands/testdata/s3andcloudtrailcache.json b/pkg/cloud/aws/commands/testdata/s3andcloudtrailcache.json deleted file mode 100644 index f9cfd2abcec2..000000000000 --- a/pkg/cloud/aws/commands/testdata/s3andcloudtrailcache.json +++ /dev/null @@ -1,420 +0,0 @@ -{ - "schema_version": 2, - "state": { - "AWS": { - "S3": { - "Buckets": [{ - "Metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "Name": { - "metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": "examplebucket" - }, - "PublicAccessBlock": null, - "BucketPolicies": null, - "Encryption": { - "Metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "Enabled": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": false - }, - "Algorithm": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": "" - }, - "KMSKeyId": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": "" - } - }, - "Versioning": { - "Metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "Enabled": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": false - }, - "MFADelete": { - "metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": false - } - }, - "Logging": { - "Metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "Enabled": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": false - }, - "TargetBucket": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": "" - } - }, - "ACL": { - "metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": "private" - } - }] - }, - "CloudTrail": { - "Trails": [{ - "Metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "unresolvable": false - }, - "Name": { - "metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "unresolvable": false - }, - "value": "management-events" - }, - "EnableLogFileValidation": { - "metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "unresolvable": false - }, - "value": false - }, - "IsMultiRegion": { - "metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "unresolvable": false - }, - "value": true - }, - "KMSKeyID": { - "metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "unresolvable": false - }, - "value": "" - }, - "CloudWatchLogsLogGroupArn": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "unresolvable": false - }, - "value": "" - }, - "IsLogging": { - "metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "unresolvable": false - }, - "value": true - }, - "BucketName": { - "metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", - "unresolvable": false - }, - "value": "aws-cloudtrail-logs-12345678-d0a47f2f" - }, - "EventSelectors": null - }] - } - } - - }, - "service_metadata": { - "s3": { - "name": "s3", - "updated": "2022-10-04T14:08:36.659817426+01:00" - }, - "cloudtrail": { - "name": "cloudtrail", - "updated": "2022-10-04T14:08:36.659817426+01:00" - } - }, - "updated": "2022-10-04T14:08:36.659817426+01:00" -} diff --git a/pkg/cloud/aws/commands/testdata/s3onlycache.json b/pkg/cloud/aws/commands/testdata/s3onlycache.json deleted file mode 100644 index 43a015aa9ca9..000000000000 --- a/pkg/cloud/aws/commands/testdata/s3onlycache.json +++ /dev/null @@ -1,261 +0,0 @@ -{ - "schema_version": 2, - "state": { - "AWS": { - "S3": { - "Buckets": [{ - "Metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "Name": { - "metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": "examplebucket" - }, - "PublicAccessBlock": null, - "BucketPolicies": null, - "Encryption": { - "Metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "Enabled": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": false - }, - "Algorithm": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": "" - }, - "KMSKeyId": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": "" - } - }, - "Versioning": { - "Metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "Enabled": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": false - }, - "MFADelete": { - "metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": false - } - }, - "Logging": { - "Metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "Enabled": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": false - }, - "TargetBucket": { - "metadata": { - "default": true, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": "" - } - }, - "ACL": { - "metadata": { - "default": false, - "explicit": false, - "managed": true, - "parent": null, - "range": { - "endLine": 0, - "filename": "arn:aws:s3:::examplebucket", - "fsKey": "", - "isLogicalSource": false, - "sourcePrefix": "remote", - "startLine": 0 - }, - "ref": "arn:aws:s3:::examplebucket", - "unresolvable": false - }, - "value": "private" - } - }] - } - } - }, - "service_metadata": { - "s3": { - "name": "s3", - "updated": "2022-10-04T14:08:36.659817426+01:00" - } - }, - "updated": "2022-10-04T14:08:36.659817426+01:00" -} diff --git a/pkg/cloud/aws/config/config.go b/pkg/cloud/aws/config/config.go index e4ef5f2f70dc..e173840cdaec 100644 --- a/pkg/cloud/aws/config/config.go +++ b/pkg/cloud/aws/config/config.go @@ -9,14 +9,14 @@ import ( ) func EndpointResolver(endpoint string) aws.EndpointResolverWithOptionsFunc { - return aws.EndpointResolverWithOptionsFunc(func(_, reg string, options ...any) (aws.Endpoint, error) { + return func(_, reg string, options ...any) (aws.Endpoint, error) { return aws.Endpoint{ PartitionID: "aws", URL: endpoint, SigningRegion: reg, Source: aws.EndpointSourceCustom, }, nil - }) + } } func MakeAWSOptions(region, endpoint string) []func(*awsconfig.LoadOptions) error { diff --git a/pkg/cloud/aws/scanner/progress.go b/pkg/cloud/aws/scanner/progress.go deleted file mode 100644 index a313dd482c6c..000000000000 --- a/pkg/cloud/aws/scanner/progress.go +++ /dev/null @@ -1,83 +0,0 @@ -package scanner - -import ( - "fmt" - "io" - "os" - - "github.com/aquasecurity/loading/pkg/bar" -) - -type progressTracker struct { - serviceBar *bar.Bar - serviceTotal int - serviceCurrent int - isTTY bool - debugWriter io.Writer -} - -func newProgressTracker(w io.Writer) *progressTracker { - var isTTY bool - if stat, err := os.Stdout.Stat(); err == nil { - isTTY = stat.Mode()&os.ModeCharDevice == os.ModeCharDevice - } - return &progressTracker{ - isTTY: isTTY, - debugWriter: w, - } -} - -func (m *progressTracker) Finish() { - if !m.isTTY || m.serviceBar == nil { - return - } - m.serviceBar.Finish() -} - -func (m *progressTracker) IncrementResource() { - if !m.isTTY { - return - } - m.serviceBar.Increment() -} - -func (m *progressTracker) SetTotalResources(i int) { - if !m.isTTY { - return - } - m.serviceBar.SetTotal(i) -} - -func (m *progressTracker) SetTotalServices(i int) { - m.serviceTotal = i -} - -func (m *progressTracker) SetServiceLabel(label string) { - if !m.isTTY { - return - } - m.serviceBar.SetLabel("└╴" + label) - m.serviceBar.SetCurrent(0) -} - -func (m *progressTracker) FinishService() { - if !m.isTTY { - return - } - m.serviceCurrent++ - m.serviceBar.Finish() -} - -func (m *progressTracker) StartService(name string) { - if !m.isTTY { - return - } - - fmt.Fprintf(m.debugWriter, "[%d/%d] Scanning %s...\n", m.serviceCurrent+1, m.serviceTotal, name) - m.serviceBar = bar.New( - bar.OptionHideOnFinish(true), - bar.OptionWithAutoComplete(false), - bar.OptionWithRenderFunc(bar.RenderColoured(0xff, 0x66, 0x00)), - ) - m.SetServiceLabel("Initializing...") -} diff --git a/pkg/cloud/aws/scanner/scanner.go b/pkg/cloud/aws/scanner/scanner.go deleted file mode 100644 index 6ea5f919f24d..000000000000 --- a/pkg/cloud/aws/scanner/scanner.go +++ /dev/null @@ -1,176 +0,0 @@ -package scanner - -import ( - "context" - "fmt" - "io/fs" - - "golang.org/x/xerrors" - - aws "github.com/aquasecurity/trivy-aws/pkg/scanner" - "github.com/aquasecurity/trivy/pkg/cloud/aws/cache" - "github.com/aquasecurity/trivy/pkg/commands/operation" - "github.com/aquasecurity/trivy/pkg/flag" - "github.com/aquasecurity/trivy/pkg/iac/framework" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - "github.com/aquasecurity/trivy/pkg/iac/state" - "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/misconf" -) - -type AWSScanner struct { - logger *log.Logger -} - -func NewScanner() *AWSScanner { - return &AWSScanner{ - logger: log.WithPrefix("aws"), - } -} - -func (s *AWSScanner) Scan(ctx context.Context, option flag.Options) (scan.Results, bool, error) { - - awsCache := cache.New(option.CacheDir, option.MaxCacheAge, option.Account, option.Region) - included, missing := awsCache.ListServices(option.Services) - - prefixedLogger := log.NewWriteLogger(log.WithPrefix("aws")) - - var scannerOpts []options.ScannerOption - if !option.NoProgress { - tracker := newProgressTracker(prefixedLogger) - defer tracker.Finish() - scannerOpts = append(scannerOpts, aws.ScannerWithProgressTracker(tracker)) - } - - if len(missing) > 0 { - scannerOpts = append(scannerOpts, aws.ScannerWithAWSServices(missing...)) - } - - if option.Debug { - scannerOpts = append(scannerOpts, options.ScannerWithDebug(prefixedLogger)) - } - - if option.Trace { - scannerOpts = append(scannerOpts, options.ScannerWithTrace(prefixedLogger)) - } - - if option.Region != "" { - scannerOpts = append( - scannerOpts, - aws.ScannerWithAWSRegion(option.Region), - ) - } - - if option.Endpoint != "" { - scannerOpts = append( - scannerOpts, - aws.ScannerWithAWSEndpoint(option.Endpoint), - ) - } - - var policyPaths []string - var downloadedPolicyPaths []string - var err error - - downloadedPolicyPaths, err = operation.InitBuiltinPolicies(context.Background(), option.CacheDir, option.Quiet, option.SkipCheckUpdate, option.MisconfOptions.ChecksBundleRepository, option.RegistryOpts()) - if err != nil { - if !option.SkipCheckUpdate { - s.logger.Error("Falling back to embedded checks", log.Err(err)) - } - } else { - s.logger.Debug("Checks successfully loaded from disk") - policyPaths = append(policyPaths, downloadedPolicyPaths...) - scannerOpts = append(scannerOpts, - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithEmbeddedLibraries(false)) - } - - var policyFS fs.FS - policyFS, policyPaths, err = misconf.CreatePolicyFS(append(policyPaths, option.RegoOptions.CheckPaths...)) - if err != nil { - return nil, false, xerrors.Errorf("unable to create policyfs: %w", err) - } - - scannerOpts = append(scannerOpts, - options.ScannerWithPolicyFilesystem(policyFS), - options.ScannerWithPolicyDirs(policyPaths...), - ) - - dataFS, dataPaths, err := misconf.CreateDataFS(option.RegoOptions.DataPaths) - if err != nil { - s.logger.Error("Could not load config data", log.Err(err)) - } - scannerOpts = append(scannerOpts, - options.ScannerWithDataDirs(dataPaths...), - options.ScannerWithDataFilesystem(dataFS), - ) - - scannerOpts = addPolicyNamespaces(option.RegoOptions.CheckNamespaces, scannerOpts) - - if option.Compliance.Spec.ID != "" { - scannerOpts = append(scannerOpts, options.ScannerWithSpec(option.Compliance.Spec.ID)) - } else { - scannerOpts = append(scannerOpts, options.ScannerWithFrameworks( - framework.Default, - framework.CIS_AWS_1_2)) - } - - scanner := aws.New(scannerOpts...) - - var freshState *state.State - if len(missing) > 0 || option.CloudOptions.UpdateCache { - var err error - freshState, err = scanner.CreateState(ctx) - if err != nil { - return nil, false, err - } - } - - fullState, err := createState(freshState, awsCache) - if err != nil { - return nil, false, err - } - - if fullState == nil { - return nil, false, fmt.Errorf("no resultant state found") - } - - if err := awsCache.AddServices(fullState, missing); err != nil { - return nil, false, err - } - - defsecResults, err := scanner.Scan(ctx, fullState) - if err != nil { - return nil, false, err - } - - return defsecResults, len(included) > 0, nil -} - -func createState(freshState *state.State, awsCache *cache.Cache) (*state.State, error) { - var fullState *state.State - if previousState, err := awsCache.LoadState(); err == nil { - if freshState != nil { - fullState, err = previousState.Merge(freshState) - if err != nil { - return nil, err - } - } else { - fullState = previousState - } - } else { - fullState = freshState - } - return fullState, nil -} - -func addPolicyNamespaces(namespaces []string, scannerOpts []options.ScannerOption) []options.ScannerOption { - if len(namespaces) > 0 { - scannerOpts = append( - scannerOpts, - options.ScannerWithPolicyNamespaces(namespaces...), - ) - } - return scannerOpts -} diff --git a/pkg/cloud/provider.go b/pkg/cloud/provider.go deleted file mode 100644 index f495e15e2095..000000000000 --- a/pkg/cloud/provider.go +++ /dev/null @@ -1,5 +0,0 @@ -package cloud - -const ( - ProviderAWS = "AWS" -) diff --git a/pkg/cloud/report/convert.go b/pkg/cloud/report/convert.go deleted file mode 100644 index ac8517380cb7..000000000000 --- a/pkg/cloud/report/convert.go +++ /dev/null @@ -1,107 +0,0 @@ -package report - -import ( - "fmt" - "strings" - "time" - - "github.com/aws/aws-sdk-go-v2/aws/arn" - - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/iac/rego" - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/types" -) - -func ConvertResults(results scan.Results, provider string, scoped []string) map[string]ResultsAtTime { - convertedResults := make(map[string]ResultsAtTime) - resultsByServiceAndARN := make(map[string]map[string]scan.Results) - for _, result := range results { - - service := result.Rule().Service - resource := result.Flatten().Resource - if service == "" || service == "general" { - if parsed, err := arn.Parse(resource); err == nil { - service = parsed.Service - } - } - - existingService, ok := resultsByServiceAndARN[service] - if !ok { - existingService = make(map[string]scan.Results) - } - - existingService[resource] = append(existingService[resource], result) - resultsByServiceAndARN[service] = existingService - } - // ensure we have entries for all scoped services, even if there are no results - for _, service := range scoped { - if _, ok := resultsByServiceAndARN[service]; !ok { - resultsByServiceAndARN[service] = nil - } - } - for service, arnResults := range resultsByServiceAndARN { - - var convertedArnResults []types.Result - - for arn, serviceResults := range arnResults { - - arnResult := types.Result{ - Target: arn, - Class: types.ClassConfig, - Type: ftypes.Cloud, - } - - for _, result := range serviceResults { - - var primaryURL string - - // empty namespace implies a go rule from defsec, "builtin" refers to a built-in rego rule - // this ensures we don't generate bad links for custom policies - if result.RegoNamespace() == "" || rego.IsBuiltinNamespace(result.RegoNamespace()) { - primaryURL = fmt.Sprintf("https://avd.aquasec.com/misconfig/%s", strings.ToLower(result.Rule().AVDID)) - } - - status := types.MisconfStatusFailure - switch result.Status() { - case scan.StatusPassed: - status = types.MisconfStatusPassed - case scan.StatusIgnored: - status = types.MisconfStatusException - } - - flat := result.Flatten() - - arnResult.Misconfigurations = append(arnResult.Misconfigurations, types.DetectedMisconfiguration{ - Type: provider, - ID: result.Rule().AVDID, - AVDID: result.Rule().AVDID, - Title: result.Rule().Summary, - Description: strings.TrimSpace(result.Rule().Explanation), - Message: strings.TrimSpace(result.Description()), - Namespace: result.RegoNamespace(), - Query: result.RegoRule(), - Resolution: result.Rule().Resolution, - Severity: string(result.Severity()), - PrimaryURL: primaryURL, - References: []string{primaryURL}, - Status: status, - CauseMetadata: ftypes.CauseMetadata{ - Resource: flat.Resource, - Provider: string(flat.RuleProvider), - Service: service, - StartLine: flat.Location.StartLine, - EndLine: flat.Location.EndLine, - }, - }) - } - - convertedArnResults = append(convertedArnResults, arnResult) - } - convertedResults[service] = ResultsAtTime{ - Results: convertedArnResults, - CreationTime: time.Now(), - } - } - return convertedResults -} diff --git a/pkg/cloud/report/convert_test.go b/pkg/cloud/report/convert_test.go deleted file mode 100644 index b8a0b728c53f..000000000000 --- a/pkg/cloud/report/convert_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package report - -import ( - "sort" - "testing" - - "github.com/aws/aws-sdk-go-v2/aws/arn" - "github.com/stretchr/testify/assert" - - fanaltypes "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/iac/scan" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/aquasecurity/trivy/pkg/types" -) - -func Test_ResultConversion(t *testing.T) { - - tests := []struct { - name string - results scan.Results - provider string - scoped []string - expected map[string]ResultsAtTime - }{ - { - name: "no results", - results: scan.Results{}, - provider: "AWS", - expected: make(map[string]ResultsAtTime), - }, - { - name: "no results, multiple scoped services", - results: scan.Results{}, - provider: "AWS", - scoped: []string{"s3", "ec2"}, - expected: map[string]ResultsAtTime{ - "s3": {}, - "ec2": {}, - }, - }, - { - name: "multiple results", - results: func() scan.Results { - - baseRule := scan.Rule{ - AVDID: "AVD-AWS-9999", - Aliases: []string{"AWS999"}, - ShortCode: "no-bad-stuff", - Summary: "Do not use bad stuff", - Explanation: "Bad stuff is... bad", - Impact: "Bad things", - Resolution: "Remove bad stuff", - Provider: "AWS", - Severity: "HIGH", - } - - var s3Results scan.Results - s3Results.Add( - "something failed", - iacTypes.NewRemoteMetadata((arn.ARN{ - Partition: "aws", - Service: "s3", - Region: "us-east-1", - AccountID: "1234567890", - Resource: "bucket1", - }).String()), - ) - s3Results.Add( - "something else failed", - iacTypes.NewRemoteMetadata((arn.ARN{ - Partition: "aws", - Service: "s3", - Region: "us-east-1", - AccountID: "1234567890", - Resource: "bucket2", - }).String()), - ) - s3Results.Add( - "something else failed again", - iacTypes.NewRemoteMetadata((arn.ARN{ - Partition: "aws", - Service: "s3", - Region: "us-east-1", - AccountID: "1234567890", - Resource: "bucket2", - }).String()), - ) - baseRule.Service = "s3" - s3Results.SetRule(baseRule) - var ec2Results scan.Results - ec2Results.Add( - "instance is bad", - iacTypes.NewRemoteMetadata((arn.ARN{ - Partition: "aws", - Service: "ec2", - Region: "us-east-1", - AccountID: "1234567890", - Resource: "instance1", - }).String()), - ) - baseRule.Service = "ec2" - ec2Results.SetRule(baseRule) - return append(s3Results, ec2Results...) - }(), - provider: "AWS", - expected: map[string]ResultsAtTime{ - "s3": { - Results: types.Results{ - { - Target: "arn:aws:s3:us-east-1:1234567890:bucket1", - Class: "config", - Type: "cloud", - Misconfigurations: []types.DetectedMisconfiguration{ - { - Type: "AWS", - ID: "AVD-AWS-9999", - AVDID: "AVD-AWS-9999", - Title: "Do not use bad stuff", - Description: "Bad stuff is... bad", - Message: "something failed", - Resolution: "Remove bad stuff", - Severity: "HIGH", - PrimaryURL: "https://avd.aquasec.com/misconfig/avd-aws-9999", - References: []string{ - "https://avd.aquasec.com/misconfig/avd-aws-9999", - }, - Status: "FAIL", - CauseMetadata: fanaltypes.CauseMetadata{ - Resource: "arn:aws:s3:us-east-1:1234567890:bucket1", - Provider: "AWS", - Service: "s3", - StartLine: 0, - EndLine: 0, - Code: fanaltypes.Code{}, - }, - }, - }, - }, - { - Target: "arn:aws:s3:us-east-1:1234567890:bucket2", - Class: "config", - Type: "cloud", - Misconfigurations: []types.DetectedMisconfiguration{ - { - Type: "AWS", - ID: "AVD-AWS-9999", - AVDID: "AVD-AWS-9999", - Title: "Do not use bad stuff", - Description: "Bad stuff is... bad", - Message: "something else failed", - Resolution: "Remove bad stuff", - Severity: "HIGH", - PrimaryURL: "https://avd.aquasec.com/misconfig/avd-aws-9999", - References: []string{ - "https://avd.aquasec.com/misconfig/avd-aws-9999", - }, - Status: "FAIL", - CauseMetadata: fanaltypes.CauseMetadata{ - Resource: "arn:aws:s3:us-east-1:1234567890:bucket2", - Provider: "AWS", - Service: "s3", - }, - }, - { - Type: "AWS", - ID: "AVD-AWS-9999", - AVDID: "AVD-AWS-9999", - Title: "Do not use bad stuff", - Description: "Bad stuff is... bad", - Message: "something else failed again", - Resolution: "Remove bad stuff", - Severity: "HIGH", - PrimaryURL: "https://avd.aquasec.com/misconfig/avd-aws-9999", - References: []string{ - "https://avd.aquasec.com/misconfig/avd-aws-9999", - }, - Status: "FAIL", - CauseMetadata: fanaltypes.CauseMetadata{ - Resource: "arn:aws:s3:us-east-1:1234567890:bucket2", - Provider: "AWS", - Service: "s3", - }, - }, - }, - }, - }, - }, - "ec2": { - Results: types.Results{ - { - Target: "arn:aws:ec2:us-east-1:1234567890:instance1", - Class: "config", - Type: "cloud", - Misconfigurations: []types.DetectedMisconfiguration{ - { - Type: "AWS", - ID: "AVD-AWS-9999", - AVDID: "AVD-AWS-9999", - Title: "Do not use bad stuff", - Description: "Bad stuff is... bad", - Message: "instance is bad", - Resolution: "Remove bad stuff", - Severity: "HIGH", - PrimaryURL: "https://avd.aquasec.com/misconfig/avd-aws-9999", - References: []string{ - "https://avd.aquasec.com/misconfig/avd-aws-9999", - }, - Status: "FAIL", - CauseMetadata: fanaltypes.CauseMetadata{ - Resource: "arn:aws:ec2:us-east-1:1234567890:instance1", - Provider: "AWS", - Service: "ec2", - }, - }, - }, - }, - }, - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - converted := ConvertResults(test.results, test.provider, test.scoped) - assertConvertedResultsMatch(t, test.expected, converted) - }) - } - -} - -func assertConvertedResultsMatch(t *testing.T, expected, actual map[string]ResultsAtTime) { - assert.Equal(t, len(expected), len(actual)) - for service, resultsAtTime := range expected { - _, ok := actual[service] - assert.True(t, ok) - sort.Slice(actual[service].Results, func(i, j int) bool { - return actual[service].Results[i].Target < actual[service].Results[j].Target - }) - assert.ElementsMatch(t, resultsAtTime.Results, actual[service].Results) - } -} diff --git a/pkg/cloud/report/report.go b/pkg/cloud/report/report.go deleted file mode 100644 index 55d992fc505e..000000000000 --- a/pkg/cloud/report/report.go +++ /dev/null @@ -1,160 +0,0 @@ -package report - -import ( - "context" - "io" - "os" - "sort" - "time" - - "golang.org/x/xerrors" - - "github.com/aquasecurity/tml" - "github.com/aquasecurity/trivy/pkg/clock" - cr "github.com/aquasecurity/trivy/pkg/compliance/report" - "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/flag" - "github.com/aquasecurity/trivy/pkg/iac/scan" - pkgReport "github.com/aquasecurity/trivy/pkg/report" - "github.com/aquasecurity/trivy/pkg/result" - "github.com/aquasecurity/trivy/pkg/types" -) - -const ( - tableFormat = "table" -) - -// Report represents an AWS scan report -type Report struct { - Provider string - AccountID string - Region string - Results map[string]ResultsAtTime - ServicesInScope []string -} - -type ResultsAtTime struct { - Results types.Results - CreationTime time.Time -} - -func New(provider, accountID, region string, defsecResults scan.Results, scopedServices []string) *Report { - return &Report{ - Provider: provider, - AccountID: accountID, - Results: ConvertResults(defsecResults, provider, scopedServices), - ServicesInScope: scopedServices, - Region: region, - } -} - -// Failed returns whether the aws report includes any "failed" results -func (r *Report) Failed() bool { - for _, set := range r.Results { - if set.Results.Failed() { - return true - } - } - return false -} - -// Write writes the results in the give format -func Write(ctx context.Context, rep *Report, opt flag.Options, fromCache bool) error { - output, cleanup, err := opt.OutputWriter(ctx) - if err != nil { - return xerrors.Errorf("failed to create output file: %w", err) - } - defer cleanup() - - if opt.Compliance.Spec.ID != "" { - return writeCompliance(ctx, rep, opt, output) - } - - ignoreConf, err := result.ParseIgnoreFile(ctx, opt.IgnoreFile) - if err != nil { - return xerrors.Errorf("%s error: %w", opt.IgnoreFile, err) - } - - var filtered []types.Result - - // filter results - for _, resultsAtTime := range rep.Results { - for _, res := range resultsAtTime.Results { - resCopy := res - if err := result.FilterResult(ctx, &resCopy, ignoreConf, opt.FilterOpts()); err != nil { - return err - } - sort.Slice(resCopy.Misconfigurations, func(i, j int) bool { - return resCopy.Misconfigurations[i].CauseMetadata.Resource < resCopy.Misconfigurations[j].CauseMetadata.Resource - }) - filtered = append(filtered, resCopy) - } - } - sort.Slice(filtered, func(i, j int) bool { - return filtered[i].Target < filtered[j].Target - }) - - base := types.Report{ - CreatedAt: clock.Now(ctx), - ArtifactName: rep.AccountID, - ArtifactType: artifact.TypeAWSAccount, - Results: filtered, - } - - switch opt.Format { - case tableFormat: - - // ensure color/formatting is disabled for pipes/non-pty - var useANSI bool - if output == os.Stdout { - if o, err := os.Stdout.Stat(); err == nil { - useANSI = (o.Mode() & os.ModeCharDevice) == os.ModeCharDevice - } - } - if !useANSI { - tml.DisableFormatting() - } - - switch { - case len(opt.Services) == 1 && opt.ARN == "": - if err := writeResourceTable(rep, filtered, output, opt.Services[0]); err != nil { - return err - } - case len(opt.Services) == 1 && opt.ARN != "": - if err := writeResultsForARN(rep, filtered, output, opt.Services[0], opt.ARN, opt.Severities); err != nil { - return err - } - default: - if err := writeServiceTable(rep, filtered, output); err != nil { - return err - } - } - - // render cache info - if fromCache { - _ = tml.Fprintf(output, "\nThis scan report was loaded from cached results. If you'd like to run a fresh scan, use --update-cache.\n") - } - - return nil - default: - return pkgReport.Write(ctx, base, opt) - } -} - -func writeCompliance(ctx context.Context, rep *Report, opt flag.Options, output io.Writer) error { - var crr []types.Results - for _, r := range rep.Results { - crr = append(crr, r.Results) - } - - complianceReport, err := cr.BuildComplianceReport(crr, opt.Compliance) - if err != nil { - return xerrors.Errorf("compliance report build error: %w", err) - } - - return cr.Write(ctx, complianceReport, cr.Option{ - Format: opt.Format, - Report: opt.ReportFormat, - Output: output, - }) -} diff --git a/pkg/cloud/report/resource.go b/pkg/cloud/report/resource.go deleted file mode 100644 index 79b1b8cc2e94..000000000000 --- a/pkg/cloud/report/resource.go +++ /dev/null @@ -1,88 +0,0 @@ -package report - -import ( - "fmt" - "io" - "sort" - "strconv" - - "golang.org/x/term" - - "github.com/aquasecurity/table" - "github.com/aquasecurity/tml" - pkgReport "github.com/aquasecurity/trivy/pkg/report/table" - "github.com/aquasecurity/trivy/pkg/types" -) - -type sortableRow struct { - name string - counts map[string]int -} - -func writeResourceTable(report *Report, results types.Results, output io.Writer, service string) error { - - termWidth, _, err := term.GetSize(0) - if err != nil { - termWidth = 80 - } - maxWidth := termWidth - 48 - if maxWidth < 20 { - maxWidth = 20 - } - - t := table.New(output) - t.SetColumnMaxWidth(maxWidth) - t.SetHeaders("Resource", "Misconfigurations") - t.AddHeaders("Resource", "Critical", "High", "Medium", "Low", "Unknown") - t.SetHeaderVerticalAlignment(table.AlignBottom) - t.SetHeaderAlignment(table.AlignLeft, table.AlignCenter, table.AlignCenter, table.AlignCenter, table.AlignCenter, table.AlignCenter) - t.SetAlignment(table.AlignLeft, table.AlignRight, table.AlignRight, table.AlignRight, table.AlignRight, table.AlignRight) - t.SetRowLines(false) - t.SetAutoMergeHeaders(true) - t.SetHeaderColSpans(0, 1, 5) - - // map resource -> severity -> count - grouped := make(map[string]map[string]int) - for _, result := range results { - for _, misconfiguration := range result.Misconfigurations { - if misconfiguration.CauseMetadata.Service != service { - continue - } - if _, ok := grouped[misconfiguration.CauseMetadata.Resource]; !ok { - grouped[misconfiguration.CauseMetadata.Resource] = make(map[string]int) - } - grouped[misconfiguration.CauseMetadata.Resource][misconfiguration.Severity]++ - } - } - - var sortable []sortableRow - for resource, severityCounts := range grouped { - sortable = append(sortable, sortableRow{ - name: resource, - counts: severityCounts, - }) - } - sort.Slice(sortable, func(i, j int) bool { return sortable[i].name < sortable[j].name }) - for _, row := range sortable { - t.AddRow( - row.name, - pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["CRITICAL"]), "CRITICAL"), - pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["HIGH"]), "HIGH"), - pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["MEDIUM"]), "MEDIUM"), - pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["LOW"]), "LOW"), - pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["UNKNOWN"]), "UNKNOWN"), - ) - } - - // render scan title - _ = tml.Fprintf(output, "\nResource Summary for Service '%s' (%s Account %s)\n", service, report.Provider, report.AccountID) - - // render table - if len(sortable) > 0 { - t.Render() - } else { - _, _ = fmt.Fprint(output, "\nNo problems detected.\n") - } - - return nil -} diff --git a/pkg/cloud/report/resource_test.go b/pkg/cloud/report/resource_test.go deleted file mode 100644 index 3f909b8d3b3f..000000000000 --- a/pkg/cloud/report/resource_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package report - -import ( - "bytes" - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy/pkg/flag" -) - -func Test_ResourceReport(t *testing.T) { - tests := []struct { - name string - options flag.Options - fromCache bool - expected string - }{ - { - name: "simple table output", - options: flag.Options{ - ReportOptions: flag.ReportOptions{ - Format: tableFormat, - Severities: []types.Severity{ - types.SeverityLow, - types.SeverityMedium, - types.SeverityHigh, - types.SeverityCritical, - }, - }, - AWSOptions: flag.AWSOptions{ - Services: []string{"s3"}, - }, - }, - fromCache: false, - expected: ` -Resource Summary for Service 's3' (AWS Account ) -┌─────────────────────────────────────────┬──────────────────────────────────────────┐ -│ │ Misconfigurations │ -│ ├──────────┬──────┬────────┬─────┬─────────┤ -│ Resource │ Critical │ High │ Medium │ Low │ Unknown │ -├─────────────────────────────────────────┼──────────┼──────┼────────┼─────┼─────────┤ -│ arn:aws:s3:us-east-1:1234567890:bucket1 │ 0 │ 1 │ 0 │ 0 │ 0 │ -│ arn:aws:s3:us-east-1:1234567890:bucket2 │ 0 │ 2 │ 0 │ 0 │ 0 │ -└─────────────────────────────────────────┴──────────┴──────┴────────┴─────┴─────────┘ -`, - }, - { - name: "results from cache", - options: flag.Options{ - ReportOptions: flag.ReportOptions{ - Format: tableFormat, - Severities: []types.Severity{ - types.SeverityLow, - types.SeverityMedium, - types.SeverityHigh, - types.SeverityCritical, - }, - }, - AWSOptions: flag.AWSOptions{ - Services: []string{"s3"}, - }, - }, - fromCache: true, - expected: ` -Resource Summary for Service 's3' (AWS Account ) -┌─────────────────────────────────────────┬──────────────────────────────────────────┐ -│ │ Misconfigurations │ -│ ├──────────┬──────┬────────┬─────┬─────────┤ -│ Resource │ Critical │ High │ Medium │ Low │ Unknown │ -├─────────────────────────────────────────┼──────────┼──────┼────────┼─────┼─────────┤ -│ arn:aws:s3:us-east-1:1234567890:bucket1 │ 0 │ 1 │ 0 │ 0 │ 0 │ -│ arn:aws:s3:us-east-1:1234567890:bucket2 │ 0 │ 2 │ 0 │ 0 │ 0 │ -└─────────────────────────────────────────┴──────────┴──────┴────────┴─────┴─────────┘ - -This scan report was loaded from cached results. If you'd like to run a fresh scan, use --update-cache. -`, - }, - { - name: "no problems", - options: flag.Options{ - ReportOptions: flag.ReportOptions{ - Format: tableFormat, - Severities: []types.Severity{ - types.SeverityLow, - }, - }, - AWSOptions: flag.AWSOptions{ - Services: []string{"s3"}, - }, - }, - fromCache: false, - expected: ` -Resource Summary for Service 's3' (AWS Account ) - -No problems detected. -`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - report := New( - "AWS", - tt.options.AWSOptions.Account, - tt.options.AWSOptions.Region, - createTestResults(), - tt.options.AWSOptions.Services, - ) - - output := bytes.NewBuffer(nil) - tt.options.SetOutputWriter(output) - require.NoError(t, Write(context.Background(), report, tt.options, tt.fromCache)) - - assert.Equal(t, "AWS", report.Provider) - assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID) - assert.Equal(t, tt.options.AWSOptions.Region, report.Region) - assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope) - assert.Equal(t, tt.expected, output.String()) - }) - } -} diff --git a/pkg/cloud/report/result.go b/pkg/cloud/report/result.go deleted file mode 100644 index 103be8a40afc..000000000000 --- a/pkg/cloud/report/result.go +++ /dev/null @@ -1,35 +0,0 @@ -package report - -import ( - "fmt" - "io" - - "github.com/aquasecurity/tml" - dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - renderer "github.com/aquasecurity/trivy/pkg/report/table" - "github.com/aquasecurity/trivy/pkg/types" -) - -func writeResultsForARN(report *Report, results types.Results, output io.Writer, service, arn string, severities []dbTypes.Severity) error { - - // render scan title - _ = tml.Fprintf(output, "\nResults for '%s' (%s Account %s)\n\n", arn, report.Provider, report.AccountID) - - for _, result := range results { - var filtered []types.DetectedMisconfiguration - for _, misconfiguration := range result.Misconfigurations { - if arn != "" && misconfiguration.CauseMetadata.Resource != arn { - continue - } - if service != "" && misconfiguration.CauseMetadata.Service != service { - continue - } - filtered = append(filtered, misconfiguration) - } - if len(filtered) > 0 { - _, _ = fmt.Fprint(output, renderer.NewMisconfigRenderer(result, severities, false, false, true).Render()) - } - } - - return nil -} diff --git a/pkg/cloud/report/result_test.go b/pkg/cloud/report/result_test.go deleted file mode 100644 index 6afc67305c4f..000000000000 --- a/pkg/cloud/report/result_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package report - -import ( - "bytes" - "context" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy/pkg/flag" -) - -func Test_ARNReport(t *testing.T) { - tests := []struct { - name string - options flag.Options - fromCache bool - expected string - }{ - { - name: "simple output", - options: flag.Options{ - ReportOptions: flag.ReportOptions{ - Format: tableFormat, - Severities: []types.Severity{ - types.SeverityLow, - types.SeverityMedium, - types.SeverityHigh, - types.SeverityCritical, - }, - }, - AWSOptions: flag.AWSOptions{ - Services: []string{"s3"}, - ARN: "arn:aws:s3:us-east-1:1234567890:bucket1", - Account: "1234567890", - }, - }, - fromCache: false, - expected: ` -Results for 'arn:aws:s3:us-east-1:1234567890:bucket1' (AWS Account 1234567890) - - -arn:aws:s3:us-east-1:1234567890:bucket1 (cloud) - -Tests: 1 (SUCCESSES: 0, FAILURES: 1, EXCEPTIONS: 0) -Failures: 1 (LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0) - -HIGH: something failed -════════════════════════════════════════ -Bad stuff is... bad - -See https://avd.aquasec.com/misconfig/avd-aws-9999 -──────────────────────────────────────── - - -`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - report := New( - "AWS", - tt.options.AWSOptions.Account, - tt.options.AWSOptions.Region, - createTestResults(), - tt.options.AWSOptions.Services, - ) - - output := bytes.NewBuffer(nil) - tt.options.SetOutputWriter(output) - require.NoError(t, Write(context.Background(), report, tt.options, tt.fromCache)) - - assert.Equal(t, "AWS", report.Provider) - assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID) - assert.Equal(t, tt.options.AWSOptions.Region, report.Region) - assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope) - assert.Equal(t, tt.expected, strings.ReplaceAll(output.String(), "\r\n", "\n")) - }) - } -} diff --git a/pkg/cloud/report/service.go b/pkg/cloud/report/service.go deleted file mode 100644 index e25d8ea393f9..000000000000 --- a/pkg/cloud/report/service.go +++ /dev/null @@ -1,85 +0,0 @@ -package report - -import ( - "fmt" - "io" - "sort" - "strconv" - "time" - - "github.com/aquasecurity/table" - "github.com/aquasecurity/tml" - pkgReport "github.com/aquasecurity/trivy/pkg/report/table" - "github.com/aquasecurity/trivy/pkg/types" -) - -func writeServiceTable(report *Report, results types.Results, output io.Writer) error { - - t := table.New(output) - - t.SetHeaders("Service", "Misconfigurations", "Last Scanned") - t.AddHeaders("Service", "Critical", "High", "Medium", "Low", "Unknown", "Last Scanned") - t.SetRowLines(false) - t.SetHeaderVerticalAlignment(table.AlignBottom) - t.SetHeaderAlignment(table.AlignLeft, table.AlignCenter, table.AlignCenter, table.AlignCenter, table.AlignCenter, table.AlignCenter, table.AlignLeft) - t.SetAlignment(table.AlignLeft, table.AlignRight, table.AlignRight, table.AlignRight, table.AlignRight, table.AlignRight, table.AlignLeft) - t.SetAutoMergeHeaders(true) - t.SetHeaderColSpans(0, 1, 5, 1) - - // map service -> severity -> count - grouped := make(map[string]map[string]int) - // set zero counts for all services - for _, service := range report.ServicesInScope { - grouped[service] = make(map[string]int) - } - for _, result := range results { - for _, misconfiguration := range result.Misconfigurations { - service := misconfiguration.CauseMetadata.Service - if _, ok := grouped[service]; !ok { - grouped[service] = make(map[string]int) - } - grouped[service][misconfiguration.Severity]++ - } - } - - var sortable []sortableRow - for service, severityCounts := range grouped { - sortable = append(sortable, sortableRow{ - name: service, - counts: severityCounts, - }) - } - sort.Slice(sortable, func(i, j int) bool { return sortable[i].name < sortable[j].name }) - for _, row := range sortable { - var lastScanned string - scanAgo := time.Since(report.Results[row.name].CreationTime).Truncate(time.Minute) - switch { - case scanAgo.Hours() >= 48: - lastScanned = fmt.Sprintf("%d days ago", int(scanAgo.Hours()/24)) - case scanAgo.Hours() > 1: - lastScanned = fmt.Sprintf("%d hours ago", int(scanAgo.Hours())) - case scanAgo.Minutes() > 1: - lastScanned = fmt.Sprintf("%d minutes ago", int(scanAgo.Minutes())) - default: - lastScanned = "just now" - } - - t.AddRow( - row.name, - pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["CRITICAL"]), "CRITICAL"), - pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["HIGH"]), "HIGH"), - pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["MEDIUM"]), "MEDIUM"), - pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["LOW"]), "LOW"), - pkgReport.ColorizeSeverity(strconv.Itoa(row.counts["UNKNOWN"]), "UNKNOWN"), - lastScanned, - ) - } - - // render scan title - _ = tml.Fprintf(output, "\nScan Overview for %s Account %s\n", report.Provider, report.AccountID) - - // render table - t.Render() - - return nil -} diff --git a/pkg/cloud/report/service_test.go b/pkg/cloud/report/service_test.go deleted file mode 100644 index 8e35bb0194e2..000000000000 --- a/pkg/cloud/report/service_test.go +++ /dev/null @@ -1,420 +0,0 @@ -package report - -import ( - "bytes" - "context" - "testing" - "time" - - "github.com/aws/aws-sdk-go-v2/aws/arn" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/flag" - "github.com/aquasecurity/trivy/pkg/iac/scan" - iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" -) - -func Test_ServiceReport(t *testing.T) { - tests := []struct { - name string - options flag.Options - fromCache bool - expected string - }{ - { - name: "simple table output", - options: flag.Options{ - ReportOptions: flag.ReportOptions{ - Format: tableFormat, - Severities: []types.Severity{ - types.SeverityLow, - types.SeverityMedium, - types.SeverityHigh, - types.SeverityCritical, - }, - }, - }, - fromCache: false, - expected: ` -Scan Overview for AWS Account -┌─────────┬──────────────────────────────────────────────────┬──────────────┐ -│ │ Misconfigurations │ │ -│ ├──────────┬──────────────┬────────┬─────┬─────────┤ │ -│ Service │ Critical │ High │ Medium │ Low │ Unknown │ Last Scanned │ -├─────────┼──────────┼──────────────┼────────┼─────┼─────────┼──────────────┤ -│ ec2 │ 0 │ 1 │ 0 │ 0 │ 0 │ just now │ -│ s3 │ 0 │ 3 │ 0 │ 0 │ 0 │ just now │ -└─────────┴──────────┴──────────────┴────────┴─────┴─────────┴──────────────┘ -`, - }, - { - name: "results from cache", - options: flag.Options{ - ReportOptions: flag.ReportOptions{ - Format: tableFormat, - Severities: []types.Severity{ - types.SeverityLow, - types.SeverityMedium, - types.SeverityHigh, - types.SeverityCritical, - }, - }, - }, - fromCache: true, - expected: ` -Scan Overview for AWS Account -┌─────────┬──────────────────────────────────────────────────┬──────────────┐ -│ │ Misconfigurations │ │ -│ ├──────────┬──────────────┬────────┬─────┬─────────┤ │ -│ Service │ Critical │ High │ Medium │ Low │ Unknown │ Last Scanned │ -├─────────┼──────────┼──────────────┼────────┼─────┼─────────┼──────────────┤ -│ ec2 │ 0 │ 1 │ 0 │ 0 │ 0 │ just now │ -│ s3 │ 0 │ 3 │ 0 │ 0 │ 0 │ just now │ -└─────────┴──────────┴──────────────┴────────┴─────┴─────────┴──────────────┘ - -This scan report was loaded from cached results. If you'd like to run a fresh scan, use --update-cache. -`, - }, - { - name: "filter severities", - options: flag.Options{ - ReportOptions: flag.ReportOptions{ - Format: tableFormat, - Severities: []types.Severity{ - types.SeverityMedium, - }, - }, - AWSOptions: flag.AWSOptions{ - Services: []string{ - "s3", - "ec2", - }, - }, - }, - fromCache: false, - expected: ` -Scan Overview for AWS Account -┌─────────┬──────────────────────────────────────────────────┬──────────────┐ -│ │ Misconfigurations │ │ -│ ├──────────┬──────────────┬────────┬─────┬─────────┤ │ -│ Service │ Critical │ High │ Medium │ Low │ Unknown │ Last Scanned │ -├─────────┼──────────┼──────────────┼────────┼─────┼─────────┼──────────────┤ -│ ec2 │ 0 │ 0 │ 0 │ 0 │ 0 │ just now │ -│ s3 │ 0 │ 0 │ 0 │ 0 │ 0 │ just now │ -└─────────┴──────────┴──────────────┴────────┴─────┴─────────┴──────────────┘ -`, - }, - { - name: "scoped services without results", - options: flag.Options{ - ReportOptions: flag.ReportOptions{ - Format: tableFormat, - Severities: []types.Severity{ - types.SeverityLow, - types.SeverityMedium, - types.SeverityHigh, - types.SeverityCritical, - }, - }, - AWSOptions: flag.AWSOptions{ - Services: []string{ - "ec2", - "s3", - "iam", - }, - }, - }, - fromCache: false, - expected: ` -Scan Overview for AWS Account -┌─────────┬──────────────────────────────────────────────────┬──────────────┐ -│ │ Misconfigurations │ │ -│ ├──────────┬──────────────┬────────┬─────┬─────────┤ │ -│ Service │ Critical │ High │ Medium │ Low │ Unknown │ Last Scanned │ -├─────────┼──────────┼──────────────┼────────┼─────┼─────────┼──────────────┤ -│ ec2 │ 0 │ 1 │ 0 │ 0 │ 0 │ just now │ -│ iam │ 0 │ 0 │ 0 │ 0 │ 0 │ just now │ -│ s3 │ 0 │ 3 │ 0 │ 0 │ 0 │ just now │ -└─────────┴──────────┴──────────────┴────────┴─────┴─────────┴──────────────┘ -`, - }, - { - name: "json output", - options: flag.Options{ - ReportOptions: flag.ReportOptions{ - Format: "json", - Severities: []types.Severity{ - types.SeverityLow, - types.SeverityMedium, - types.SeverityHigh, - types.SeverityCritical, - }, - }, - }, - fromCache: false, - expected: `{ - "CreatedAt": "2021-08-25T12:20:30.000000005Z", - "ArtifactType": "aws_account", - "Metadata": { - "ImageConfig": { - "architecture": "", - "created": "0001-01-01T00:00:00Z", - "os": "", - "rootfs": { - "type": "", - "diff_ids": null - }, - "config": {} - } - }, - "Results": [ - { - "Target": "arn:aws:ec2:us-east-1:1234567890:instance1", - "Class": "config", - "Type": "cloud", - "MisconfSummary": { - "Successes": 0, - "Failures": 1, - "Exceptions": 0 - }, - "Misconfigurations": [ - { - "Type": "AWS", - "ID": "AVD-AWS-9999", - "AVDID": "AVD-AWS-9999", - "Title": "Do not use bad stuff", - "Description": "Bad stuff is... bad", - "Message": "instance is bad", - "Resolution": "Remove bad stuff", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-9999", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-9999" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:ec2:us-east-1:1234567890:instance1", - "Provider": "AWS", - "Service": "ec2", - "Code": { - "Lines": null - } - } - } - ] - }, - { - "Target": "arn:aws:s3:us-east-1:1234567890:bucket1", - "Class": "config", - "Type": "cloud", - "MisconfSummary": { - "Successes": 0, - "Failures": 1, - "Exceptions": 0 - }, - "Misconfigurations": [ - { - "Type": "AWS", - "ID": "AVD-AWS-9999", - "AVDID": "AVD-AWS-9999", - "Title": "Do not use bad stuff", - "Description": "Bad stuff is... bad", - "Message": "something failed", - "Resolution": "Remove bad stuff", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-9999", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-9999" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:us-east-1:1234567890:bucket1", - "Provider": "AWS", - "Service": "s3", - "Code": { - "Lines": null - } - } - } - ] - }, - { - "Target": "arn:aws:s3:us-east-1:1234567890:bucket2", - "Class": "config", - "Type": "cloud", - "MisconfSummary": { - "Successes": 0, - "Failures": 2, - "Exceptions": 0 - }, - "Misconfigurations": [ - { - "Type": "AWS", - "ID": "AVD-AWS-9999", - "AVDID": "AVD-AWS-9999", - "Title": "Do not use bad stuff", - "Description": "Bad stuff is... bad", - "Message": "something else failed", - "Resolution": "Remove bad stuff", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-9999", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-9999" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:us-east-1:1234567890:bucket2", - "Provider": "AWS", - "Service": "s3", - "Code": { - "Lines": null - } - } - }, - { - "Type": "AWS", - "ID": "AVD-AWS-9999", - "AVDID": "AVD-AWS-9999", - "Title": "Do not use bad stuff", - "Description": "Bad stuff is... bad", - "Message": "something else failed again", - "Resolution": "Remove bad stuff", - "Severity": "HIGH", - "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-9999", - "References": [ - "https://avd.aquasec.com/misconfig/avd-aws-9999" - ], - "Status": "FAIL", - "Layer": {}, - "CauseMetadata": { - "Resource": "arn:aws:s3:us-east-1:1234567890:bucket2", - "Provider": "AWS", - "Service": "s3", - "Code": { - "Lines": null - } - } - } - ] - }, - { - "Target": "arn:aws:s3:us-east-1:1234567890:bucket3", - "Class": "config", - "Type": "cloud", - "MisconfSummary": { - "Successes": 1, - "Failures": 0, - "Exceptions": 0 - } - } - ] -}`, - }, - } - ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - report := New( - "AWS", - tt.options.AWSOptions.Account, - tt.options.AWSOptions.Region, - createTestResults(), - tt.options.AWSOptions.Services, - ) - - output := bytes.NewBuffer(nil) - tt.options.SetOutputWriter(output) - require.NoError(t, Write(ctx, report, tt.options, tt.fromCache)) - - assert.Equal(t, "AWS", report.Provider) - assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID) - assert.Equal(t, tt.options.AWSOptions.Region, report.Region) - assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope) - - if tt.options.Format == "json" { - // json output can be formatted/ordered differently - we just care that the data matches - assert.JSONEq(t, tt.expected, output.String()) - } else { - assert.Equal(t, tt.expected, output.String()) - } - }) - } -} - -func createTestResults() scan.Results { - - baseRule := scan.Rule{ - AVDID: "AVD-AWS-9999", - Aliases: []string{"AWS999"}, - ShortCode: "no-bad-stuff", - Summary: "Do not use bad stuff", - Explanation: "Bad stuff is... bad", - Impact: "Bad things", - Resolution: "Remove bad stuff", - Provider: "AWS", - Severity: "HIGH", - } - - var s3Results scan.Results - s3Results.Add( - "something failed", - iacTypes.NewRemoteMetadata((arn.ARN{ - Partition: "aws", - Service: "s3", - Region: "us-east-1", - AccountID: "1234567890", - Resource: "bucket1", - }).String()), - ) - s3Results.Add( - "something else failed", - iacTypes.NewRemoteMetadata((arn.ARN{ - Partition: "aws", - Service: "s3", - Region: "us-east-1", - AccountID: "1234567890", - Resource: "bucket2", - }).String()), - ) - s3Results.Add( - "something else failed again", - iacTypes.NewRemoteMetadata((arn.ARN{ - Partition: "aws", - Service: "s3", - Region: "us-east-1", - AccountID: "1234567890", - Resource: "bucket2", - }).String()), - ) - s3Results.AddPassed( - iacTypes.NewRemoteMetadata((arn.ARN{ - Partition: "aws", - Service: "s3", - Region: "us-east-1", - AccountID: "1234567890", - Resource: "bucket3", - }).String()), - ) - baseRule.Service = "s3" - s3Results.SetRule(baseRule) - var ec2Results scan.Results - ec2Results.Add( - "instance is bad", - iacTypes.NewRemoteMetadata((arn.ARN{ - Partition: "aws", - Service: "ec2", - Region: "us-east-1", - AccountID: "1234567890", - Resource: "instance1", - }).String()), - ) - baseRule.Service = "ec2" - ec2Results.SetRule(baseRule) - return append(s3Results, ec2Results...) -} diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 7746e1b70784..e01bc53e1ea0 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -7,16 +7,12 @@ import ( "fmt" "io" "os" - "sort" - "strings" "time" "github.com/spf13/cobra" "github.com/spf13/viper" "golang.org/x/xerrors" - awsScanner "github.com/aquasecurity/trivy-aws/pkg/scanner" - awscommands "github.com/aquasecurity/trivy/pkg/cloud/aws/commands" "github.com/aquasecurity/trivy/pkg/commands/artifact" "github.com/aquasecurity/trivy/pkg/commands/convert" "github.com/aquasecurity/trivy/pkg/commands/server" @@ -97,7 +93,7 @@ func NewApp() *cobra.Command { NewKubernetesCommand(globalFlags), NewSBOMCommand(globalFlags), NewVersionCommand(globalFlags), - NewAWSCommand(globalFlags), + NewAWSCommand(), NewVMCommand(globalFlags), ) @@ -1019,77 +1015,11 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return cmd } -func NewAWSCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { - reportFlagGroup := flag.NewReportFlagGroup() - compliance := flag.ComplianceFlag - compliance.Values = []string{ - types.ComplianceAWSCIS12, - types.ComplianceAWSCIS14, - } - reportFlagGroup.Compliance = &compliance // override usage as the accepted values differ for each subcommand. - reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' - reportFlagGroup.ShowSuppressed = nil // disable '--show-suppressed' - - awsFlags := &flag.Flags{ - GlobalFlagGroup: globalFlags, - AWSFlagGroup: flag.NewAWSFlagGroup(), - CloudFlagGroup: flag.NewCloudFlagGroup(), - MisconfFlagGroup: flag.NewMisconfFlagGroup(), - RegoFlagGroup: flag.NewRegoFlagGroup(), - ReportFlagGroup: reportFlagGroup, - } - - services := awsScanner.AllSupportedServices() - sort.Strings(services) - +func NewAWSCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "aws [flags]", - Aliases: []string{}, - GroupID: groupScanning, - Args: cobra.ExactArgs(0), - Short: "[EXPERIMENTAL] Scan AWS account", - Long: fmt.Sprintf(`Scan an AWS account for misconfigurations. Trivy uses the same authentication methods as the AWS CLI. See https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html - -The following services are supported: - -- %s -`, strings.Join(services, "\n- ")), - Example: ` # basic scanning - $ trivy aws --region us-east-1 - - # limit scan to a single service: - $ trivy aws --region us-east-1 --service s3 - - # limit scan to multiple services: - $ trivy aws --region us-east-1 --service s3 --service ec2 - - # force refresh of cache for fresh results - $ trivy aws --region us-east-1 --update-cache -`, - PreRunE: func(cmd *cobra.Command, args []string) error { - if err := awsFlags.Bind(cmd); err != nil { - return xerrors.Errorf("flag bind error: %w", err) - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - opts, err := awsFlags.ToOptions(args) - if err != nil { - return xerrors.Errorf("flag error: %w", err) - } - if opts.Timeout < time.Hour { - opts.Timeout = time.Hour - log.Info("Timeout is set to less than 1 hour - upgrading to 1 hour for this command.") - } - return awscommands.Run(cmd.Context(), opts) - }, - SilenceErrors: true, - SilenceUsage: true, + Deprecated: "Trivy AWS is now available as an optional plugin. See github.com/aquasecurity/trivy-aws for details", + Use: "aws [flags]", } - cmd.SetFlagErrorFunc(flagErrorFunc) - awsFlags.AddFlags(cmd) - cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, awsFlags.Usages(cmd))) - return cmd } From 9dc8a2ba6bdc8554ca068285f8488286132697d2 Mon Sep 17 00:00:00 2001 From: Itay Shakury Date: Fri, 21 Jun 2024 09:32:32 +0300 Subject: [PATCH 181/352] docs: non-packaged and sbom clarifications (#6975) Co-authored-by: Teppei Fukuda --- docs/docs/coverage/language/golang.md | 2 +- docs/docs/scanner/vulnerability.md | 20 ++++++++++++++------ docs/docs/supply-chain/sbom.md | 20 +++++++++++++------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/docs/docs/coverage/language/golang.md b/docs/docs/coverage/language/golang.md index 3d57edade7ec..6b3646329318 100644 --- a/docs/docs/coverage/language/golang.md +++ b/docs/docs/coverage/language/golang.md @@ -66,7 +66,7 @@ such as `go mod download`, `go mod tidy`, etc. Trivy traverses `$GOPATH/pkg/mod` and collects those extra information. ### Go binaries -Trivy scans binaries built by Go. +Trivy scans binaries built by Go, which include [module information](https://tip.golang.org/doc/go1.18#go-version). If there is a Go binary in your container image, Trivy automatically finds and scans it. Also, you can scan your local binaries. diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md index 57cb6d79c1c3..55403dda2207 100644 --- a/docs/docs/scanner/vulnerability.md +++ b/docs/docs/scanner/vulnerability.md @@ -1,13 +1,12 @@ # Vulnerability Scanning -Trivy detects known vulnerabilities according to the versions of installed packages. +Trivy detects known vulnerabilities in software components that it finds in the scan target. -The following packages are supported. +The following are supported: - [OS packages](#os-packages) - [Language-specific packages](#language-specific-packages) -- [Kubernetes components (control plane, node and addons)](#kubernetes) - -Trivy also detects known vulnerabilities in Kubernetes components using KBOM (Kubernetes bill of Material) scanning. To learn more, see the [documentation for Kubernetes scanning](../target/kubernetes.md#KBOM). +- [Non-packaged software](#non-packaged-software) +- [Kubernetes components](#kubernetes) ## OS Packages Trivy is capable of automatically detecting installed OS packages when scanning container images, VM images and running hosts. @@ -138,9 +137,18 @@ See [here](../coverage/language/index.md#supported-languages) for the supported [^1]: Intentional delay between vulnerability disclosure and registration in the DB +## Non-packaged software + +If you have software that is not managed by a package manager, Trivy can still detect vulnerabilities in it in some cases: + +- [Using SBOM from Sigstore Rekor](../supply-chain/attestation/rekor/#non-packaged-binaries) +- [Go Binaries with embedded module information](../coverage/language/golang/#go-binaries) +- [Rust Binaries with embedded information](../coverage/language/rust/#binaries) +- [SBOM embedded in container images](../supply-chain/container-image/#sbom-embedded-in-container-images) + ## Kubernetes -Trivy can detect vulnerabilities in Kubernetes clusters and components. +Trivy can detect vulnerabilities in Kubernetes clusters and components by scanning a Kubernetes Cluster, or a KBOM (Kubernetes bill of Material). To learn more, see the [documentation for Kubernetes scanning](../target/kubernetes.md). ### Data Sources diff --git a/docs/docs/supply-chain/sbom.md b/docs/docs/supply-chain/sbom.md index cb3a68c9d8f3..ed57195b3550 100644 --- a/docs/docs/supply-chain/sbom.md +++ b/docs/docs/supply-chain/sbom.md @@ -731,17 +731,20 @@ $ cat result.spdx.json | jq . ## Scanning -Trivy can take SBOM documents as input for scanning. + +### SBOM as Target +Trivy can take SBOM documents as input for scanning, e.g `trivy sbom ./sbom.spdx`. See [here](../target/sbom.md) for more details. -Also, Trivy searches for SBOM files in container images. +### SBOM Detection inside Targets +Trivy searches for SBOM files in container images with the following extensions: +- `.spdx` +- `.spdx.json` +- `.cdx` +- `.cdx.json` -```bash -$ trivy image bitnami/elasticsearch:8.7.1 -``` +In addition, Trivy automatically detects SBOM files in [Bitnami images](https://github.com/bitnami/containers), [see here](../coverage/os/bitnami.md) for more details. -For example, [Bitnami images](https://github.com/bitnami/containers) contain SBOM files in `/opt/bitnami` directory. -Trivy automatically detects the SBOM files and uses them for scanning. It is enabled in the following targets. | Target | Enabled | @@ -755,6 +758,9 @@ It is enabled in the following targets. | AWS | | | SBOM | | +### SBOM Discovery for Container Images + +When scanning container images, Trivy can discover SBOM for those images. [See here](../target/container_image.md) for more details. [spdx]: https://spdx.dev/wp-content/uploads/sites/41/2020/08/SPDX-specification-2-2.pdf From 6dff4223ed47f7be2fa5166f1e2c15240109cd5f Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 21 Jun 2024 10:35:33 +0400 Subject: [PATCH 182/352] refactor: unify cache implementations (#6977) Signed-off-by: knqyf263 --- go.mod | 23 +- go.sum | 46 +-- pkg/{fanal => }/cache/cache.go | 0 pkg/cache/client.go | 200 +++++++++++ pkg/cache/client_test.go | 129 ++++++++ pkg/cache/dir.go | 30 ++ pkg/{fanal => }/cache/fs.go | 0 pkg/{fanal => }/cache/fs_test.go | 0 pkg/{fanal => }/cache/key.go | 0 pkg/{fanal => }/cache/key_test.go | 0 pkg/{fanal => }/cache/mock_artifact_cache.go | 0 pkg/{fanal => }/cache/mock_cache.go | 0 .../cache/mock_local_artifact_cache.go | 0 pkg/cache/nop.go | 19 +- pkg/{fanal => }/cache/redis.go | 0 pkg/{fanal => }/cache/redis_test.go | 2 +- pkg/cache/remote.go | 8 +- pkg/cache/remote_test.go | 8 +- .../cache/testdata/broken-image.db | Bin .../cache/testdata/broken-layer.db | Bin .../cache/testdata/different-image-schema.db | Bin pkg/{fanal => }/cache/testdata/fanal.db | Bin .../cache/testdata/policy/test.rego | 0 .../cache/testdata/trivy-secret.yaml | 0 pkg/commands/artifact/inject.go | 3 +- pkg/commands/artifact/run.go | 40 +-- pkg/commands/artifact/wire_gen.go | 2 +- pkg/commands/operation/operation.go | 110 ------ pkg/commands/server/run.go | 14 +- pkg/fanal/applier/applier.go | 2 +- pkg/fanal/applier/applier_test.go | 2 +- pkg/fanal/artifact/image/image.go | 2 +- pkg/fanal/artifact/image/image_test.go | 2 +- pkg/fanal/artifact/image/remote_sbom_test.go | 2 +- pkg/fanal/artifact/local/fs.go | 2 +- pkg/fanal/artifact/local/fs_test.go | 2 +- pkg/fanal/artifact/repo/git.go | 2 +- pkg/fanal/artifact/repo/git_test.go | 2 +- pkg/fanal/artifact/sbom/sbom.go | 2 +- pkg/fanal/artifact/sbom/sbom_test.go | 2 +- pkg/fanal/artifact/vm/ebs.go | 2 +- pkg/fanal/artifact/vm/file.go | 2 +- pkg/fanal/artifact/vm/vm.go | 2 +- pkg/fanal/artifact/vm/vm_test.go | 2 +- pkg/fanal/cache/s3.go | 180 ---------- pkg/fanal/cache/s3_test.go | 312 ------------------ pkg/fanal/test/integration/containerd_test.go | 2 +- pkg/fanal/test/integration/library_test.go | 2 +- pkg/fanal/test/integration/registry_test.go | 2 +- pkg/flag/cache_flags.go | 60 +--- pkg/flag/cache_flags_test.go | 160 --------- pkg/flag/global_flags.go | 4 +- pkg/k8s/inject.go | 2 +- pkg/k8s/wire_gen.go | 2 +- pkg/oci/artifact_test.go | 4 +- pkg/plugin/index_test.go | 6 +- pkg/plugin/manager.go | 3 +- pkg/plugin/manager_unix_test.go | 4 +- pkg/rpc/server/inject.go | 2 +- pkg/rpc/server/listen.go | 2 +- pkg/rpc/server/listen_test.go | 2 +- pkg/rpc/server/server.go | 2 +- pkg/rpc/server/server_test.go | 2 +- pkg/rpc/server/wire_gen.go | 2 +- pkg/utils/fsutils/fs.go | 24 -- 65 files changed, 481 insertions(+), 962 deletions(-) rename pkg/{fanal => }/cache/cache.go (100%) create mode 100644 pkg/cache/client.go create mode 100644 pkg/cache/client_test.go create mode 100644 pkg/cache/dir.go rename pkg/{fanal => }/cache/fs.go (100%) rename pkg/{fanal => }/cache/fs_test.go (100%) rename pkg/{fanal => }/cache/key.go (100%) rename pkg/{fanal => }/cache/key_test.go (100%) rename pkg/{fanal => }/cache/mock_artifact_cache.go (100%) rename pkg/{fanal => }/cache/mock_cache.go (100%) rename pkg/{fanal => }/cache/mock_local_artifact_cache.go (100%) rename pkg/{fanal => }/cache/redis.go (100%) rename pkg/{fanal => }/cache/redis_test.go (99%) rename pkg/{fanal => }/cache/testdata/broken-image.db (100%) rename pkg/{fanal => }/cache/testdata/broken-layer.db (100%) rename pkg/{fanal => }/cache/testdata/different-image-schema.db (100%) rename pkg/{fanal => }/cache/testdata/fanal.db (100%) rename pkg/{fanal => }/cache/testdata/policy/test.rego (100%) rename pkg/{fanal => }/cache/testdata/trivy-secret.yaml (100%) delete mode 100644 pkg/fanal/cache/s3.go delete mode 100644 pkg/fanal/cache/s3_test.go delete mode 100644 pkg/flag/cache_flags_test.go diff --git a/go.mod b/go.mod index b12cdf30855a..71f2d1628f7e 100644 --- a/go.mod +++ b/go.mod @@ -30,13 +30,12 @@ require ( github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7 github.com/aws/aws-sdk-go-v2 v1.27.2 - github.com/aws/aws-sdk-go-v2/config v1.27.15 - github.com/aws/aws-sdk-go-v2/credentials v1.17.15 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.20 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.161.3 - github.com/aws/aws-sdk-go-v2/service/ecr v1.28.2 - github.com/aws/aws-sdk-go-v2/service/s3 v1.54.2 - github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.18 + github.com/aws/aws-sdk-go-v2/credentials v1.17.18 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.1 + github.com/aws/aws-sdk-go-v2/service/ecr v1.28.5 + github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect github.com/aws/smithy-go v1.20.2 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 @@ -169,19 +168,15 @@ require ( github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.53.0 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 // indirect github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/briandowns/spinner v1.23.0 // indirect diff --git a/go.sum b/go.sum index 93c0f75b63ea..17d31b547023 100644 --- a/go.sum +++ b/go.sum @@ -791,46 +791,36 @@ github.com/aws/aws-sdk-go v1.53.0 h1:MMo1x1ggPPxDfHMXJnQudTbGXYlD4UigUAud1DJxPVo github.com/aws/aws-sdk-go v1.53.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8= github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= -github.com/aws/aws-sdk-go-v2/config v1.27.15 h1:uNnGLZ+DutuNEkuPh6fwqK7LpEiPmzb7MIMA1mNWEUc= -github.com/aws/aws-sdk-go-v2/config v1.27.15/go.mod h1:7j7Kxx9/7kTmL7z4LlhwQe63MYEE5vkVV6nWg4ZAI8M= -github.com/aws/aws-sdk-go-v2/credentials v1.17.15 h1:YDexlvDRCA8ems2T5IP1xkMtOZ1uLJOCJdTr0igs5zo= -github.com/aws/aws-sdk-go-v2/credentials v1.17.15/go.mod h1:vxHggqW6hFNaeNC0WyXS3VdyjcV0a4KMUY4dKJ96buU= +github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk= +github.com/aws/aws-sdk-go-v2/config v1.27.18/go.mod h1:0xz6cgdX55+kmppvPm2IaKzIXOheGJhAufacPJaXZ7c= +github.com/aws/aws-sdk-go-v2/credentials v1.17.18 h1:D/ALDWqK4JdY3OFgA2thcPO1c9aYTT5STS/CvnkqY1c= +github.com/aws/aws-sdk-go-v2/credentials v1.17.18/go.mod h1:JuitCWq+F5QGUrmMPsk945rop6bB57jdscu+Glozdnc= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 h1:dDgptDO9dxeFkXy+tEgVkzSClHZje/6JkPW5aZyEvrQ= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5/go.mod h1:gjvE2KBUgUQhcv89jqxrIxH9GaKs1JbZzWejj/DaHGA= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.20 h1:NCM9wYaJCmlIWZSO/JwUEveKf0NCvsSgo9V9BwOAolo= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.20/go.mod h1:dmxIx3qriuepxqZgFeFMitFuftWPB94+MZv/6Btpth4= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 h1:cy8ahBJuhtM8GTTSyOkfy6WVPV1IE+SS5/wfXUYuulw= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9/go.mod h1:CZBXGLaJnEZI6EVNcPd7a6B5IC5cA/GkRWtu9fp3S6Y= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 h1:A4SYk07ef04+vxZToz9LWvAXl9LW0NClpPpMsi31cz0= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9/go.mod h1:5jJcHuwDagxN+ErjQ3PU3ocf6Ylc/p9x+BLO/+X4iXw= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 h1:/FUtT3xsoHO3cfh+I/kCbcMCN98QZRsiFet/V8QkWSs= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7/go.mod h1:MaCAgWpGooQoCWZnMur97rGn5dp350w2+CeiV5406wE= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.161.3 h1:l0mvKOGm25yo/Fy+Y/08Cm4aTA4XmnIuq4ppy+shfMI= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.161.3/go.mod h1:iJ2sQeUTkjNp3nL7kE/Bav0xXYhtiRCRP5ZXk4jFhCQ= -github.com/aws/aws-sdk-go-v2/service/ecr v1.28.2 h1:xUpMnRZonKfrHaNLC77IMpWZSUMRRXIi6IU5EhAPsrM= -github.com/aws/aws-sdk-go-v2/service/ecr v1.28.2/go.mod h1:X52zjAVRaXklEU1TE/wO8kyyJSr9cJx9ZsqliWbyRys= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.1 h1:0RiDkJO1veM6/FQ+GJcGiIhZgPwXlscX29B0zFE4Ulo= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.1/go.mod h1:gYk1NtyvkH1SxPcndDtfro3lwbiE5t0tW4eRki5YnOQ= +github.com/aws/aws-sdk-go-v2/service/ecr v1.28.5 h1:dvvTFXpWSv9+8lTNPl1EPNZL6BCUV6MgVckEMvXaOgk= +github.com/aws/aws-sdk-go-v2/service/ecr v1.28.5/go.mod h1:Ogt6AOZ/sPBlJZpVFJgOK+jGGREuo8DMjNg+O/7gpjI= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 h1:UXqEWQI0n+q0QixzU0yUUQBZXRd5037qdInTIHFTl98= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9/go.mod h1:xP6Gq6fzGZT8w/ZN+XvGMZ2RU1LeEs7b2yUP5DN8NY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 h1:Wx0rlZoEJR7JwlSZcHnEa7CNjrSIyVxMFWGAaXy4fJY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9/go.mod h1:aVMHdE0aHO3v+f/iw01fmXV/5DbfQ3Bi9nN7nd9bE9Y= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 h1:uO5XR6QGBcmPyo2gxofYJLFkcVQ4izOoGDNenlZhTEk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7/go.mod h1:feeeAYfAcwTReM6vbwjEyDmiGho+YgBhaFULuXDW8kc= -github.com/aws/aws-sdk-go-v2/service/s3 v1.54.2 h1:gYSJhNiOF6J9xaYxu2NFNstoiNELwt0T9w29FxSfN+Y= -github.com/aws/aws-sdk-go-v2/service/s3 v1.54.2/go.mod h1:739CllldowZiPPsDFcJHNF4FXrVxaSGVnZ9Ez9Iz9hc= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.8 h1:Kv1hwNG6jHC/sxMTe5saMjH6t6ZLkgfvVxyEjfWL1ks= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.8/go.mod h1:c1qtZUWtygI6ZdvKppzCSXsDOq5I4luJPZ0Ud3juFCA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2 h1:nWBZ1xHCF+A7vv9sDzJOq4NWIdzFYm0kH7Pr4OjHYsQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.2/go.mod h1:9lmoVDVLz/yUZwLaQ676TK02fhCu4+PgRSmMaKR1ozk= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.9 h1:Qp6Boy0cGDloOE3zI6XhNLNZgjNS8YmiFQFHe71SaW0= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.9/go.mod h1:0Aqn1MnEuitqfsCNyKsdKLhDUOr4txD/g19EfiUqgws= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 h1:o4T+fKxA3gTMcluBNZZXE9DNaMkJuUL1O3mffCUjoJo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11/go.mod h1:84oZdJ+VjuJKs9v1UTC9NaodRZRseOXCTgku+vQJWR8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1 h1:UAxBuh0/8sFJk1qOkvOKewP5sWeWaTPDknbQz0ZkDm0= +github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1/go.mod h1:hWjsYGjVuqCgfoveVcVFPXIWgz0aByzwaxKlN1StKcM= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 h1:gEYM2GSpr4YNWc6hCd5nod4+d4kd9vWIAWrmGuLdlMw= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.11/go.mod h1:gVvwPdPNYehHSP9Rs7q27U1EU+3Or2ZpXvzAYJNh63w= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 h1:iXjh3uaH3vsVcnyZX7MqCoCfcyxIrVE9iOQruRaWPrQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5/go.mod h1:5ZXesEuy/QcO0WUnt+4sDkxhdXRHTu2yG0uCSH8B6os= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 h1:M/1u4HBpwLuMtjlxuI2y6HoVLzF5e2mfxHCg7ZVMYmk= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.12/go.mod h1:kcfd+eTdEi/40FIbLq4Hif3XMXnl5b/+t/KTfLt9xIk= github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= diff --git a/pkg/fanal/cache/cache.go b/pkg/cache/cache.go similarity index 100% rename from pkg/fanal/cache/cache.go rename to pkg/cache/cache.go diff --git a/pkg/cache/client.go b/pkg/cache/client.go new file mode 100644 index 000000000000..fea9395770c7 --- /dev/null +++ b/pkg/cache/client.go @@ -0,0 +1,200 @@ +package cache + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "os" + "strings" + "time" + + "github.com/go-redis/redis/v8" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" +) + +const ( + TypeFS Type = "fs" + TypeRedis Type = "redis" +) + +type Client struct { + Cache +} + +type Type string + +type Options struct { + Type Type + TTL time.Duration + Redis RedisOptions +} + +func NewOptions(backend, redisCACert, redisCert, redisKey string, redisTLS bool, ttl time.Duration) (Options, error) { + t, err := NewType(backend) + if err != nil { + return Options{}, xerrors.Errorf("cache type error: %w", err) + } + + var redisOpts RedisOptions + if t == TypeRedis { + redisTLSOpts, err := NewRedisTLSOptions(redisCACert, redisCert, redisKey) + if err != nil { + return Options{}, xerrors.Errorf("redis TLS option error: %w", err) + } + redisOpts = RedisOptions{ + Backend: backend, + TLS: redisTLS, + TLSOptions: redisTLSOpts, + } + } else if ttl != 0 { + log.Warn("'--cache-ttl' is only available with Redis cache backend") + } + + return Options{ + Type: t, + TTL: ttl, + Redis: redisOpts, + }, nil +} + +type RedisOptions struct { + Backend string + TLS bool + TLSOptions RedisTLSOptions +} + +// BackendMasked returns the redis connection string masking credentials +func (o *RedisOptions) BackendMasked() string { + endIndex := strings.Index(o.Backend, "@") + if endIndex == -1 { + return o.Backend + } + + startIndex := strings.Index(o.Backend, "//") + + return fmt.Sprintf("%s****%s", o.Backend[:startIndex+2], o.Backend[endIndex:]) +} + +// RedisTLSOptions holds the options for redis cache +type RedisTLSOptions struct { + CACert string + Cert string + Key string +} + +func NewRedisTLSOptions(caCert, cert, key string) (RedisTLSOptions, error) { + opts := RedisTLSOptions{ + CACert: caCert, + Cert: cert, + Key: key, + } + + // If one of redis option not nil, make sure CA, cert, and key provided + if !lo.IsEmpty(opts) { + if opts.CACert == "" || opts.Cert == "" || opts.Key == "" { + return RedisTLSOptions{}, xerrors.Errorf("you must provide Redis CA, cert and key file path when using TLS") + } + } + return opts, nil +} + +func NewType(backend string) (Type, error) { + // "redis://" or "fs" are allowed for now + // An empty value is also allowed for testability + switch { + case strings.HasPrefix(backend, "redis://"): + return TypeRedis, nil + case backend == "fs", backend == "": + return TypeFS, nil + default: + return "", xerrors.Errorf("unknown cache backend: %s", backend) + } +} + +// NewClient returns a new cache client +func NewClient(opts Options) (*Client, error) { + if opts.Type == TypeRedis { + log.Info("Redis cache", log.String("url", opts.Redis.BackendMasked())) + options, err := redis.ParseURL(opts.Redis.Backend) + if err != nil { + return nil, err + } + + if tlsOpts := opts.Redis.TLSOptions; !lo.IsEmpty(tlsOpts) { + caCert, cert, err := GetTLSConfig(tlsOpts.CACert, tlsOpts.Cert, tlsOpts.Key) + if err != nil { + return nil, err + } + + options.TLSConfig = &tls.Config{ + RootCAs: caCert, + Certificates: []tls.Certificate{cert}, + MinVersion: tls.VersionTLS12, + } + } else if opts.Redis.TLS { + options.TLSConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + } + } + + redisCache := NewRedisCache(options, opts.TTL) + return &Client{Cache: redisCache}, nil + } + + // standalone mode + fsCache, err := NewFSCache(Dir()) + if err != nil { + return nil, xerrors.Errorf("unable to initialize fs cache: %w", err) + } + return &Client{Cache: fsCache}, nil +} + +// Reset resets the cache +func (c *Client) Reset() (err error) { + if err := c.ClearDB(); err != nil { + return xerrors.Errorf("failed to clear the database: %w", err) + } + if err := c.ClearArtifacts(); err != nil { + return xerrors.Errorf("failed to clear the artifact cache: %w", err) + } + return nil +} + +// ClearDB clears the DB cache +func (c *Client) ClearDB() (err error) { + log.Info("Removing DB file...") + if err = os.RemoveAll(Dir()); err != nil { + return xerrors.Errorf("failed to remove the directory (%s) : %w", Dir(), err) + } + return nil +} + +// ClearArtifacts clears the artifact cache +func (c *Client) ClearArtifacts() error { + log.Info("Removing artifact caches...") + if err := c.Clear(); err != nil { + return xerrors.Errorf("failed to remove the cache: %w", err) + } + return nil +} + +// GetTLSConfig gets tls config from CA, Cert and Key file +func GetTLSConfig(caCertPath, certPath, keyPath string) (*x509.CertPool, tls.Certificate, error) { + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, tls.Certificate{}, err + } + + caCert, err := os.ReadFile(caCertPath) + if err != nil { + return nil, tls.Certificate{}, err + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + return caCertPool, cert, nil +} diff --git a/pkg/cache/client_test.go b/pkg/cache/client_test.go new file mode 100644 index 000000000000..f22ce4f93e2f --- /dev/null +++ b/pkg/cache/client_test.go @@ -0,0 +1,129 @@ +package cache_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/cache" +) + +func TestNewOptions(t *testing.T) { + type args struct { + backend string + redisCACert string + redisCert string + redisKey string + redisTLS bool + ttl time.Duration + } + tests := []struct { + name string + args args + want cache.Options + assertion require.ErrorAssertionFunc + }{ + { + name: "fs", + args: args{backend: "fs"}, + want: cache.Options{Type: cache.TypeFS}, + assertion: require.NoError, + }, + { + name: "redis", + args: args{backend: "redis://localhost:6379"}, + want: cache.Options{ + Type: cache.TypeRedis, + Redis: cache.RedisOptions{Backend: "redis://localhost:6379"}, + }, + assertion: require.NoError, + }, + { + name: "redis tls", + args: args{ + backend: "redis://localhost:6379", + redisCACert: "ca-cert.pem", + redisCert: "cert.pem", + redisKey: "key.pem", + }, + want: cache.Options{ + Type: cache.TypeRedis, + Redis: cache.RedisOptions{ + Backend: "redis://localhost:6379", + TLSOptions: cache.RedisTLSOptions{ + CACert: "ca-cert.pem", + Cert: "cert.pem", + Key: "key.pem", + }, + }, + }, + assertion: require.NoError, + }, + { + name: "redis tls with public certificates", + args: args{ + backend: "redis://localhost:6379", + redisTLS: true, + }, + want: cache.Options{ + Type: cache.TypeRedis, + Redis: cache.RedisOptions{ + Backend: "redis://localhost:6379", + TLS: true, + }, + }, + assertion: require.NoError, + }, + { + name: "unknown backend", + args: args{backend: "unknown"}, + assertion: func(t require.TestingT, err error, msgs ...any) { + require.ErrorContains(t, err, "unknown cache backend") + }, + }, + { + name: "sad redis tls", + args: args{ + backend: "redis://localhost:6379", + redisCACert: "ca-cert.pem", + }, + assertion: func(t require.TestingT, err error, msgs ...any) { + require.ErrorContains(t, err, "you must provide Redis CA, cert and key file path when using TLS") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := cache.NewOptions(tt.args.backend, tt.args.redisCACert, tt.args.redisCert, tt.args.redisKey, tt.args.redisTLS, tt.args.ttl) + tt.assertion(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestRedisOptions_BackendMasked(t *testing.T) { + tests := []struct { + name string + fields cache.RedisOptions + want string + }{ + { + name: "redis cache backend masked", + fields: cache.RedisOptions{Backend: "redis://root:password@localhost:6379"}, + want: "redis://****@localhost:6379", + }, + { + name: "redis cache backend masked does nothing", + fields: cache.RedisOptions{Backend: "redis://localhost:6379"}, + want: "redis://localhost:6379", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.fields.BackendMasked()) + }) + } +} diff --git a/pkg/cache/dir.go b/pkg/cache/dir.go new file mode 100644 index 000000000000..b687017c9faf --- /dev/null +++ b/pkg/cache/dir.go @@ -0,0 +1,30 @@ +package cache + +import ( + "os" + "path/filepath" +) + +var cacheDir string + +// defaultDir returns/creates the cache-dir to be used for trivy operations +func defaultDir() string { + tmpDir, err := os.UserCacheDir() + if err != nil { + tmpDir = os.TempDir() + } + return filepath.Join(tmpDir, "trivy") +} + +// Dir returns the directory used for caching +func Dir() string { + if cacheDir == "" { + return defaultDir() + } + return cacheDir +} + +// SetDir sets the trivy cache dir +func SetDir(dir string) { + cacheDir = dir +} diff --git a/pkg/fanal/cache/fs.go b/pkg/cache/fs.go similarity index 100% rename from pkg/fanal/cache/fs.go rename to pkg/cache/fs.go diff --git a/pkg/fanal/cache/fs_test.go b/pkg/cache/fs_test.go similarity index 100% rename from pkg/fanal/cache/fs_test.go rename to pkg/cache/fs_test.go diff --git a/pkg/fanal/cache/key.go b/pkg/cache/key.go similarity index 100% rename from pkg/fanal/cache/key.go rename to pkg/cache/key.go diff --git a/pkg/fanal/cache/key_test.go b/pkg/cache/key_test.go similarity index 100% rename from pkg/fanal/cache/key_test.go rename to pkg/cache/key_test.go diff --git a/pkg/fanal/cache/mock_artifact_cache.go b/pkg/cache/mock_artifact_cache.go similarity index 100% rename from pkg/fanal/cache/mock_artifact_cache.go rename to pkg/cache/mock_artifact_cache.go diff --git a/pkg/fanal/cache/mock_cache.go b/pkg/cache/mock_cache.go similarity index 100% rename from pkg/fanal/cache/mock_cache.go rename to pkg/cache/mock_cache.go diff --git a/pkg/fanal/cache/mock_local_artifact_cache.go b/pkg/cache/mock_local_artifact_cache.go similarity index 100% rename from pkg/fanal/cache/mock_local_artifact_cache.go rename to pkg/cache/mock_local_artifact_cache.go diff --git a/pkg/cache/nop.go b/pkg/cache/nop.go index 6b52e91108c6..4a76cd84e414 100644 --- a/pkg/cache/nop.go +++ b/pkg/cache/nop.go @@ -1,16 +1,11 @@ package cache -import "github.com/aquasecurity/trivy/pkg/fanal/cache" +import "github.com/aquasecurity/trivy/pkg/fanal/types" -func NopCache(ac cache.ArtifactCache) cache.Cache { - return nopCache{ArtifactCache: ac} -} +type NopCache struct{} -type nopCache struct { - cache.ArtifactCache - cache.LocalArtifactCache -} - -func (nopCache) Close() error { - return nil -} +func NewNopCache() NopCache { return NopCache{} } +func (NopCache) GetArtifact(string) (types.ArtifactInfo, error) { return types.ArtifactInfo{}, nil } +func (NopCache) GetBlob(string) (types.BlobInfo, error) { return types.BlobInfo{}, nil } +func (NopCache) Close() error { return nil } +func (NopCache) Clear() error { return nil } diff --git a/pkg/fanal/cache/redis.go b/pkg/cache/redis.go similarity index 100% rename from pkg/fanal/cache/redis.go rename to pkg/cache/redis.go diff --git a/pkg/fanal/cache/redis_test.go b/pkg/cache/redis_test.go similarity index 99% rename from pkg/fanal/cache/redis_test.go rename to pkg/cache/redis_test.go index 335e272890f2..46716a7d7bfe 100644 --- a/pkg/fanal/cache/redis_test.go +++ b/pkg/cache/redis_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" ) diff --git a/pkg/cache/remote.go b/pkg/cache/remote.go index 5900bf7a3b91..f55d877d5f3d 100644 --- a/pkg/cache/remote.go +++ b/pkg/cache/remote.go @@ -7,7 +7,6 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/rpc" "github.com/aquasecurity/trivy/pkg/rpc/client" @@ -21,7 +20,7 @@ type RemoteCache struct { } // NewRemoteCache is the factory method for RemoteCache -func NewRemoteCache(url string, customHeaders http.Header, insecure bool) cache.ArtifactCache { +func NewRemoteCache(url string, customHeaders http.Header, insecure bool) ArtifactCache { ctx := client.WithCustomHeaders(context.Background(), customHeaders) httpClient := &http.Client{ @@ -33,7 +32,10 @@ func NewRemoteCache(url string, customHeaders http.Header, insecure bool) cache. }, } c := rpcCache.NewCacheProtobufClient(url, httpClient) - return &RemoteCache{ctx: ctx, client: c} + return &RemoteCache{ + ctx: ctx, + client: c, + } } // PutArtifact sends artifact to remote client diff --git a/pkg/cache/remote_test.go b/pkg/cache/remote_test.go index a7a8e27dbaa9..bbfd72e3b20d 100644 --- a/pkg/cache/remote_test.go +++ b/pkg/cache/remote_test.go @@ -15,14 +15,13 @@ import ( "google.golang.org/protobuf/types/known/emptypb" "github.com/aquasecurity/trivy/pkg/cache" - fcache "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" rpcCache "github.com/aquasecurity/trivy/rpc/cache" rpcScanner "github.com/aquasecurity/trivy/rpc/scanner" ) type mockCacheServer struct { - cache fcache.Cache + cache cache.Cache } func (s *mockCacheServer) PutArtifact(_ context.Context, in *rpcCache.PutArtifactRequest) (*emptypb.Empty, error) { @@ -47,7 +46,10 @@ func (s *mockCacheServer) MissingBlobs(_ context.Context, in *rpcCache.MissingBl } layerIDs = append(layerIDs, layerID) } - return &rpcCache.MissingBlobsResponse{MissingArtifact: true, MissingBlobIds: layerIDs}, nil + return &rpcCache.MissingBlobsResponse{ + MissingArtifact: true, + MissingBlobIds: layerIDs, + }, nil } func (s *mockCacheServer) DeleteBlobs(_ context.Context, in *rpcCache.DeleteBlobsRequest) (*emptypb.Empty, error) { diff --git a/pkg/fanal/cache/testdata/broken-image.db b/pkg/cache/testdata/broken-image.db similarity index 100% rename from pkg/fanal/cache/testdata/broken-image.db rename to pkg/cache/testdata/broken-image.db diff --git a/pkg/fanal/cache/testdata/broken-layer.db b/pkg/cache/testdata/broken-layer.db similarity index 100% rename from pkg/fanal/cache/testdata/broken-layer.db rename to pkg/cache/testdata/broken-layer.db diff --git a/pkg/fanal/cache/testdata/different-image-schema.db b/pkg/cache/testdata/different-image-schema.db similarity index 100% rename from pkg/fanal/cache/testdata/different-image-schema.db rename to pkg/cache/testdata/different-image-schema.db diff --git a/pkg/fanal/cache/testdata/fanal.db b/pkg/cache/testdata/fanal.db similarity index 100% rename from pkg/fanal/cache/testdata/fanal.db rename to pkg/cache/testdata/fanal.db diff --git a/pkg/fanal/cache/testdata/policy/test.rego b/pkg/cache/testdata/policy/test.rego similarity index 100% rename from pkg/fanal/cache/testdata/policy/test.rego rename to pkg/cache/testdata/policy/test.rego diff --git a/pkg/fanal/cache/testdata/trivy-secret.yaml b/pkg/cache/testdata/trivy-secret.yaml similarity index 100% rename from pkg/fanal/cache/testdata/trivy-secret.yaml rename to pkg/cache/testdata/trivy-secret.yaml diff --git a/pkg/commands/artifact/inject.go b/pkg/commands/artifact/inject.go index f0538efdbce3..174af5045f74 100644 --- a/pkg/commands/artifact/inject.go +++ b/pkg/commands/artifact/inject.go @@ -5,10 +5,11 @@ package artifact import ( "context" + "github.com/google/wire" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/rpc/client" "github.com/aquasecurity/trivy/pkg/scanner" diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index b430fdc1efc9..3a22f762c71c 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -12,11 +12,10 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy-db/pkg/db" - tcache "github.com/aquasecurity/trivy/pkg/cache" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/commands/operation" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/fanal/cache" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" "github.com/aquasecurity/trivy/pkg/flag" @@ -31,7 +30,6 @@ import ( "github.com/aquasecurity/trivy/pkg/rpc/client" "github.com/aquasecurity/trivy/pkg/scanner" "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" "github.com/aquasecurity/trivy/pkg/version/doc" ) @@ -92,8 +90,9 @@ type Runner interface { } type runner struct { - cache cache.Cache - dbOpen bool + cache cache.ArtifactCache + localCache cache.LocalArtifactCache + dbOpen bool // WASM modules module *module.Manager @@ -106,6 +105,7 @@ type runnerOption func(*runner) func WithCacheClient(c cache.Cache) runnerOption { return func(r *runner) { r.cache = c + r.localCache = c } } @@ -143,7 +143,7 @@ func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...runnerOptio // Close closes everything func (r *runner) Close(ctx context.Context) error { var errs error - if err := r.cache.Close(); err != nil { + if err := r.localCache.Close(); err != nil { errs = multierror.Append(errs, err) } @@ -259,7 +259,7 @@ func (r *runner) ScanVM(ctx context.Context, opts flag.Options) (types.Report, e } func (r *runner) scanArtifact(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner) (types.Report, error) { - report, err := scan(ctx, opts, initializeScanner, r.cache) + report, err := r.scan(ctx, opts, initializeScanner) if err != nil { return types.Report{}, xerrors.Errorf("scan error: %w", err) } @@ -344,18 +344,18 @@ func (r *runner) initCache(opts flag.Options) error { // client/server mode if opts.ServerAddr != "" { - remoteCache := tcache.NewRemoteCache(opts.ServerAddr, opts.CustomHeaders, opts.Insecure) - r.cache = tcache.NopCache(remoteCache) + r.cache = cache.NewRemoteCache(opts.ServerAddr, opts.CustomHeaders, opts.Insecure) + r.localCache = cache.NewNopCache() // No need to use local cache in client/server mode return nil } // standalone mode - fsutils.SetCacheDir(opts.CacheDir) - cacheClient, err := operation.NewCache(opts.CacheOptions) + cache.SetDir(opts.CacheDir) + cacheClient, err := cache.NewClient(opts.CacheOptions.CacheBackendOptions) if err != nil { return xerrors.Errorf("unable to initialize the cache: %w", err) } - log.Debug("Cache dir", log.String("dir", fsutils.CacheDir())) + log.Debug("Cache dir", log.String("dir", cache.Dir())) if opts.Reset { defer cacheClient.Close() @@ -366,7 +366,7 @@ func (r *runner) initCache(opts flag.Options) error { } if opts.ResetChecksBundle { - c, err := policy.NewClient(fsutils.CacheDir(), true, opts.MisconfOptions.ChecksBundleRepository) + c, err := policy.NewClient(cache.Dir(), true, opts.MisconfOptions.ChecksBundleRepository) if err != nil { return xerrors.Errorf("failed to instantiate check client: %w", err) } @@ -384,7 +384,8 @@ func (r *runner) initCache(opts flag.Options) error { return SkipScan } - r.cache = cacheClient + r.cache = cacheClient.Cache + r.localCache = cacheClient.Cache return nil } @@ -526,7 +527,7 @@ func filterMisconfigAnalyzers(included, all []analyzer.Type) ([]analyzer.Type, e return lo.Without(all, included...), nil } -func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfig, types.ScanOptions, error) { +func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.ScanOptions, error) { target := opts.Target if opts.Input != "" { target = opts.Input @@ -616,8 +617,8 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi return ScannerConfig{ Target: target, - ArtifactCache: cacheClient, - LocalArtifactCache: cacheClient, + ArtifactCache: r.cache, + LocalArtifactCache: r.localCache, ServerOption: client.ScannerOption{ RemoteURL: opts.ServerAddr, CustomHeaders: opts.CustomHeaders, @@ -675,9 +676,8 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi }, scanOptions, nil } -func scan(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner, cacheClient cache.Cache) ( - types.Report, error) { - scannerConfig, scanOptions, err := initScannerConfig(opts, cacheClient) +func (r *runner) scan(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner) (types.Report, error) { + scannerConfig, scanOptions, err := r.initScannerConfig(opts) if err != nil { return types.Report{}, err } diff --git a/pkg/commands/artifact/wire_gen.go b/pkg/commands/artifact/wire_gen.go index 5e12c10e54d8..85e66b8214f2 100644 --- a/pkg/commands/artifact/wire_gen.go +++ b/pkg/commands/artifact/wire_gen.go @@ -16,7 +16,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/artifact/repo" "github.com/aquasecurity/trivy/pkg/fanal/artifact/sbom" "github.com/aquasecurity/trivy/pkg/fanal/artifact/vm" - "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/image" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index e315405b50b9..63946710f1b2 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -2,114 +2,22 @@ package operation import ( "context" - "crypto/tls" - "crypto/x509" - "os" - "strings" "sync" - "github.com/go-redis/redis/v8" "github.com/google/go-containerregistry/pkg/name" - "github.com/google/wire" - "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy-db/pkg/metadata" "github.com/aquasecurity/trivy/pkg/db" - "github.com/aquasecurity/trivy/pkg/fanal/cache" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/policy" "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) var mu sync.Mutex -// SuperSet binds cache dependencies -var SuperSet = wire.NewSet( - cache.NewFSCache, - wire.Bind(new(cache.LocalArtifactCache), new(cache.FSCache)), - NewCache, -) - -// Cache implements the local cache -type Cache struct { - cache.Cache -} - -// NewCache is the factory method for Cache -func NewCache(c flag.CacheOptions) (Cache, error) { - if strings.HasPrefix(c.CacheBackend, "redis://") { - log.Info("Redis cache", log.String("url", c.CacheBackendMasked())) - options, err := redis.ParseURL(c.CacheBackend) - if err != nil { - return Cache{}, err - } - - if !lo.IsEmpty(c.RedisOptions) { - caCert, cert, err := GetTLSConfig(c.RedisCACert, c.RedisCert, c.RedisKey) - if err != nil { - return Cache{}, err - } - - options.TLSConfig = &tls.Config{ - RootCAs: caCert, - Certificates: []tls.Certificate{cert}, - MinVersion: tls.VersionTLS12, - } - } else if c.RedisTLS { - options.TLSConfig = &tls.Config{ - MinVersion: tls.VersionTLS12, - } - } - - redisCache := cache.NewRedisCache(options, c.CacheTTL) - return Cache{Cache: redisCache}, nil - } - - if c.CacheTTL != 0 { - log.Warn("'--cache-ttl' is only available with Redis cache backend") - } - - // standalone mode - fsCache, err := cache.NewFSCache(fsutils.CacheDir()) - if err != nil { - return Cache{}, xerrors.Errorf("unable to initialize fs cache: %w", err) - } - return Cache{Cache: fsCache}, nil -} - -// Reset resets the cache -func (c Cache) Reset() (err error) { - if err := c.ClearDB(); err != nil { - return xerrors.Errorf("failed to clear the database: %w", err) - } - if err := c.ClearArtifacts(); err != nil { - return xerrors.Errorf("failed to clear the artifact cache: %w", err) - } - return nil -} - -// ClearDB clears the DB cache -func (c Cache) ClearDB() (err error) { - log.Info("Removing DB file...") - if err = os.RemoveAll(fsutils.CacheDir()); err != nil { - return xerrors.Errorf("failed to remove the directory (%s) : %w", fsutils.CacheDir(), err) - } - return nil -} - -// ClearArtifacts clears the artifact cache -func (c Cache) ClearArtifacts() error { - log.Info("Removing artifact caches...") - if err := c.Clear(); err != nil { - return xerrors.Errorf("failed to remove the cache: %w", err) - } - return nil -} - // DownloadDB downloads the DB func DownloadDB(ctx context.Context, appVersion, cacheDir string, dbRepository name.Reference, quiet, skipUpdate bool, opt ftypes.RegistryOptions) error { @@ -186,24 +94,6 @@ func InitBuiltinPolicies(ctx context.Context, cacheDir string, quiet, skipUpdate return policyPaths, nil } -// GetTLSConfig gets tls config from CA, Cert and Key file -func GetTLSConfig(caCertPath, certPath, keyPath string) (*x509.CertPool, tls.Certificate, error) { - cert, err := tls.LoadX509KeyPair(certPath, keyPath) - if err != nil { - return nil, tls.Certificate{}, err - } - - caCert, err := os.ReadFile(caCertPath) - if err != nil { - return nil, tls.Certificate{}, err - } - - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) - - return caCertPool, cert, nil -} - func Exit(opts flag.Options, failedResults bool, m types.Metadata) error { if opts.ExitOnEOL != 0 && m.OS != nil && m.OS.Eosl { log.Error("Detected EOL OS", log.String("family", string(m.OS.Family)), diff --git a/pkg/commands/server/run.go b/pkg/commands/server/run.go index 70788db6a6f3..eeef097990ce 100644 --- a/pkg/commands/server/run.go +++ b/pkg/commands/server/run.go @@ -6,12 +6,12 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/commands/operation" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/module" rpcServer "github.com/aquasecurity/trivy/pkg/rpc/server" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) // Run runs the scan @@ -19,16 +19,16 @@ func Run(ctx context.Context, opts flag.Options) (err error) { log.InitLogger(opts.Debug, opts.Quiet) // configure cache dir - fsutils.SetCacheDir(opts.CacheDir) - cache, err := operation.NewCache(opts.CacheOptions) + cache.SetDir(opts.CacheDir) + cacheClient, err := cache.NewClient(opts.CacheOptions.CacheBackendOptions) if err != nil { return xerrors.Errorf("server cache error: %w", err) } - defer cache.Close() - log.Debug("Cache", log.String("dir", fsutils.CacheDir())) + defer cacheClient.Close() + log.Debug("Cache", log.String("dir", cache.Dir())) if opts.Reset { - return cache.ClearDB() + return cacheClient.ClearDB() } // download the database file @@ -57,5 +57,5 @@ func Run(ctx context.Context, opts flag.Options) (err error) { server := rpcServer.NewServer(opts.AppVersion, opts.Listen, opts.CacheDir, opts.Token, opts.TokenHeader, opts.DBRepository, opts.RegistryOpts()) - return server.ListenAndServe(ctx, cache, opts.SkipDBUpdate) + return server.ListenAndServe(ctx, cacheClient, opts.SkipDBUpdate) } diff --git a/pkg/fanal/applier/applier.go b/pkg/fanal/applier/applier.go index 192c9d2184f2..0ddeef11eba2 100644 --- a/pkg/fanal/applier/applier.go +++ b/pkg/fanal/applier/applier.go @@ -3,8 +3,8 @@ package applier import ( "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/cache" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) diff --git a/pkg/fanal/applier/applier_test.go b/pkg/fanal/applier/applier_test.go index ac8915f2dad6..b2a992f80012 100644 --- a/pkg/fanal/applier/applier_test.go +++ b/pkg/fanal/applier/applier_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/applier" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" ) diff --git a/pkg/fanal/artifact/image/image.go b/pkg/fanal/artifact/image/image.go index c4491eaa90ab..b4350b25b866 100644 --- a/pkg/fanal/artifact/image/image.go +++ b/pkg/fanal/artifact/image/image.go @@ -14,9 +14,9 @@ import ( "github.com/samber/lo" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/handler" "github.com/aquasecurity/trivy/pkg/fanal/image" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go index 05fc6b229105..6ce36846868a 100644 --- a/pkg/fanal/artifact/image/image_test.go +++ b/pkg/fanal/artifact/image/image_test.go @@ -11,10 +11,10 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" image2 "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/image" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/artifact/image/remote_sbom_test.go b/pkg/fanal/artifact/image/remote_sbom_test.go index 1fd29fe2c69a..29fcc10f52fc 100644 --- a/pkg/fanal/artifact/image/remote_sbom_test.go +++ b/pkg/fanal/artifact/image/remote_sbom_test.go @@ -14,9 +14,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/artifact" image2 "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/rekortest" diff --git a/pkg/fanal/artifact/local/fs.go b/pkg/fanal/artifact/local/fs.go index b807db2c6733..2f5ef7fe4ecd 100644 --- a/pkg/fanal/artifact/local/fs.go +++ b/pkg/fanal/artifact/local/fs.go @@ -14,9 +14,9 @@ import ( "github.com/opencontainers/go-digest" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/handler" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index 2cee794c85b2..d27b5ffb4366 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -10,9 +10,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" "github.com/aquasecurity/trivy/pkg/misconf" diff --git a/pkg/fanal/artifact/repo/git.go b/pkg/fanal/artifact/repo/git.go index 4ce1c990c925..8eb3d9af7f9d 100644 --- a/pkg/fanal/artifact/repo/git.go +++ b/pkg/fanal/artifact/repo/git.go @@ -12,9 +12,9 @@ import ( "github.com/hashicorp/go-multierror" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact/local" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/walker" ) diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go index 0e4c8ee39d4b..8de1f3d8864b 100644 --- a/pkg/fanal/artifact/repo/git_test.go +++ b/pkg/fanal/artifact/repo/git_test.go @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/internal/gittest" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/walker" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all" diff --git a/pkg/fanal/artifact/sbom/sbom.go b/pkg/fanal/artifact/sbom/sbom.go index 979c5c5a8517..a5b646c18889 100644 --- a/pkg/fanal/artifact/sbom/sbom.go +++ b/pkg/fanal/artifact/sbom/sbom.go @@ -11,9 +11,9 @@ import ( "github.com/samber/lo" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/handler" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" diff --git a/pkg/fanal/artifact/sbom/sbom_test.go b/pkg/fanal/artifact/sbom/sbom_test.go index 37ea39380b43..1dffa52f68c4 100644 --- a/pkg/fanal/artifact/sbom/sbom_test.go +++ b/pkg/fanal/artifact/sbom/sbom_test.go @@ -11,9 +11,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact/sbom" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" ) diff --git a/pkg/fanal/artifact/vm/ebs.go b/pkg/fanal/artifact/vm/ebs.go index 64e1cc6a6b5a..280236cb371f 100644 --- a/pkg/fanal/artifact/vm/ebs.go +++ b/pkg/fanal/artifact/vm/ebs.go @@ -8,9 +8,9 @@ import ( ebsfile "github.com/masahiro331/go-ebs-file" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/cloud/aws/config" "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/log" ) diff --git a/pkg/fanal/artifact/vm/file.go b/pkg/fanal/artifact/vm/file.go index 7968cf44681b..a3cb262a98c8 100644 --- a/pkg/fanal/artifact/vm/file.go +++ b/pkg/fanal/artifact/vm/file.go @@ -12,8 +12,8 @@ import ( "github.com/opencontainers/go-digest" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/vm" "github.com/aquasecurity/trivy/pkg/fanal/vm/disk" diff --git a/pkg/fanal/artifact/vm/vm.go b/pkg/fanal/artifact/vm/vm.go index 5b9aae130f4c..56f7a0f5fa88 100644 --- a/pkg/fanal/artifact/vm/vm.go +++ b/pkg/fanal/artifact/vm/vm.go @@ -10,9 +10,9 @@ import ( "github.com/google/wire" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/handler" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" diff --git a/pkg/fanal/artifact/vm/vm_test.go b/pkg/fanal/artifact/vm/vm_test.go index d1becf0f7067..c1331554f5cc 100644 --- a/pkg/fanal/artifact/vm/vm_test.go +++ b/pkg/fanal/artifact/vm/vm_test.go @@ -14,10 +14,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact/vm" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" "github.com/aquasecurity/trivy/pkg/misconf" diff --git a/pkg/fanal/cache/s3.go b/pkg/fanal/cache/s3.go deleted file mode 100644 index 0176d2cf80d8..000000000000 --- a/pkg/fanal/cache/s3.go +++ /dev/null @@ -1,180 +0,0 @@ -package cache - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/feature/s3/manager" - "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/hashicorp/go-multierror" - "golang.org/x/xerrors" - - "github.com/aquasecurity/trivy/pkg/fanal/types" -) - -var _ Cache = &S3Cache{} - -type s3API interface { - HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error) - PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) - DeleteBucket(ctx context.Context, params *s3.DeleteBucketInput, optFns ...func(*s3.Options)) (*s3.DeleteBucketOutput, error) -} - -type S3Cache struct { - s3Client s3API - downloader *manager.Downloader - bucketName string - prefix string -} - -func NewS3Cache(bucketName, prefix string, api s3API, downloaderAPI *manager.Downloader) S3Cache { - return S3Cache{ - s3Client: api, - downloader: downloaderAPI, - bucketName: bucketName, - prefix: prefix, - } -} - -func (c S3Cache) PutArtifact(artifactID string, artifactConfig types.ArtifactInfo) (err error) { - key := fmt.Sprintf("%s/%s/%s", artifactBucket, c.prefix, artifactID) - if err := c.put(key, artifactConfig); err != nil { - return xerrors.Errorf("unable to store artifact information in cache (%s): %w", artifactID, err) - } - return nil -} - -func (c S3Cache) DeleteBlobs(blobIDs []string) error { - var errs error - for _, blobID := range blobIDs { - key := fmt.Sprintf("%s/%s/%s", blobBucket, c.prefix, blobID) - input := &s3.DeleteBucketInput{Bucket: aws.String(key)} - if _, err := c.s3Client.DeleteBucket(context.TODO(), input); err != nil { - errs = multierror.Append(errs, err) - } - } - return errs -} - -func (c S3Cache) PutBlob(blobID string, blobInfo types.BlobInfo) error { - key := fmt.Sprintf("%s/%s/%s", blobBucket, c.prefix, blobID) - if err := c.put(key, blobInfo); err != nil { - return xerrors.Errorf("unable to store blob information in cache (%s): %w", blobID, err) - } - return nil -} - -func (c S3Cache) put(key string, body any) (err error) { - b, err := json.Marshal(body) - if err != nil { - return err - } - params := &s3.PutObjectInput{ - Bucket: aws.String(c.bucketName), - Key: aws.String(key), - Body: bytes.NewReader(b), - } - _, err = c.s3Client.PutObject(context.TODO(), params) - if err != nil { - return xerrors.Errorf("unable to put object: %w", err) - } - // Index file due S3 caveat read after write consistency - _, err = c.s3Client.PutObject(context.TODO(), &s3.PutObjectInput{ - Bucket: aws.String(c.bucketName), - Key: aws.String(fmt.Sprintf("%s.index", key)), - }) - if err != nil { - return xerrors.Errorf("unable to put index object: %w", err) - } - return nil -} - -func (c S3Cache) GetBlob(blobID string) (types.BlobInfo, error) { - var blobInfo types.BlobInfo - buf := manager.NewWriteAtBuffer([]byte{}) - _, err := c.downloader.Download(context.TODO(), buf, &s3.GetObjectInput{ - Bucket: aws.String(c.bucketName), - Key: aws.String(fmt.Sprintf("%s/%s/%s", blobBucket, c.prefix, blobID)), - }) - if err != nil { - return types.BlobInfo{}, xerrors.Errorf("failed to get blob from the cache: %w", err) - } - err = json.Unmarshal(buf.Bytes(), &blobInfo) - if err != nil { - return types.BlobInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err) - } - return blobInfo, nil -} - -func (c S3Cache) GetArtifact(artifactID string) (types.ArtifactInfo, error) { - var info types.ArtifactInfo - buf := manager.NewWriteAtBuffer([]byte{}) - _, err := c.downloader.Download(context.TODO(), buf, &s3.GetObjectInput{ - Bucket: aws.String(c.bucketName), - Key: aws.String(fmt.Sprintf("%s/%s/%s", artifactBucket, c.prefix, artifactID)), - }) - if err != nil { - return types.ArtifactInfo{}, xerrors.Errorf("failed to get artifact from the cache: %w", err) - } - err = json.Unmarshal(buf.Bytes(), &info) - if err != nil { - return types.ArtifactInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err) - } - return info, nil -} - -func (c S3Cache) getIndex(key, keyType string) error { - _, err := c.s3Client.HeadObject(context.TODO(), &s3.HeadObjectInput{ - Key: aws.String(fmt.Sprintf("%s/%s/%s.index", keyType, c.prefix, key)), - Bucket: &c.bucketName, - }) - if err != nil { - return xerrors.Errorf("failed to get index from the cache: %w", err) - } - return nil -} - -func (c S3Cache) MissingBlobs(artifactID string, blobIDs []string) (bool, []string, error) { - var missingArtifact bool - var missingBlobIDs []string - for _, blobID := range blobIDs { - err := c.getIndex(blobID, blobBucket) - if err != nil { - // error means cache missed blob info - missingBlobIDs = append(missingBlobIDs, blobID) - continue - } - blobInfo, err := c.GetBlob(blobID) - if err != nil { - return true, missingBlobIDs, xerrors.Errorf("the blob object (%s) doesn't exist in S3 even though the index file exists: %w", blobID, err) - } - if blobInfo.SchemaVersion != types.BlobJSONSchemaVersion { - missingBlobIDs = append(missingBlobIDs, blobID) - } - } - // get artifact info - err := c.getIndex(artifactID, artifactBucket) - // error means cache missed artifact info - if err != nil { - return true, missingBlobIDs, nil - } - artifactInfo, err := c.GetArtifact(artifactID) - if err != nil { - return true, missingBlobIDs, xerrors.Errorf("the artifact object (%s) doesn't exist in S3 even though the index file exists: %w", artifactID, err) - } - if artifactInfo.SchemaVersion != types.ArtifactJSONSchemaVersion { - missingArtifact = true - } - return missingArtifact, missingBlobIDs, nil -} - -func (c S3Cache) Close() error { - return nil -} - -func (c S3Cache) Clear() error { - return nil -} diff --git a/pkg/fanal/cache/s3_test.go b/pkg/fanal/cache/s3_test.go deleted file mode 100644 index ed3da27b974f..000000000000 --- a/pkg/fanal/cache/s3_test.go +++ /dev/null @@ -1,312 +0,0 @@ -package cache - -import ( - "context" - "errors" - "reflect" - "testing" - "time" - - "github.com/aws/aws-sdk-go-v2/feature/s3/manager" - "github.com/aws/aws-sdk-go-v2/service/s3" - "golang.org/x/xerrors" - - "github.com/aquasecurity/trivy/pkg/fanal/types" -) - -type mockS3Client struct { - s3API -} - -const ( - correctHash = "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7" -) - -func (m *mockS3Client) PutObject(ctx context.Context, in *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) { - return &s3.PutObjectOutput{}, nil -} - -func (m *mockS3Client) HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error) { - return &s3.HeadObjectOutput{}, nil -} - -func (m *mockS3Client) DeleteBucket(ctx context.Context, in *s3.DeleteBucketInput, optFns ...func(*s3.Options)) (*s3.DeleteBucketOutput, error) { - if in != nil && *in.Bucket == blobBucket+"/prefix/"+correctHash { - return &s3.DeleteBucketOutput{}, nil - } - return nil, errors.New("unknown bucket") -} - -func TestS3Cache_PutBlob(t *testing.T) { - mockSvc := &mockS3Client{} - - type fields struct { - S3 s3API - Downloader *manager.Downloader - BucketName string - Prefix string - } - type args struct { - blobID string - blobInfo types.BlobInfo - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - S3: mockSvc, - BucketName: "test", - Prefix: "prefix", - }, - args: args{ - blobID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", - blobInfo: types.BlobInfo{ - SchemaVersion: 1, - OS: types.OS{ - Family: "alpine", - Name: "3.10", - }, - }}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewS3Cache(tt.fields.BucketName, tt.fields.Prefix, tt.fields.S3, tt.fields.Downloader) - if err := c.PutBlob(tt.args.blobID, tt.args.blobInfo); (err != nil) != tt.wantErr { - t.Errorf("S3Cache.PutBlob() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestS3Cache_PutArtifact(t *testing.T) { - mockSvc := &mockS3Client{} - - type fields struct { - S3 s3API - Downloader *manager.Downloader - BucketName string - Prefix string - } - type args struct { - artifactID string - artifactConfig types.ArtifactInfo - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - S3: mockSvc, - BucketName: "test", - Prefix: "prefix", - }, - args: args{ - artifactID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", - artifactConfig: types.ArtifactInfo{ - SchemaVersion: 1, - Architecture: "amd64", - Created: time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC), - DockerVersion: "18.06.1-ce", - OS: "linux", - HistoryPackages: []types.Package{ - { - Name: "musl", - Version: "1.2.3", - }, - }, - }}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewS3Cache(tt.fields.BucketName, tt.fields.Prefix, tt.fields.S3, tt.fields.Downloader) - if err := c.PutArtifact(tt.args.artifactID, tt.args.artifactConfig); (err != nil) != tt.wantErr { - t.Errorf("S3Cache.PutArtifact() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestS3Cache_getIndex(t *testing.T) { - mockSvc := &mockS3Client{} - - type fields struct { - S3 s3API - Downloader *manager.Downloader - BucketName string - Prefix string - } - type args struct { - key string - keyType string - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - S3: mockSvc, - BucketName: "test", - Prefix: "prefix", - }, - args: args{ - key: "key", - keyType: "artifactBucket", - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewS3Cache(tt.fields.BucketName, tt.fields.Prefix, tt.fields.S3, tt.fields.Downloader) - if err := c.getIndex(tt.args.key, tt.args.keyType); (err != nil) != tt.wantErr { - t.Errorf("S3Cache.getIndex() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -type mockS3ClientMissingBlobs struct { - s3API -} - -func (m *mockS3ClientMissingBlobs) PutObject(ctx context.Context, in *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) { - return &s3.PutObjectOutput{}, nil -} - -func (m *mockS3ClientMissingBlobs) HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error) { - return &s3.HeadObjectOutput{}, xerrors.Errorf("the object doesn't exist in S3") -} - -func TestS3Cache_MissingBlobs(t *testing.T) { - mockSvc := &mockS3ClientMissingBlobs{} - - type fields struct { - S3 s3API - Downloader *manager.Downloader - BucketName string - Prefix string - } - type args struct { - artifactID string - blobIDs []string - analyzerVersions map[string]int - configAnalyzerVersions map[string]int - } - tests := []struct { - name string - fields fields - args args - want bool - wantStringSlice []string - wantErr bool - }{{ - name: "happy path", - fields: fields{ - S3: mockSvc, - BucketName: "test", - Prefix: "prefix", - }, - args: args{ - artifactID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4/1", - blobIDs: []string{"sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7/10011"}, - }, - want: true, - wantStringSlice: []string{"sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7/10011"}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewS3Cache(tt.fields.BucketName, tt.fields.Prefix, tt.fields.S3, tt.fields.Downloader) - got, got1, err := c.MissingBlobs(tt.args.artifactID, tt.args.blobIDs) - if (err != nil) != tt.wantErr { - t.Errorf("S3Cache.MissingBlobs() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("S3Cache.MissingBlobs() got = %v, want %v", got, tt.want) - } - if !reflect.DeepEqual(got1, tt.wantStringSlice) { - t.Errorf("S3Cache.MissingBlobs() got1 = %v, want %v", got1, tt.wantStringSlice) - } - }) - } -} - -func TestS3Cache_DeleteBlobs(t *testing.T) { - mockSvc := &mockS3Client{} - - type fields struct { - S3 s3API - Downloader *manager.Downloader - BucketName string - Prefix string - } - type args struct { - blobIDs []string - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - { - name: "happy path", - fields: fields{ - S3: mockSvc, - BucketName: "test", - Prefix: "prefix", - }, - args: args{ - blobIDs: []string{correctHash}, - }, - }, - { - name: "delete blob with bad ID", - fields: fields{ - S3: mockSvc, - BucketName: "test", - Prefix: "prefix", - }, - args: args{ - blobIDs: []string{"unde"}, - }, - wantErr: true, - }, - { - name: "delete blobs with bad ID", - fields: fields{ - S3: mockSvc, - BucketName: "test", - Prefix: "prefix", - }, - args: args{ - blobIDs: []string{correctHash}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewS3Cache(tt.fields.BucketName, tt.fields.Prefix, tt.fields.S3, tt.fields.Downloader) - if err := c.DeleteBlobs(tt.args.blobIDs); (err != nil) != tt.wantErr { - t.Errorf("S3Cache.PutBlob() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/pkg/fanal/test/integration/containerd_test.go b/pkg/fanal/test/integration/containerd_test.go index e0fdd7602e32..d16ad3dac059 100644 --- a/pkg/fanal/test/integration/containerd_test.go +++ b/pkg/fanal/test/integration/containerd_test.go @@ -27,11 +27,11 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/applier" "github.com/aquasecurity/trivy/pkg/fanal/artifact" aimage "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/image" "github.com/aquasecurity/trivy/pkg/fanal/types" ) diff --git a/pkg/fanal/test/integration/library_test.go b/pkg/fanal/test/integration/library_test.go index 9e2073185c60..f06a8c3f5c6c 100644 --- a/pkg/fanal/test/integration/library_test.go +++ b/pkg/fanal/test/integration/library_test.go @@ -20,11 +20,11 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/cache" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/all" "github.com/aquasecurity/trivy/pkg/fanal/applier" "github.com/aquasecurity/trivy/pkg/fanal/artifact" aimage "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" - "github.com/aquasecurity/trivy/pkg/fanal/cache" _ "github.com/aquasecurity/trivy/pkg/fanal/handler/all" "github.com/aquasecurity/trivy/pkg/fanal/image" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/test/integration/registry_test.go b/pkg/fanal/test/integration/registry_test.go index 081f9df20d95..5b062e425729 100644 --- a/pkg/fanal/test/integration/registry_test.go +++ b/pkg/fanal/test/integration/registry_test.go @@ -20,12 +20,12 @@ import ( testcontainers "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/all" "github.com/aquasecurity/trivy/pkg/fanal/applier" "github.com/aquasecurity/trivy/pkg/fanal/artifact" aimage "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/image" testdocker "github.com/aquasecurity/trivy/pkg/fanal/test/integration/docker" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/flag/cache_flags.go b/pkg/flag/cache_flags.go index c259d5b9e963..75b7a1837371 100644 --- a/pkg/flag/cache_flags.go +++ b/pkg/flag/cache_flags.go @@ -1,12 +1,11 @@ package flag import ( - "fmt" - "strings" "time" - "github.com/samber/lo" "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/cache" ) // e.g. config yaml: @@ -70,18 +69,8 @@ type CacheFlagGroup struct { } type CacheOptions struct { - ClearCache bool - CacheBackend string - CacheTTL time.Duration - RedisTLS bool - RedisOptions -} - -// RedisOptions holds the options for redis cache -type RedisOptions struct { - RedisCACert string - RedisCert string - RedisKey string + ClearCache bool + CacheBackendOptions cache.Options } // NewCacheFlagGroup returns a default CacheFlagGroup @@ -118,43 +107,14 @@ func (fg *CacheFlagGroup) ToOptions() (CacheOptions, error) { return CacheOptions{}, err } - cacheBackend := fg.CacheBackend.Value() - redisOptions := RedisOptions{ - RedisCACert: fg.RedisCACert.Value(), - RedisCert: fg.RedisCert.Value(), - RedisKey: fg.RedisKey.Value(), - } - - // "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") - } + backendOpts, err := cache.NewOptions(fg.CacheBackend.Value(), fg.RedisCACert.Value(), fg.RedisCert.Value(), + fg.RedisKey.Value(), fg.RedisTLS.Value(), fg.CacheTTL.Value()) + if err != nil { + return CacheOptions{}, xerrors.Errorf("failed to initialize cache options: %w", err) } return CacheOptions{ - ClearCache: fg.ClearCache.Value(), - CacheBackend: cacheBackend, - CacheTTL: fg.CacheTTL.Value(), - RedisTLS: fg.RedisTLS.Value(), - RedisOptions: redisOptions, + ClearCache: fg.ClearCache.Value(), + CacheBackendOptions: backendOpts, }, 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:]) -} diff --git a/pkg/flag/cache_flags_test.go b/pkg/flag/cache_flags_test.go deleted file mode 100644 index c795cdbd6715..000000000000 --- a/pkg/flag/cache_flags_test.go +++ /dev/null @@ -1,160 +0,0 @@ -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 - RedisTLS bool - 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: "redis tls with public certificates", - fields: fields{ - CacheBackend: "redis://localhost:6379", - RedisTLS: true, - }, - want: flag.CacheOptions{ - CacheBackend: "redis://localhost:6379", - RedisTLS: true, - }, - assertion: require.NoError, - }, - { - name: "unknown backend", - fields: fields{ - CacheBackend: "unknown", - }, - assertion: func(t require.TestingT, err error, msgs ...any) { - 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 ...any) { - 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.RedisTLSFlag.ConfigName, tt.fields.RedisTLS) - 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.Clone(), - CacheBackend: flag.CacheBackendFlag.Clone(), - CacheTTL: flag.CacheTTLFlag.Clone(), - RedisTLS: flag.RedisTLSFlag.Clone(), - RedisCACert: flag.RedisCACertFlag.Clone(), - RedisCert: flag.RedisCertFlag.Clone(), - RedisKey: flag.RedisKeyFlag.Clone(), - } - - 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()) - }) - } -} diff --git a/pkg/flag/global_flags.go b/pkg/flag/global_flags.go index aa4851c657f0..eb34e2f589dc 100644 --- a/pkg/flag/global_flags.go +++ b/pkg/flag/global_flags.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" + "github.com/aquasecurity/trivy/pkg/cache" ) var ( @@ -55,7 +55,7 @@ var ( CacheDirFlag = Flag[string]{ Name: "cache-dir", ConfigName: "cache.dir", - Default: fsutils.CacheDir(), + Default: cache.Dir(), Usage: "cache directory", Persistent: true, } diff --git a/pkg/k8s/inject.go b/pkg/k8s/inject.go index 31ffd2afffa7..e71b11fe57d9 100644 --- a/pkg/k8s/inject.go +++ b/pkg/k8s/inject.go @@ -6,7 +6,7 @@ package k8s import ( "github.com/google/wire" - "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/cache" ) func initializeScanK8s(localArtifactCache cache.LocalArtifactCache) *ScanKubernetes { diff --git a/pkg/k8s/wire_gen.go b/pkg/k8s/wire_gen.go index 2b2343a654b7..134fa8c1ec49 100644 --- a/pkg/k8s/wire_gen.go +++ b/pkg/k8s/wire_gen.go @@ -9,7 +9,7 @@ package k8s import ( "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy/pkg/fanal/applier" - "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/aquasecurity/trivy/pkg/scanner/local" "github.com/aquasecurity/trivy/pkg/scanner/ospkg" diff --git a/pkg/oci/artifact_test.go b/pkg/oci/artifact_test.go index d28dbe55bc1e..479c607af425 100644 --- a/pkg/oci/artifact_test.go +++ b/pkg/oci/artifact_test.go @@ -14,9 +14,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/cache" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/oci" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) type fakeLayer struct { @@ -97,7 +97,7 @@ func TestArtifact_Download(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tempDir := t.TempDir() - fsutils.SetCacheDir(tempDir) + cache.SetDir(tempDir) // Mock image img := new(fakei.FakeImage) diff --git a/pkg/plugin/index_test.go b/pkg/plugin/index_test.go index 7e5621d5ca1c..e420dfaa6d08 100644 --- a/pkg/plugin/index_test.go +++ b/pkg/plugin/index_test.go @@ -12,13 +12,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/plugin" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) func TestManager_Update(t *testing.T) { tempDir := t.TempDir() - fsutils.SetCacheDir(tempDir) + cache.SetDir(tempDir) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := w.Write([]byte(`this is index`)) @@ -73,7 +73,7 @@ bar A bar plugin } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - fsutils.SetCacheDir(tt.dir) + cache.SetDir(tt.dir) var got bytes.Buffer m := plugin.NewManager(plugin.WithWriter(&got)) diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 949c87525be7..70ae3758fbdc 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -14,6 +14,7 @@ import ( "gopkg.in/yaml.v3" "github.com/aquasecurity/go-version/pkg/semver" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/downloader" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" @@ -63,7 +64,7 @@ func NewManager(opts ...ManagerOption) *Manager { indexURL: indexURL, logger: log.WithPrefix("plugin"), pluginRoot: filepath.Join(fsutils.HomeDir(), pluginsRelativeDir), - indexPath: filepath.Join(fsutils.CacheDir(), "plugin", "index.yaml"), + indexPath: filepath.Join(cache.Dir(), "plugin", "index.yaml"), } for _, opt := range opts { opt(m) diff --git a/pkg/plugin/manager_unix_test.go b/pkg/plugin/manager_unix_test.go index 0250a80d7907..dd59109f94ee 100644 --- a/pkg/plugin/manager_unix_test.go +++ b/pkg/plugin/manager_unix_test.go @@ -20,11 +20,11 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/internal/gittest" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/clock" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/plugin" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) func setupGitRepository(t *testing.T, repo, dir string) *httptest.Server { @@ -200,7 +200,7 @@ func TestManager_Install(t *testing.T) { t.Setenv("XDG_DATA_HOME", dst) // For plugin index - fsutils.SetCacheDir("testdata") + cache.SetDir("testdata") if tt.installed != nil { setupInstalledPlugin(t, dst, *tt.installed) diff --git a/pkg/rpc/server/inject.go b/pkg/rpc/server/inject.go index 453e4e80a892..4c05df08b15e 100644 --- a/pkg/rpc/server/inject.go +++ b/pkg/rpc/server/inject.go @@ -6,7 +6,7 @@ package server import ( "github.com/google/wire" - "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/cache" ) func initializeScanServer(localArtifactCache cache.LocalArtifactCache) *ScanServer { diff --git a/pkg/rpc/server/listen.go b/pkg/rpc/server/listen.go index 78edb5a21876..7e2ebc6b8227 100644 --- a/pkg/rpc/server/listen.go +++ b/pkg/rpc/server/listen.go @@ -15,8 +15,8 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/metadata" + "github.com/aquasecurity/trivy/pkg/cache" dbc "github.com/aquasecurity/trivy/pkg/db" - "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/utils/fsutils" diff --git a/pkg/rpc/server/listen_test.go b/pkg/rpc/server/listen_test.go index 02cf8d0c59fd..82c8b2669bc4 100644 --- a/pkg/rpc/server/listen_test.go +++ b/pkg/rpc/server/listen_test.go @@ -17,9 +17,9 @@ import ( trivydb "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/metadata" "github.com/aquasecurity/trivy/internal/dbtest" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/db" - "github.com/aquasecurity/trivy/pkg/fanal/cache" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/policy" "github.com/aquasecurity/trivy/pkg/version" diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 79801b3bd212..eb29683942f5 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -8,7 +8,7 @@ import ( "golang.org/x/xerrors" "google.golang.org/protobuf/types/known/emptypb" - "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/rpc" "github.com/aquasecurity/trivy/pkg/scanner" diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 956db45249e3..78db0c06aac3 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -15,7 +15,7 @@ import ( dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/utils" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" - "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/cache" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/scanner" "github.com/aquasecurity/trivy/pkg/types" diff --git a/pkg/rpc/server/wire_gen.go b/pkg/rpc/server/wire_gen.go index 81f5ba451a72..4d667cbe9dfd 100644 --- a/pkg/rpc/server/wire_gen.go +++ b/pkg/rpc/server/wire_gen.go @@ -9,7 +9,7 @@ package server import ( "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy/pkg/fanal/applier" - "github.com/aquasecurity/trivy/pkg/fanal/cache" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/aquasecurity/trivy/pkg/scanner/local" "github.com/aquasecurity/trivy/pkg/scanner/ospkg" diff --git a/pkg/utils/fsutils/fs.go b/pkg/utils/fsutils/fs.go index c4f9c75cc9fc..b8efbf1cbc60 100644 --- a/pkg/utils/fsutils/fs.go +++ b/pkg/utils/fsutils/fs.go @@ -18,30 +18,6 @@ const ( xdgDataHome = "XDG_DATA_HOME" ) -var cacheDir string - -// defaultCacheDir returns/creates the cache-dir to be used for trivy operations -func defaultCacheDir() string { - tmpDir, err := os.UserCacheDir() - if err != nil { - tmpDir = os.TempDir() - } - return filepath.Join(tmpDir, "trivy") -} - -// CacheDir returns the directory used for caching -func CacheDir() string { - if cacheDir == "" { - return defaultCacheDir() - } - return cacheDir -} - -// SetCacheDir sets the trivy cacheDir -func SetCacheDir(dir string) { - cacheDir = dir -} - func HomeDir() string { dataHome := os.Getenv(xdgDataHome) if dataHome != "" { From 333087c9e8fad472912efd83093d80d0e508beca Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 21 Jun 2024 11:56:16 +0400 Subject: [PATCH 183/352] chore: bump Go toolchain version (#6984) Signed-off-by: knqyf263 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 71f2d1628f7e..cbfcdd088abd 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/aquasecurity/trivy go 1.22.0 -toolchain go1.22.2 +toolchain go1.22.4 require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible From 7eabb92ec2e617300433445718be07ac74956454 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 21 Jun 2024 14:14:50 +0600 Subject: [PATCH 184/352] fix(sbom): use `purl` for `bitnami` pkg names (#6982) Co-authored-by: Teppei Fukuda --- pkg/fanal/analyzer/sbom/sbom_test.go | 26 +++++++++++++------------- pkg/sbom/io/decode.go | 8 ++++++++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/pkg/fanal/analyzer/sbom/sbom_test.go b/pkg/fanal/analyzer/sbom/sbom_test.go index cce12a7c4955..542a7f50addd 100644 --- a/pkg/fanal/analyzer/sbom/sbom_test.go +++ b/pkg/fanal/analyzer/sbom/sbom_test.go @@ -93,8 +93,8 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { FilePath: "opt/bitnami/elasticsearch", Packages: types.Packages{ { - ID: "Elasticsearch@8.9.1", - Name: "Elasticsearch", + ID: "elasticsearch@8.9.1", + Name: "elasticsearch", Version: "8.9.1", Arch: "arm64", Licenses: []string{"Elastic-2.0"}, @@ -174,8 +174,8 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { FilePath: "opt/bitnami/postgresql", Packages: types.Packages{ { - ID: "GDAL@3.7.1", - Name: "GDAL", + ID: "gdal@3.7.1", + Name: "gdal", Version: "3.7.1", Licenses: []string{"MIT"}, Identifier: types.PkgIdentifier{ @@ -187,8 +187,8 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "GEOS@3.8.3", - Name: "GEOS", + ID: "geos@3.8.3", + Name: "geos", Version: "3.8.3", Licenses: []string{"LGPL-2.1-only"}, Identifier: types.PkgIdentifier{ @@ -200,8 +200,8 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { }, }, { - ID: "PostgreSQL@15.3.0", - Name: "PostgreSQL", + ID: "postgresql@15.3.0", + Name: "postgresql", Version: "15.3.0", Licenses: []string{"PostgreSQL"}, Identifier: types.PkgIdentifier{ @@ -212,14 +212,14 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { }, }, DependsOn: []string{ - "GEOS@3.8.3", - "Proj@6.3.2", - "GDAL@3.7.1", + "geos@3.8.3", + "proj@6.3.2", + "gdal@3.7.1", }, }, { - ID: "Proj@6.3.2", - Name: "Proj", + ID: "proj@6.3.2", + Name: "proj", Version: "6.3.2", Licenses: []string{"MIT"}, Identifier: types.PkgIdentifier{ diff --git a/pkg/sbom/io/decode.go b/pkg/sbom/io/decode.go index 707ed9a4c8dc..b740d756c4bd 100644 --- a/pkg/sbom/io/decode.go +++ b/pkg/sbom/io/decode.go @@ -256,6 +256,14 @@ func (m *Decoder) pkgName(pkg *ftypes.Package, c *core.Component) string { return pkg.Name } + // TODO(backward compatibility): Remove after 03/2025 + // Bitnami used different pkg.Name and the name from PURL. + // For backwards compatibility - we need to use PURL. + // cf. https://github.com/aquasecurity/trivy/issues/6981 + if c.PkgIdentifier.PURL.Type == packageurl.TypeBitnami { + return pkg.Name + } + if c.Group != "" { if p.Type == packageurl.TypeMaven || p.Type == packageurl.TypeGradle { return c.Group + ":" + c.Name From 648ead9553eb2cbeac90e3ef7330a70c352255ac Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 21 Jun 2024 13:45:39 +0400 Subject: [PATCH 185/352] refactor: replace global cache directory with parameter passing (#6986) Signed-off-by: knqyf263 --- pkg/cache/client.go | 33 ++++++------ pkg/cache/client_test.go | 50 +++++++++++++++++++ pkg/cache/dir.go | 19 +------ pkg/commands/app.go | 3 +- pkg/commands/artifact/run.go | 7 ++- pkg/commands/server/run.go | 7 ++- pkg/flag/global_flags.go | 2 +- pkg/oci/artifact_test.go | 2 - pkg/plugin/index_test.go | 7 ++- pkg/plugin/manager.go | 6 +-- pkg/plugin/manager_unix_test.go | 8 ++- .../{plugin => .trivy/plugins}/index.yaml | 0 12 files changed, 86 insertions(+), 58 deletions(-) rename pkg/plugin/testdata/{plugin => .trivy/plugins}/index.yaml (100%) diff --git a/pkg/cache/client.go b/pkg/cache/client.go index fea9395770c7..7b18827016d5 100644 --- a/pkg/cache/client.go +++ b/pkg/cache/client.go @@ -21,6 +21,7 @@ const ( ) type Client struct { + dir string Cache } @@ -115,7 +116,8 @@ func NewType(backend string) (Type, error) { } // NewClient returns a new cache client -func NewClient(opts Options) (*Client, error) { +func NewClient(dir string, opts Options) (*Client, error) { + client := &Client{dir: dir} if opts.Type == TypeRedis { log.Info("Redis cache", log.String("url", opts.Redis.BackendMasked())) options, err := redis.ParseURL(opts.Redis.Backend) @@ -140,34 +142,27 @@ func NewClient(opts Options) (*Client, error) { } } - redisCache := NewRedisCache(options, opts.TTL) - return &Client{Cache: redisCache}, nil + client.Cache = NewRedisCache(options, opts.TTL) + return client, nil } // standalone mode - fsCache, err := NewFSCache(Dir()) + var err error + client.Cache, err = NewFSCache(dir) if err != nil { return nil, xerrors.Errorf("unable to initialize fs cache: %w", err) } - return &Client{Cache: fsCache}, nil + return client, nil } // Reset resets the cache -func (c *Client) Reset() (err error) { - if err := c.ClearDB(); err != nil { - return xerrors.Errorf("failed to clear the database: %w", err) - } - if err := c.ClearArtifacts(); err != nil { - return xerrors.Errorf("failed to clear the artifact cache: %w", err) +func (c *Client) Reset() error { + log.Info("Removing all caches...") + if err := c.Clear(); err != nil { + return xerrors.Errorf("failed to remove the cache: %w", err) } - return nil -} - -// ClearDB clears the DB cache -func (c *Client) ClearDB() (err error) { - log.Info("Removing DB file...") - if err = os.RemoveAll(Dir()); err != nil { - return xerrors.Errorf("failed to remove the directory (%s) : %w", Dir(), err) + if err := os.RemoveAll(c.dir); err != nil { + return xerrors.Errorf("failed to remove the directory (%s) : %w", c.dir, err) } return nil } diff --git a/pkg/cache/client_test.go b/pkg/cache/client_test.go index f22ce4f93e2f..e41e84034a27 100644 --- a/pkg/cache/client_test.go +++ b/pkg/cache/client_test.go @@ -1,6 +1,8 @@ package cache_test import ( + "os" + "path/filepath" "testing" "time" @@ -127,3 +129,51 @@ func TestRedisOptions_BackendMasked(t *testing.T) { }) } } + +func TestClient_Reset(t *testing.T) { + // Create a temporary directory for testing + tempDir := t.TempDir() + + // Create test files and subdirectories + subDir := filepath.Join(tempDir, "subdir") + err := os.MkdirAll(subDir, 0755) + require.NoError(t, err) + + testFile := filepath.Join(tempDir, "testfile.txt") + err = os.WriteFile(testFile, []byte("test content"), 0644) + require.NoError(t, err) + + // Create a cache client + client, err := cache.NewClient(tempDir, cache.Options{Type: cache.TypeFS}) + require.NoError(t, err) + + // Call Reset method + err = client.Reset() + require.NoError(t, err) + + // Verify that the subdirectory no longer exists + require.NoDirExists(t, subDir, "Subdirectory should not exist after Reset") + + // Verify that the test file no longer exists + require.NoFileExists(t, testFile, "Test file should not exist after Reset") + + // Verify that the cache directory no longer exists + require.NoDirExists(t, tempDir, "Cache directory should not exist after Reset") +} + +func TestClient_ClearArtifacts(t *testing.T) { + // Create a temporary directory for testing + tempDir := t.TempDir() + + // Create a client + client, err := cache.NewClient(tempDir, cache.Options{Type: cache.TypeFS}) + require.NoError(t, err) + + require.FileExists(t, filepath.Join(tempDir, "fanal", "fanal.db"), "Database file should exist") + + // Call ClearArtifacts method + err = client.ClearArtifacts() + require.NoError(t, err) + + require.NoDirExists(t, filepath.Join(tempDir, "fanal"), "Artifact cache should not exist after ClearArtifacts") +} diff --git a/pkg/cache/dir.go b/pkg/cache/dir.go index b687017c9faf..2a67d269bdfe 100644 --- a/pkg/cache/dir.go +++ b/pkg/cache/dir.go @@ -5,26 +5,11 @@ import ( "path/filepath" ) -var cacheDir string - -// defaultDir returns/creates the cache-dir to be used for trivy operations -func defaultDir() string { +// DefaultDir returns/creates the cache-dir to be used for trivy operations +func DefaultDir() string { tmpDir, err := os.UserCacheDir() if err != nil { tmpDir = os.TempDir() } return filepath.Join(tmpDir, "trivy") } - -// Dir returns the directory used for caching -func Dir() string { - if cacheDir == "" { - return defaultDir() - } - return cacheDir -} - -// SetDir sets the trivy cache dir -func SetDir(dir string) { - cacheDir = dir -} diff --git a/pkg/commands/app.go b/pkg/commands/app.go index e01bc53e1ea0..90c737a040bb 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -110,10 +110,9 @@ func NewApp() *cobra.Command { func loadPluginCommands() []*cobra.Command { ctx := context.Background() - manager := plugin.NewManager() var commands []*cobra.Command - plugins, err := manager.LoadAll(ctx) + plugins, err := plugin.NewManager().LoadAll(ctx) if err != nil { log.DebugContext(ctx, "No plugins loaded") return nil diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 3a22f762c71c..65bfd455321b 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -350,12 +350,11 @@ func (r *runner) initCache(opts flag.Options) error { } // standalone mode - cache.SetDir(opts.CacheDir) - cacheClient, err := cache.NewClient(opts.CacheOptions.CacheBackendOptions) + cacheClient, err := cache.NewClient(opts.CacheDir, opts.CacheOptions.CacheBackendOptions) if err != nil { return xerrors.Errorf("unable to initialize the cache: %w", err) } - log.Debug("Cache dir", log.String("dir", cache.Dir())) + log.Debug("Cache dir", log.String("dir", opts.CacheDir)) if opts.Reset { defer cacheClient.Close() @@ -366,7 +365,7 @@ func (r *runner) initCache(opts flag.Options) error { } if opts.ResetChecksBundle { - c, err := policy.NewClient(cache.Dir(), true, opts.MisconfOptions.ChecksBundleRepository) + c, err := policy.NewClient(opts.CacheDir, true, opts.MisconfOptions.ChecksBundleRepository) if err != nil { return xerrors.Errorf("failed to instantiate check client: %w", err) } diff --git a/pkg/commands/server/run.go b/pkg/commands/server/run.go index eeef097990ce..f2957b75dbb6 100644 --- a/pkg/commands/server/run.go +++ b/pkg/commands/server/run.go @@ -19,16 +19,15 @@ func Run(ctx context.Context, opts flag.Options) (err error) { log.InitLogger(opts.Debug, opts.Quiet) // configure cache dir - cache.SetDir(opts.CacheDir) - cacheClient, err := cache.NewClient(opts.CacheOptions.CacheBackendOptions) + cacheClient, err := cache.NewClient(opts.CacheDir, opts.CacheOptions.CacheBackendOptions) if err != nil { return xerrors.Errorf("server cache error: %w", err) } defer cacheClient.Close() - log.Debug("Cache", log.String("dir", cache.Dir())) + log.Debug("Cache", log.String("dir", opts.CacheDir)) if opts.Reset { - return cacheClient.ClearDB() + return cacheClient.Reset() } // download the database file diff --git a/pkg/flag/global_flags.go b/pkg/flag/global_flags.go index eb34e2f589dc..ef19a09dd4e8 100644 --- a/pkg/flag/global_flags.go +++ b/pkg/flag/global_flags.go @@ -55,7 +55,7 @@ var ( CacheDirFlag = Flag[string]{ Name: "cache-dir", ConfigName: "cache.dir", - Default: cache.Dir(), + Default: cache.DefaultDir(), Usage: "cache directory", Persistent: true, } diff --git a/pkg/oci/artifact_test.go b/pkg/oci/artifact_test.go index 479c607af425..ddfe0304cc63 100644 --- a/pkg/oci/artifact_test.go +++ b/pkg/oci/artifact_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/cache" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/oci" ) @@ -97,7 +96,6 @@ func TestArtifact_Download(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tempDir := t.TempDir() - cache.SetDir(tempDir) // Mock image img := new(fakei.FakeImage) diff --git a/pkg/plugin/index_test.go b/pkg/plugin/index_test.go index e420dfaa6d08..4fbd4c9411aa 100644 --- a/pkg/plugin/index_test.go +++ b/pkg/plugin/index_test.go @@ -12,13 +12,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/plugin" ) func TestManager_Update(t *testing.T) { tempDir := t.TempDir() - cache.SetDir(tempDir) + t.Setenv("XDG_DATA_HOME", tempDir) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := w.Write([]byte(`this is index`)) @@ -30,7 +29,7 @@ func TestManager_Update(t *testing.T) { err := manager.Update(context.Background()) require.NoError(t, err) - indexPath := filepath.Join(tempDir, "plugin", "index.yaml") + indexPath := filepath.Join(tempDir, ".trivy", "plugins", "index.yaml") assert.FileExists(t, indexPath) b, err := os.ReadFile(indexPath) @@ -73,7 +72,7 @@ bar A bar plugin } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cache.SetDir(tt.dir) + t.Setenv("XDG_DATA_HOME", tt.dir) var got bytes.Buffer m := plugin.NewManager(plugin.WithWriter(&got)) diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 70ae3758fbdc..a3a806f8f42b 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -14,7 +14,6 @@ import ( "gopkg.in/yaml.v3" "github.com/aquasecurity/go-version/pkg/semver" - "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/downloader" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" @@ -59,12 +58,13 @@ type Manager struct { } func NewManager(opts ...ManagerOption) *Manager { + root := filepath.Join(fsutils.HomeDir(), pluginsRelativeDir) m := &Manager{ w: os.Stdout, indexURL: indexURL, logger: log.WithPrefix("plugin"), - pluginRoot: filepath.Join(fsutils.HomeDir(), pluginsRelativeDir), - indexPath: filepath.Join(cache.Dir(), "plugin", "index.yaml"), + pluginRoot: root, + indexPath: filepath.Join(root, "index.yaml"), } for _, opt := range opts { opt(m) diff --git a/pkg/plugin/manager_unix_test.go b/pkg/plugin/manager_unix_test.go index dd59109f94ee..728d3c7cf041 100644 --- a/pkg/plugin/manager_unix_test.go +++ b/pkg/plugin/manager_unix_test.go @@ -20,11 +20,11 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/internal/gittest" - "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/clock" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/plugin" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) func setupGitRepository(t *testing.T, repo, dir string) *httptest.Server { @@ -200,7 +200,11 @@ func TestManager_Install(t *testing.T) { t.Setenv("XDG_DATA_HOME", dst) // For plugin index - cache.SetDir("testdata") + pluginDir := filepath.Join(dst, ".trivy", "plugins") + err := os.MkdirAll(pluginDir, 0755) + require.NoError(t, err) + _, err = fsutils.CopyFile("testdata/.trivy/plugins/index.yaml", filepath.Join(pluginDir, "index.yaml")) + require.NoError(t, err) if tt.installed != nil { setupInstalledPlugin(t, dst, *tt.installed) diff --git a/pkg/plugin/testdata/plugin/index.yaml b/pkg/plugin/testdata/.trivy/plugins/index.yaml similarity index 100% rename from pkg/plugin/testdata/plugin/index.yaml rename to pkg/plugin/testdata/.trivy/plugins/index.yaml From 979e118a9e0ca8943bef9143f492d7eb1fd4d863 Mon Sep 17 00:00:00 2001 From: simar7 <1254783+simar7@users.noreply.github.com> Date: Mon, 24 Jun 2024 23:57:16 -0600 Subject: [PATCH 186/352] feat(aws)!: Remove aws subcommand (#6995) --- pkg/commands/app.go | 18 +++++++------- pkg/flag/cloud_flags.go | 55 ----------------------------------------- pkg/flag/options.go | 12 --------- 3 files changed, 9 insertions(+), 76 deletions(-) delete mode 100644 pkg/flag/cloud_flags.go diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 90c737a040bb..97ff44dc392a 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -93,7 +93,6 @@ func NewApp() *cobra.Command { NewKubernetesCommand(globalFlags), NewSBOMCommand(globalFlags), NewVersionCommand(globalFlags), - NewAWSCommand(), NewVMCommand(globalFlags), ) @@ -105,6 +104,15 @@ func NewApp() *cobra.Command { rootCmd.AddCommand(plugins...) } + // TODO(simar7): Only for backwards support guidance, delete the subcommand after a while. + if cmd, _, _ := rootCmd.Find([]string{"aws"}); cmd == cmd.Root() { // "trivy aws" not installed + rootCmd.AddCommand(&cobra.Command{ + Hidden: true, + Long: "Trivy AWS is now available as an optional plugin. See github.com/aquasecurity/trivy-aws for details.", + Use: "aws", + }) + } + return rootCmd } @@ -1014,14 +1022,6 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return cmd } -func NewAWSCommand() *cobra.Command { - cmd := &cobra.Command{ - Deprecated: "Trivy AWS is now available as an optional plugin. See github.com/aquasecurity/trivy-aws for details", - Use: "aws [flags]", - } - return cmd -} - func NewVMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { vmFlags := &flag.Flags{ GlobalFlagGroup: globalFlags, diff --git a/pkg/flag/cloud_flags.go b/pkg/flag/cloud_flags.go deleted file mode 100644 index fd96c206d496..000000000000 --- a/pkg/flag/cloud_flags.go +++ /dev/null @@ -1,55 +0,0 @@ -package flag - -import "time" - -var ( - cloudUpdateCacheFlag = Flag[bool]{ - Name: "update-cache", - ConfigName: "cloud.update-cache", - Usage: "Update the cache for the applicable cloud provider instead of using cached results.", - } - cloudMaxCacheAgeFlag = Flag[time.Duration]{ - Name: "max-cache-age", - ConfigName: "cloud.max-cache-age", - Default: time.Hour * 24, - Usage: "The maximum age of the cloud cache. Cached data will be required from the cloud provider if it is older than this.", - } -) - -type CloudFlagGroup struct { - UpdateCache *Flag[bool] - MaxCacheAge *Flag[time.Duration] -} - -type CloudOptions struct { - MaxCacheAge time.Duration - UpdateCache bool -} - -func NewCloudFlagGroup() *CloudFlagGroup { - return &CloudFlagGroup{ - UpdateCache: cloudUpdateCacheFlag.Clone(), - MaxCacheAge: cloudMaxCacheAgeFlag.Clone(), - } -} - -func (f *CloudFlagGroup) Name() string { - return "Cloud" -} - -func (f *CloudFlagGroup) Flags() []Flagger { - return []Flagger{ - f.UpdateCache, - f.MaxCacheAge, - } -} - -func (f *CloudFlagGroup) ToOptions() (CloudOptions, error) { - if err := parseFlags(f); err != nil { - return CloudOptions{}, err - } - return CloudOptions{ - UpdateCache: f.UpdateCache.Value(), - MaxCacheAge: f.MaxCacheAge.Value(), - }, nil -} diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 3777bed507fb..db042ff802a1 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -301,7 +301,6 @@ type Flags struct { GlobalFlagGroup *GlobalFlagGroup AWSFlagGroup *AWSFlagGroup CacheFlagGroup *CacheFlagGroup - CloudFlagGroup *CloudFlagGroup DBFlagGroup *DBFlagGroup ImageFlagGroup *ImageFlagGroup K8sFlagGroup *K8sFlagGroup @@ -324,7 +323,6 @@ type Options struct { GlobalOptions AWSOptions CacheOptions - CloudOptions DBOptions ImageOptions K8sOptions @@ -527,9 +525,6 @@ func (f *Flags) groups() []FlagGroup { if f.RegoFlagGroup != nil { groups = append(groups, f.RegoFlagGroup) } - if f.CloudFlagGroup != nil { - groups = append(groups, f.CloudFlagGroup) - } if f.AWSFlagGroup != nil { groups = append(groups, f.AWSFlagGroup) } @@ -619,13 +614,6 @@ func (f *Flags) ToOptions(args []string) (Options, error) { } } - if f.CloudFlagGroup != nil { - opts.CloudOptions, err = f.CloudFlagGroup.ToOptions() - if err != nil { - return Options{}, xerrors.Errorf("cloud flag error: %w", err) - } - } - if f.CacheFlagGroup != nil { opts.CacheOptions, err = f.CacheFlagGroup.ToOptions() if err != nil { From de201dc77203a500dc0cba89301d826df35bd48d Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Tue, 25 Jun 2024 11:23:14 +0400 Subject: [PATCH 187/352] chore: use `!` for breaking changes (#6994) Signed-off-by: knqyf263 --- .github/workflows/semantic-pr.yaml | 1 - docs/community/contribute/pr.md | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/semantic-pr.yaml b/.github/workflows/semantic-pr.yaml index b136c9ae5ca9..4005520f1949 100644 --- a/.github/workflows/semantic-pr.yaml +++ b/.github/workflows/semantic-pr.yaml @@ -29,7 +29,6 @@ jobs: chore revert release - BREAKING scopes: | vuln diff --git a/docs/community/contribute/pr.md b/docs/community/contribute/pr.md index 0f7cc70ec22e..0324bb6f8059 100644 --- a/docs/community/contribute/pr.md +++ b/docs/community/contribute/pr.md @@ -185,12 +185,20 @@ others: The `` can be empty (e.g. if the change is a global or difficult to assign to a single component), in which case the parentheses are omitted. +**Breaking changes** + +A PR, introducing a breaking API change, needs to append a `!` after the type/scope. + ### Example titles ``` feat(alma): add support for AlmaLinux ``` +``` +feat(vuln)!: delete the existing CLI flag +``` + ``` fix(oracle): handle advisories with ksplice versions ``` From 8d0ae1f5de72d92a043dcd6b7c164d30e51b6047 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Tue, 25 Jun 2024 13:06:27 +0400 Subject: [PATCH 188/352] feat!: add clean subcommand (#6993) Signed-off-by: knqyf263 Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Co-authored-by: DmitriyLewen --- .github/DISCUSSION_TEMPLATE/bugs.yml | 2 +- docs/docs/configuration/cache.md | 13 +- docs/docs/configuration/db.md | 6 +- .../references/configuration/cli/trivy.md | 1 + .../configuration/cli/trivy_clean.md | 50 +++++++ .../configuration/cli/trivy_config.md | 2 - .../configuration/cli/trivy_filesystem.md | 3 - .../configuration/cli/trivy_image.md | 3 - .../configuration/cli/trivy_kubernetes.md | 3 - .../configuration/cli/trivy_repository.md | 3 - .../configuration/cli/trivy_rootfs.md | 3 - .../configuration/cli/trivy_sbom.md | 2 - .../configuration/cli/trivy_server.md | 2 - .../references/configuration/cli/trivy_vm.md | 3 - docs/docs/references/troubleshooting.md | 4 +- docs/tutorials/integrations/gitlab-ci.md | 2 - mkdocs.yml | 1 + pkg/cache/client.go | 39 +---- pkg/cache/client_test.go | 50 ------- pkg/commands/app.go | 46 +++++- pkg/commands/artifact/run.go | 34 +---- pkg/commands/clean/run.go | 102 +++++++++++++ pkg/commands/clean/run_test.go | 135 ++++++++++++++++++ pkg/commands/server/run.go | 6 +- pkg/db/db.go | 8 ++ pkg/flag/cache_flags.go | 2 + pkg/flag/clean_flags.go | 84 +++++++++++ pkg/flag/db_flags.go | 10 +- pkg/flag/db_flags_test.go | 19 --- pkg/flag/misconf_flags.go | 2 + pkg/flag/options.go | 29 +++- pkg/flag/sbom_flags.go | 22 +-- pkg/flag/scan_flags.go | 2 +- pkg/javadb/client.go | 12 +- pkg/policy/policy.go | 1 - 35 files changed, 496 insertions(+), 210 deletions(-) create mode 100644 docs/docs/references/configuration/cli/trivy_clean.md create mode 100644 pkg/commands/clean/run.go create mode 100644 pkg/commands/clean/run_test.go create mode 100644 pkg/flag/clean_flags.go diff --git a/.github/DISCUSSION_TEMPLATE/bugs.yml b/.github/DISCUSSION_TEMPLATE/bugs.yml index 711b85c88334..59d0ee088365 100644 --- a/.github/DISCUSSION_TEMPLATE/bugs.yml +++ b/.github/DISCUSSION_TEMPLATE/bugs.yml @@ -116,7 +116,7 @@ body: label: Checklist description: Have you tried the following? options: - - label: Run `trivy image --reset` + - label: Run `trivy clean --all` - label: Read [the troubleshooting](https://aquasecurity.github.io/trivy/latest/docs/references/troubleshooting/) - type: markdown attributes: diff --git a/docs/docs/configuration/cache.md b/docs/docs/configuration/cache.md index ff3a373c22ce..719b0deced17 100644 --- a/docs/docs/configuration/cache.md +++ b/docs/docs/configuration/cache.md @@ -9,24 +9,25 @@ The cache directory includes The cache option is common to all scanners. ## Clear Caches -The `--clear-cache` option removes caches. - -**The scan is not performed.** +`trivy clean` subcommand removes caches. ``` -$ trivy image --clear-cache +$ trivy clean --scan-cache ```
Result ``` -2019-11-15T15:13:26.209+0200 INFO Reopening vulnerability DB -2019-11-15T15:13:26.209+0200 INFO Removing image caches... +2024-06-21T21:58:21+04:00 INFO Removing scan cache... ```
+If you want to delete cached vulnerability databases, use `--vuln-db`. +You can also delete all caches with `--all`. +See `trivy clean --help` for details. + ## Cache Directory Specify where the cache is stored with `--cache-dir`. diff --git a/docs/docs/configuration/db.md b/docs/docs/configuration/db.md index 1479a79183a1..f6525fb61568 100644 --- a/docs/docs/configuration/db.md +++ b/docs/docs/configuration/db.md @@ -78,8 +78,10 @@ $ trivy image --java-db-repository registry.gitlab.com/gitlab-org/security-produ `java-db-registry:latest` => `java-db-registry:latest`, but `java-db-registry` => `java-db-registry:1`. ## Remove DBs -The `--reset` flag removes all caches and databases. +"trivy clean" command removes caches and databases. ``` -$ trivy image --reset +$ trivy clean --vuln-db --java-db +2024-06-24T11:42:31+06:00 INFO Removing vulnerability database... +2024-06-24T11:42:31+06:00 INFO Removing Java database... ``` \ No newline at end of file diff --git a/docs/docs/references/configuration/cli/trivy.md b/docs/docs/references/configuration/cli/trivy.md index 500a367fff8a..2992bc0faa9b 100644 --- a/docs/docs/references/configuration/cli/trivy.md +++ b/docs/docs/references/configuration/cli/trivy.md @@ -43,6 +43,7 @@ trivy [global flags] command [flags] target ### SEE ALSO +* [trivy clean](trivy_clean.md) - Remove cached files * [trivy config](trivy_config.md) - Scan config files for misconfigurations * [trivy convert](trivy_convert.md) - Convert Trivy JSON report into a different format * [trivy filesystem](trivy_filesystem.md) - Scan local filesystem diff --git a/docs/docs/references/configuration/cli/trivy_clean.md b/docs/docs/references/configuration/cli/trivy_clean.md new file mode 100644 index 000000000000..7a997bf7b581 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_clean.md @@ -0,0 +1,50 @@ +## trivy clean + +Remove cached files + +``` +trivy clean [flags] +``` + +### Examples + +``` + # Remove all caches + $ trivy clean --all + + # Remove scan cache + $ trivy clean --scan-cache + + # Remove vulnerability database + $ trivy clean --vuln-db + +``` + +### Options + +``` + -a, --all remove all caches + --checks-bundle remove checks bundle + -h, --help help for clean + --java-db remove Java database + --scan-cache remove scan cache (container and VM image analysis results) + --vuln-db remove vulnerability database +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 993570f1587b..f10788347129 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -14,7 +14,6 @@ trivy config [flags] DIR --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") - --clear-cache clear image caches without scanning --compliance string compliance report to generate --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded @@ -45,7 +44,6 @@ trivy config [flags] DIR --redis-tls enable redis TLS with public certificates, if using redis as cache backend --registry-token string registry token --report string specify a compliance report format for the output (all,summary) (default "all") - --reset-checks-bundle remove checks bundle -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) --skip-check-update skip fetching rego check updates --skip-dirs strings specify the directories or glob patterns to skip diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 4d90f4e87e63..d024b2738d92 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -24,7 +24,6 @@ trivy filesystem [flags] PATH --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") - --clear-cache clear image caches without scanning --compliance string compliance report to generate --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded @@ -71,8 +70,6 @@ trivy filesystem [flags] PATH --registry-token string registry token --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --report string specify a compliance report format for the output (all,summary) (default "all") - --reset remove all caches and database - --reset-checks-bundle remove checks bundle --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index c61c6b648d7c..251ea85b6f33 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -38,7 +38,6 @@ trivy image [flags] IMAGE_NAME --cache-ttl duration cache TTL when using redis as cache backend --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") - --clear-cache clear image caches without scanning --compliance string compliance report to generate (docker-cis) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded @@ -92,8 +91,6 @@ trivy image [flags] IMAGE_NAME --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --removed-pkgs detect vulnerabilities of removed packages (only for Alpine) --report string specify a format for the compliance report. (all,summary) (default "summary") - --reset remove all caches and database - --reset-checks-bundle remove checks bundle --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 54dc2db07f75..a39275750bde 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -34,7 +34,6 @@ trivy kubernetes [flags] [CONTEXT] --cache-ttl duration cache TTL when using redis as cache backend --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") - --clear-cache clear image caches without scanning --compliance string compliance report to generate (k8s-nsa,k8s-cis,k8s-pss-baseline,k8s-pss-restricted) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded @@ -87,8 +86,6 @@ trivy kubernetes [flags] [CONTEXT] --registry-token string registry token --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --report string specify a report format for the output (all,summary) (default "all") - --reset remove all caches and database - --reset-checks-bundle remove checks bundle --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,rbac) (default [vuln,misconfig,secret,rbac]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 4f73c0d4e13d..963727554494 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -24,7 +24,6 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") - --clear-cache clear image caches without scanning --commit string pass the commit hash to be scanned --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded @@ -70,8 +69,6 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --redis-tls enable redis TLS with public certificates, if using redis as cache backend --registry-token string registry token --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") - --reset remove all caches and database - --reset-checks-bundle remove checks bundle --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 6f264b5c2362..ed287f689928 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -27,7 +27,6 @@ trivy rootfs [flags] ROOTDIR --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") - --clear-cache clear image caches without scanning --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded --custom-headers strings custom headers in client mode @@ -72,8 +71,6 @@ trivy rootfs [flags] ROOTDIR --redis-tls enable redis TLS with public certificates, if using redis as cache backend --registry-token string registry token --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") - --reset remove all caches and database - --reset-checks-bundle remove checks bundle --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index d5c4030d7281..b5681576c830 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -22,7 +22,6 @@ trivy sbom [flags] SBOM_PATH ``` --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend - --clear-cache clear image caches without scanning --compliance string compliance report to generate --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") @@ -49,7 +48,6 @@ trivy sbom [flags] SBOM_PATH --redis-key string redis key file location, if using redis as cache backend --redis-tls enable redis TLS with public certificates, if using redis as cache backend --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") - --reset remove all caches and database --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,license) (default [vuln]) --server string server address in client mode diff --git a/docs/docs/references/configuration/cli/trivy_server.md b/docs/docs/references/configuration/cli/trivy_server.md index d888034c34bf..d8d711092e5e 100644 --- a/docs/docs/references/configuration/cli/trivy_server.md +++ b/docs/docs/references/configuration/cli/trivy_server.md @@ -22,7 +22,6 @@ trivy server [flags] ``` --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend - --clear-cache clear image caches without scanning --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --download-db-only download/update vulnerability database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable @@ -36,7 +35,6 @@ trivy server [flags] --redis-key string redis key file location, if using redis as cache backend --redis-tls enable redis TLS with public certificates, if using redis as cache backend --registry-token string registry token - --reset remove all caches and database --skip-db-update skip updating vulnerability database --token string for authentication in client/server mode --token-header string specify a header name for token in client/server mode (default "Trivy-Token") diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 51b7ad43cf38..67be823ed6b6 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -24,7 +24,6 @@ trivy vm [flags] VM_IMAGE --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") - --clear-cache clear image caches without scanning --compliance string compliance report to generate --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") @@ -62,8 +61,6 @@ trivy vm [flags] VM_IMAGE --redis-key string redis key file location, if using redis as cache backend --redis-tls enable redis TLS with public certificates, if using redis as cache backend --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") - --reset remove all caches and database - --reset-checks-bundle remove checks bundle --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,misconfig,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/docs/docs/references/troubleshooting.md b/docs/docs/references/troubleshooting.md index 7d2c3258aa2c..d271882c5ecb 100644 --- a/docs/docs/references/troubleshooting.md +++ b/docs/docs/references/troubleshooting.md @@ -264,10 +264,10 @@ $ brew install aquasecurity/trivy/trivy ## Others ### Unknown error -Try again with `--reset` option: +Try again after running `trivy clean --all`: ``` -$ trivy image --reset +$ trivy clean --all ``` [air-gapped]: ../advanced/air-gap.md diff --git a/docs/tutorials/integrations/gitlab-ci.md b/docs/tutorials/integrations/gitlab-ci.md index dbfe46d1ca4d..8b4e8c34e7bb 100644 --- a/docs/tutorials/integrations/gitlab-ci.md +++ b/docs/tutorials/integrations/gitlab-ci.md @@ -85,8 +85,6 @@ container_scanning: FULL_IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG script: - trivy --version - # cache cleanup is needed when scanning images with the same tags, it does not remove the database - - time trivy image --clear-cache # update vulnerabilities db - time trivy image --download-db-only # Builds report and puts it in the default workdir $CI_PROJECT_DIR, so `artifacts:` can take it from there diff --git a/mkdocs.yml b/mkdocs.yml index 042ffe789d6d..2222a30220fb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -152,6 +152,7 @@ nav: - Configuration: - CLI: - Overview: docs/references/configuration/cli/trivy.md + - Clean: docs/references/configuration/cli/trivy_clean.md - Config: docs/references/configuration/cli/trivy_config.md - Convert: docs/references/configuration/cli/trivy_convert.md - Filesystem: docs/references/configuration/cli/trivy_filesystem.md diff --git a/pkg/cache/client.go b/pkg/cache/client.go index 7b18827016d5..ab9dd4799428 100644 --- a/pkg/cache/client.go +++ b/pkg/cache/client.go @@ -20,11 +20,6 @@ const ( TypeRedis Type = "redis" ) -type Client struct { - dir string - Cache -} - type Type string type Options struct { @@ -115,9 +110,8 @@ func NewType(backend string) (Type, error) { } } -// NewClient returns a new cache client -func NewClient(dir string, opts Options) (*Client, error) { - client := &Client{dir: dir} +// New returns a new cache client +func New(dir string, opts Options) (Cache, error) { if opts.Type == TypeRedis { log.Info("Redis cache", log.String("url", opts.Redis.BackendMasked())) options, err := redis.ParseURL(opts.Redis.Backend) @@ -142,38 +136,15 @@ func NewClient(dir string, opts Options) (*Client, error) { } } - client.Cache = NewRedisCache(options, opts.TTL) - return client, nil + return NewRedisCache(options, opts.TTL), nil } // standalone mode - var err error - client.Cache, err = NewFSCache(dir) + fsCache, err := NewFSCache(dir) if err != nil { return nil, xerrors.Errorf("unable to initialize fs cache: %w", err) } - return client, nil -} - -// Reset resets the cache -func (c *Client) Reset() error { - log.Info("Removing all caches...") - if err := c.Clear(); err != nil { - return xerrors.Errorf("failed to remove the cache: %w", err) - } - if err := os.RemoveAll(c.dir); err != nil { - return xerrors.Errorf("failed to remove the directory (%s) : %w", c.dir, err) - } - return nil -} - -// ClearArtifacts clears the artifact cache -func (c *Client) ClearArtifacts() error { - log.Info("Removing artifact caches...") - if err := c.Clear(); err != nil { - return xerrors.Errorf("failed to remove the cache: %w", err) - } - return nil + return fsCache, nil } // GetTLSConfig gets tls config from CA, Cert and Key file diff --git a/pkg/cache/client_test.go b/pkg/cache/client_test.go index e41e84034a27..f22ce4f93e2f 100644 --- a/pkg/cache/client_test.go +++ b/pkg/cache/client_test.go @@ -1,8 +1,6 @@ package cache_test import ( - "os" - "path/filepath" "testing" "time" @@ -129,51 +127,3 @@ func TestRedisOptions_BackendMasked(t *testing.T) { }) } } - -func TestClient_Reset(t *testing.T) { - // Create a temporary directory for testing - tempDir := t.TempDir() - - // Create test files and subdirectories - subDir := filepath.Join(tempDir, "subdir") - err := os.MkdirAll(subDir, 0755) - require.NoError(t, err) - - testFile := filepath.Join(tempDir, "testfile.txt") - err = os.WriteFile(testFile, []byte("test content"), 0644) - require.NoError(t, err) - - // Create a cache client - client, err := cache.NewClient(tempDir, cache.Options{Type: cache.TypeFS}) - require.NoError(t, err) - - // Call Reset method - err = client.Reset() - require.NoError(t, err) - - // Verify that the subdirectory no longer exists - require.NoDirExists(t, subDir, "Subdirectory should not exist after Reset") - - // Verify that the test file no longer exists - require.NoFileExists(t, testFile, "Test file should not exist after Reset") - - // Verify that the cache directory no longer exists - require.NoDirExists(t, tempDir, "Cache directory should not exist after Reset") -} - -func TestClient_ClearArtifacts(t *testing.T) { - // Create a temporary directory for testing - tempDir := t.TempDir() - - // Create a client - client, err := cache.NewClient(tempDir, cache.Options{Type: cache.TypeFS}) - require.NoError(t, err) - - require.FileExists(t, filepath.Join(tempDir, "fanal", "fanal.db"), "Database file should exist") - - // Call ClearArtifacts method - err = client.ClearArtifacts() - require.NoError(t, err) - - require.NoDirExists(t, filepath.Join(tempDir, "fanal"), "Artifact cache should not exist after ClearArtifacts") -} diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 97ff44dc392a..0cd331e7b9b7 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -14,6 +14,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/commands/artifact" + "github.com/aquasecurity/trivy/pkg/commands/clean" "github.com/aquasecurity/trivy/pkg/commands/convert" "github.com/aquasecurity/trivy/pkg/commands/server" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -94,6 +95,7 @@ func NewApp() *cobra.Command { NewSBOMCommand(globalFlags), NewVersionCommand(globalFlags), NewVMCommand(globalFlags), + NewCleanCommand(globalFlags), ) if plugins := loadPluginCommands(); len(plugins) > 0 { @@ -1160,6 +1162,47 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return cmd } +func NewCleanCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + cleanFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + CleanFlagGroup: flag.NewCleanFlagGroup(), + } + cmd := &cobra.Command{ + Use: "clean [flags]", + GroupID: groupUtility, + Short: "Remove cached files", + Example: ` # Remove all caches + $ trivy clean --all + + # Remove scan cache + $ trivy clean --scan-cache + + # Remove vulnerability database + $ trivy clean --vuln-db +`, + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := cleanFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + opts, err := cleanFlags.ToOptions(args) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + + return clean.Run(cmd.Context(), opts) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + cleanFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, cleanFlags.Usages(cmd))) + + return cmd +} func NewVersionCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { var versionFormat string cmd := &cobra.Command{ @@ -1199,7 +1242,8 @@ func showVersion(cacheDir, outputFormat string, w io.Writer) error { } func validateArgs(cmd *cobra.Command, args []string) error { - // '--clear-cache', '--download-db-only', '--download-java-db-only', '--reset', '--reset-checks-bundle' and '--generate-default-config' don't conduct the subsequent scanning + // '--clear-cache' (removed), '--download-db-only', '--download-java-db-only', '--reset' (removed), + // '--reset-checks-bundle' (removed) and '--generate-default-config' don't conduct the subsequent scanning if viper.GetBool(flag.ClearCacheFlag.ConfigName) || viper.GetBool(flag.DownloadDBOnlyFlag.ConfigName) || viper.GetBool(flag.ResetFlag.ConfigName) || viper.GetBool(flag.GenerateDefaultConfigFlag.ConfigName) || viper.GetBool(flag.DownloadJavaDBOnlyFlag.ConfigName) || viper.GetBool(flag.ResetChecksBundleFlag.ConfigName) { diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 65bfd455321b..bb2e144c5050 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -24,7 +24,6 @@ import ( "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/misconf" "github.com/aquasecurity/trivy/pkg/module" - "github.com/aquasecurity/trivy/pkg/policy" pkgReport "github.com/aquasecurity/trivy/pkg/report" "github.com/aquasecurity/trivy/pkg/result" "github.com/aquasecurity/trivy/pkg/rpc/client" @@ -350,41 +349,14 @@ func (r *runner) initCache(opts flag.Options) error { } // standalone mode - cacheClient, err := cache.NewClient(opts.CacheDir, opts.CacheOptions.CacheBackendOptions) + cacheClient, err := cache.New(opts.CacheDir, opts.CacheOptions.CacheBackendOptions) if err != nil { return xerrors.Errorf("unable to initialize the cache: %w", err) } log.Debug("Cache dir", log.String("dir", opts.CacheDir)) - if opts.Reset { - defer cacheClient.Close() - if err = cacheClient.Reset(); err != nil { - return xerrors.Errorf("cache reset error: %w", err) - } - return SkipScan - } - - if opts.ResetChecksBundle { - c, err := policy.NewClient(opts.CacheDir, true, opts.MisconfOptions.ChecksBundleRepository) - if err != nil { - return xerrors.Errorf("failed to instantiate check client: %w", err) - } - if err := c.Clear(); err != nil { - return xerrors.Errorf("failed to remove the cache: %w", err) - } - return SkipScan - } - - if opts.ClearCache { - defer cacheClient.Close() - if err = cacheClient.ClearArtifacts(); err != nil { - return xerrors.Errorf("cache clear error: %w", err) - } - return SkipScan - } - - r.cache = cacheClient.Cache - r.localCache = cacheClient.Cache + r.cache = cacheClient + r.localCache = cacheClient return nil } diff --git a/pkg/commands/clean/run.go b/pkg/commands/clean/run.go new file mode 100644 index 000000000000..f2ac57539d54 --- /dev/null +++ b/pkg/commands/clean/run.go @@ -0,0 +1,102 @@ +package clean + +import ( + "context" + "os" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/cache" + "github.com/aquasecurity/trivy/pkg/db" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/javadb" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/policy" +) + +func Run(ctx context.Context, opts flag.Options) error { + ctx, cancel := context.WithTimeout(ctx, opts.Timeout) + defer cancel() + + if !opts.CleanAll && !opts.CleanScanCache && !opts.CleanVulnerabilityDB && !opts.CleanJavaDB && !opts.CleanChecksBundle { + return xerrors.New("no clean option is specified") + } + + if opts.CleanAll { + return cleanAll(ctx, opts) + } + + if opts.CleanScanCache { + if err := cleanScanCache(ctx, opts); err != nil { + return xerrors.Errorf("failed to remove scan cache : %w", err) + } + } + + if opts.CleanVulnerabilityDB { + if err := cleanVulnerabilityDB(ctx, opts); err != nil { + return xerrors.Errorf("vuln db clean error: %w", err) + } + } + + if opts.CleanJavaDB { + if err := cleanJavaDB(ctx, opts); err != nil { + return xerrors.Errorf("java db clean error: %w", err) + } + } + + if opts.CleanChecksBundle { + if err := cleanCheckBundle(opts); err != nil { + return xerrors.Errorf("check bundle clean error: %w", err) + } + } + return nil +} + +func cleanAll(ctx context.Context, opts flag.Options) error { + log.InfoContext(ctx, "Removing all caches...") + if err := os.RemoveAll(opts.CacheDir); err != nil { + return xerrors.Errorf("failed to remove the directory (%s) : %w", opts.CacheDir, err) + } + return nil +} + +func cleanScanCache(ctx context.Context, opts flag.Options) error { + log.InfoContext(ctx, "Removing scan cache...") + c, err := cache.New(opts.CacheDir, opts.CacheBackendOptions) + if err != nil { + return xerrors.Errorf("failed to instantiate cache client: %w", err) + } + if err = c.Clear(); err != nil { + return xerrors.Errorf("clear scan cache: %w", err) + } + return nil +} + +func cleanVulnerabilityDB(ctx context.Context, opts flag.Options) error { + log.InfoContext(ctx, "Removing vulnerability database...") + if err := db.NewClient(opts.CacheDir, true).Clear(ctx); err != nil { + return xerrors.Errorf("clear vulnerability database: %w", err) + + } + return nil +} + +func cleanJavaDB(ctx context.Context, opts flag.Options) error { + log.InfoContext(ctx, "Removing Java database...") + if err := javadb.Clear(ctx, opts.CacheDir); err != nil { + return xerrors.Errorf("clear Java database: %w", err) + } + return nil +} + +func cleanCheckBundle(opts flag.Options) error { + log.Info("Removing check bundle...") + c, err := policy.NewClient(opts.CacheDir, true, opts.MisconfOptions.ChecksBundleRepository) + if err != nil { + return xerrors.Errorf("failed to instantiate check client: %w", err) + } + if err := c.Clear(); err != nil { + return xerrors.Errorf("clear check bundle: %w", err) + } + return nil +} diff --git a/pkg/commands/clean/run_test.go b/pkg/commands/clean/run_test.go new file mode 100644 index 000000000000..a26fef86a572 --- /dev/null +++ b/pkg/commands/clean/run_test.go @@ -0,0 +1,135 @@ +package clean_test + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/commands/clean" + "github.com/aquasecurity/trivy/pkg/flag" +) + +func TestRun(t *testing.T) { + tests := []struct { + name string + cleanOpts flag.CleanOptions + wantErr bool + checkFunc func(*testing.T, string) + }{ + { + name: "clean all", + cleanOpts: flag.CleanOptions{ + CleanAll: true, + }, + wantErr: false, + checkFunc: func(t *testing.T, dir string) { + assert.NoDirExists(t, dir) + }, + }, + { + name: "clean scan cache", + cleanOpts: flag.CleanOptions{ + CleanScanCache: true, + }, + wantErr: false, + checkFunc: func(t *testing.T, dir string) { + assert.NoDirExists(t, filepath.Join(dir, "fanal")) + assert.DirExists(t, filepath.Join(dir, "db")) + assert.DirExists(t, filepath.Join(dir, "java-db")) + assert.DirExists(t, filepath.Join(dir, "policy")) + }, + }, + { + name: "clean vulnerability DB", + cleanOpts: flag.CleanOptions{ + CleanVulnerabilityDB: true, + }, + wantErr: false, + checkFunc: func(t *testing.T, dir string) { + assert.NoDirExists(t, filepath.Join(dir, "db")) + assert.DirExists(t, filepath.Join(dir, "fanal")) + assert.DirExists(t, filepath.Join(dir, "java-db")) + assert.DirExists(t, filepath.Join(dir, "policy")) + }, + }, + { + name: "clean Java DB", + cleanOpts: flag.CleanOptions{ + CleanJavaDB: true, + }, + wantErr: false, + checkFunc: func(t *testing.T, dir string) { + assert.NoDirExists(t, filepath.Join(dir, "java-db")) + assert.DirExists(t, filepath.Join(dir, "fanal")) + assert.DirExists(t, filepath.Join(dir, "db")) + assert.DirExists(t, filepath.Join(dir, "policy")) + }, + }, + { + name: "clean check bundle", + cleanOpts: flag.CleanOptions{ + CleanChecksBundle: true, + }, + wantErr: false, + checkFunc: func(t *testing.T, dir string) { + assert.NoDirExists(t, filepath.Join(dir, "policy")) + assert.DirExists(t, filepath.Join(dir, "fanal")) + assert.DirExists(t, filepath.Join(dir, "db")) + assert.DirExists(t, filepath.Join(dir, "java-db")) + }, + }, + { + name: "no clean option specified", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a temporary directory for testing + tempDir := t.TempDir() + + // Create test directories and files + createTestFiles(t, tempDir) + + opts := flag.Options{ + GlobalOptions: flag.GlobalOptions{ + CacheDir: tempDir, + }, + CleanOptions: tt.cleanOpts, + } + + err := clean.Run(context.Background(), opts) + + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + if tt.checkFunc != nil { + tt.checkFunc(t, tempDir) + } + }) + } +} + +func createTestFiles(t *testing.T, dir string) { + subdirs := []string{ + "fanal", + "db", + "java-db", + "policy", + } + for _, subdir := range subdirs { + err := os.MkdirAll(filepath.Join(dir, subdir), 0755) + require.NoError(t, err) + + testFile := filepath.Join(dir, subdir, "testfile.txt") + err = os.WriteFile(testFile, []byte("test content"), 0644) + require.NoError(t, err) + } +} diff --git a/pkg/commands/server/run.go b/pkg/commands/server/run.go index f2957b75dbb6..917f1b4aa459 100644 --- a/pkg/commands/server/run.go +++ b/pkg/commands/server/run.go @@ -19,17 +19,13 @@ func Run(ctx context.Context, opts flag.Options) (err error) { log.InitLogger(opts.Debug, opts.Quiet) // configure cache dir - cacheClient, err := cache.NewClient(opts.CacheDir, opts.CacheOptions.CacheBackendOptions) + cacheClient, err := cache.New(opts.CacheDir, opts.CacheOptions.CacheBackendOptions) if err != nil { return xerrors.Errorf("server cache error: %w", err) } defer cacheClient.Close() log.Debug("Cache", log.String("dir", opts.CacheDir)) - if opts.Reset { - return cacheClient.Reset() - } - // download the database file if err = operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, true, opts.SkipDBUpdate, opts.RegistryOpts()); err != nil { diff --git a/pkg/db/db.go b/pkg/db/db.go index a006404d2c83..e87277f93375 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "os" "time" "github.com/google/go-containerregistry/pkg/name" @@ -158,6 +159,13 @@ func (c *Client) Download(ctx context.Context, dst string, opt types.RegistryOpt return nil } +func (c *Client) Clear(ctx context.Context) error { + if err := os.RemoveAll(db.Dir(c.cacheDir)); err != nil { + return xerrors.Errorf("failed to remove vulnerability database: %w", err) + } + return nil +} + func (c *Client) updateDownloadedAt(ctx context.Context, dst string) error { log.Debug("Updating database metadata...") diff --git a/pkg/flag/cache_flags.go b/pkg/flag/cache_flags.go index 75b7a1837371..73a31fd2684d 100644 --- a/pkg/flag/cache_flags.go +++ b/pkg/flag/cache_flags.go @@ -18,10 +18,12 @@ import ( // cert: cert.pem // key: key.pem var ( + // Deprecated ClearCacheFlag = Flag[bool]{ Name: "clear-cache", ConfigName: "cache.clear", Usage: "clear image caches without scanning", + Removed: `Use "trivy clean --scan-cache" instead`, } CacheBackendFlag = Flag[string]{ Name: "cache-backend", diff --git a/pkg/flag/clean_flags.go b/pkg/flag/clean_flags.go new file mode 100644 index 000000000000..7a898c38ad63 --- /dev/null +++ b/pkg/flag/clean_flags.go @@ -0,0 +1,84 @@ +package flag + +var ( + CleanAll = Flag[bool]{ + Name: "all", + Shorthand: "a", + ConfigName: "clean.all", + Usage: "remove all caches", + } + CleanScanCache = Flag[bool]{ + Name: "scan-cache", + ConfigName: "clean.scan-cache", + Usage: "remove scan cache (container and VM image analysis results)", + } + CleanVulnerabilityDB = Flag[bool]{ + Name: "vuln-db", + ConfigName: "clean.vuln-db", + Usage: "remove vulnerability database", + } + CleanJavaDB = Flag[bool]{ + Name: "java-db", + ConfigName: "clean.java-db", + Usage: "remove Java database", + } + CleanChecksBundle = Flag[bool]{ + Name: "checks-bundle", + ConfigName: "clean.checks-bundle", + Usage: "remove checks bundle", + } +) + +type CleanFlagGroup struct { + CleanAll *Flag[bool] + CleanVulnerabilityDB *Flag[bool] + CleanJavaDB *Flag[bool] + CleanChecksBundle *Flag[bool] + CleanScanCache *Flag[bool] +} + +type CleanOptions struct { + CleanAll bool + CleanVulnerabilityDB bool + CleanJavaDB bool + CleanChecksBundle bool + CleanScanCache bool +} + +func NewCleanFlagGroup() *CleanFlagGroup { + return &CleanFlagGroup{ + CleanAll: CleanAll.Clone(), + CleanVulnerabilityDB: CleanVulnerabilityDB.Clone(), + CleanJavaDB: CleanJavaDB.Clone(), + CleanChecksBundle: CleanChecksBundle.Clone(), + CleanScanCache: CleanScanCache.Clone(), + } +} + +func (fg *CleanFlagGroup) Name() string { + return "Clean" +} + +func (fg *CleanFlagGroup) Flags() []Flagger { + return []Flagger{ + fg.CleanAll, + fg.CleanVulnerabilityDB, + fg.CleanJavaDB, + fg.CleanChecksBundle, + fg.CleanScanCache, + } +} + +func (fg *CleanFlagGroup) ToOptions() (CleanOptions, error) { + if err := parseFlags(fg); err != nil { + return CleanOptions{}, err + } + + return CleanOptions{ + CleanAll: fg.CleanAll.Value(), + CleanVulnerabilityDB: fg.CleanVulnerabilityDB.Value(), + CleanJavaDB: fg.CleanJavaDB.Value(), + CleanChecksBundle: fg.CleanChecksBundle.Value(), + CleanScanCache: fg.CleanScanCache.Value(), + }, nil +} diff --git a/pkg/flag/db_flags.go b/pkg/flag/db_flags.go index fd426ae9ccbb..37685b104204 100644 --- a/pkg/flag/db_flags.go +++ b/pkg/flag/db_flags.go @@ -12,10 +12,12 @@ import ( ) var ( + // Deprecated ResetFlag = Flag[bool]{ Name: "reset", ConfigName: "reset", Usage: "remove all caches and database", + Removed: `Use "trivy clean --all" instead.`, } DownloadDBOnlyFlag = Flag[bool]{ Name: "download-db-only", @@ -64,7 +66,7 @@ var ( Name: "light", ConfigName: "db.light", Usage: "deprecated", - Deprecated: true, + Deprecated: `This flag is ignored.`, } ) @@ -90,7 +92,6 @@ type DBOptions struct { NoProgress bool DBRepository name.Reference JavaDBRepository name.Reference - Light bool // deprecated } // NewDBFlagGroup returns a default DBFlagGroup @@ -135,7 +136,6 @@ func (f *DBFlagGroup) ToOptions() (DBOptions, error) { skipJavaDBUpdate := f.SkipJavaDBUpdate.Value() downloadDBOnly := f.DownloadDBOnly.Value() downloadJavaDBOnly := f.DownloadJavaDBOnly.Value() - light := f.Light.Value() if downloadDBOnly && skipDBUpdate { return DBOptions{}, xerrors.New("--skip-db-update and --download-db-only options can not be specified both") @@ -143,9 +143,6 @@ func (f *DBFlagGroup) ToOptions() (DBOptions, error) { if downloadJavaDBOnly && skipJavaDBUpdate { return DBOptions{}, xerrors.New("--skip-java-db-update and --download-java-db-only options can not be specified both") } - if light { - log.Warn("'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649") - } var dbRepository, javaDBRepository name.Reference var err error @@ -179,7 +176,6 @@ func (f *DBFlagGroup) ToOptions() (DBOptions, error) { SkipDBUpdate: skipDBUpdate, DownloadJavaDBOnly: downloadJavaDBOnly, SkipJavaDBUpdate: skipJavaDBUpdate, - Light: light, NoProgress: f.NoProgress.Value(), DBRepository: dbRepository, JavaDBRepository: javaDBRepository, diff --git a/pkg/flag/db_flags_test.go b/pkg/flag/db_flags_test.go index fb6b31effaf4..2bbd5b00198c 100644 --- a/pkg/flag/db_flags_test.go +++ b/pkg/flag/db_flags_test.go @@ -43,23 +43,6 @@ func TestDBFlagGroup_ToOptions(t *testing.T) { }, assertion: require.NoError, }, - { - name: "light", - fields: fields{ - Light: true, - DBRepository: "ghcr.io/aquasecurity/trivy-db", - JavaDBRepository: "ghcr.io/aquasecurity/trivy-java-db", - }, - want: flag.DBOptions{ - Light: true, - DBRepository: name.Tag{}, // All fields are unexported - JavaDBRepository: name.Tag{}, // All fields are unexported - }, - 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{ @@ -88,7 +71,6 @@ func TestDBFlagGroup_ToOptions(t *testing.T) { viper.Set(flag.SkipDBUpdateFlag.ConfigName, tt.fields.SkipDBUpdate) viper.Set(flag.DownloadDBOnlyFlag.ConfigName, tt.fields.DownloadDBOnly) - viper.Set(flag.LightFlag.ConfigName, tt.fields.Light) viper.Set(flag.DBRepositoryFlag.ConfigName, tt.fields.DBRepository) viper.Set(flag.JavaDBRepositoryFlag.ConfigName, tt.fields.JavaDBRepository) @@ -96,7 +78,6 @@ func TestDBFlagGroup_ToOptions(t *testing.T) { f := &flag.DBFlagGroup{ DownloadDBOnly: flag.DownloadDBOnlyFlag.Clone(), SkipDBUpdate: flag.SkipDBUpdateFlag.Clone(), - Light: flag.LightFlag.Clone(), DBRepository: flag.DBRepositoryFlag.Clone(), JavaDBRepository: flag.JavaDBRepositoryFlag.Clone(), } diff --git a/pkg/flag/misconf_flags.go b/pkg/flag/misconf_flags.go index a7f929fc4590..fc7505fec393 100644 --- a/pkg/flag/misconf_flags.go +++ b/pkg/flag/misconf_flags.go @@ -15,10 +15,12 @@ import ( // config-policy: "custom-policy/policy" // policy-namespaces: "user" var ( + // Deprecated ResetChecksBundleFlag = Flag[bool]{ Name: "reset-checks-bundle", ConfigName: "misconfiguration.reset-checks-bundle", Usage: "remove checks bundle", + Removed: `Use "trivy clean --checks-bundle" instead`, Aliases: []Alias{ { Name: "reset-policy-bundle", diff --git a/pkg/flag/options.go b/pkg/flag/options.go index db042ff802a1..ee18c45c4092 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -59,7 +59,10 @@ type Flag[T FlagType] struct { Persistent bool // Deprecated represents if the flag is deprecated - Deprecated bool + Deprecated string + + // Removed represents if the flag is removed and no longer works + Removed string // Aliases represents aliases Aliases []Alias @@ -107,6 +110,14 @@ func (f *Flag[T]) Parse() error { return xerrors.Errorf(`invalid argument "%s" for "--%s" flag: must be one of %q`, value, f.Name, f.Values) } + if f.Deprecated != "" && f.isSet() { + log.Warnf(`"--%s" is deprecated. %s`, f.Name, f.Deprecated) + } + if f.Removed != "" && f.isSet() { + log.Errorf(`"--%s" was removed. %s`, f.Name, f.Removed) + return xerrors.Errorf(`removed flag ("--%s")`, f.Name) + } + f.value = value return nil } @@ -229,8 +240,8 @@ func (f *Flag[T]) Add(cmd *cobra.Command) { flags.Float64P(f.Name, f.Shorthand, v, f.Usage) } - if f.Deprecated { - flags.MarkHidden(f.Name) // nolint: gosec + if f.Deprecated != "" || f.Removed != "" { + _ = flags.MarkHidden(f.Name) } } @@ -301,6 +312,7 @@ type Flags struct { GlobalFlagGroup *GlobalFlagGroup AWSFlagGroup *AWSFlagGroup CacheFlagGroup *CacheFlagGroup + CleanFlagGroup *CleanFlagGroup DBFlagGroup *DBFlagGroup ImageFlagGroup *ImageFlagGroup K8sFlagGroup *K8sFlagGroup @@ -323,6 +335,7 @@ type Options struct { GlobalOptions AWSOptions CacheOptions + CleanOptions DBOptions ImageOptions K8sOptions @@ -495,6 +508,9 @@ func (f *Flags) groups() []FlagGroup { if f.CacheFlagGroup != nil { groups = append(groups, f.CacheFlagGroup) } + if f.CleanFlagGroup != nil { + groups = append(groups, f.CleanFlagGroup) + } if f.DBFlagGroup != nil { groups = append(groups, f.DBFlagGroup) } @@ -621,6 +637,13 @@ func (f *Flags) ToOptions(args []string) (Options, error) { } } + if f.CleanFlagGroup != nil { + opts.CleanOptions, err = f.CleanFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("clean flag error: %w", err) + } + } + if f.DBFlagGroup != nil { opts.DBOptions, err = f.DBFlagGroup.ToOptions() if err != nil { diff --git a/pkg/flag/sbom_flags.go b/pkg/flag/sbom_flags.go index 388911abd83e..9af8414ed546 100644 --- a/pkg/flag/sbom_flags.go +++ b/pkg/flag/sbom_flags.go @@ -1,23 +1,17 @@ package flag -import ( - "golang.org/x/xerrors" - - "github.com/aquasecurity/trivy/pkg/log" -) - var ( ArtifactTypeFlag = Flag[string]{ Name: "artifact-type", ConfigName: "sbom.artifact-type", Usage: "deprecated", - Deprecated: true, + Removed: `Use 'trivy image' or other subcommands. See also https://github.com/aquasecurity/trivy/discussions/2407`, } SBOMFormatFlag = Flag[string]{ Name: "sbom-format", ConfigName: "sbom.format", Usage: "deprecated", - Deprecated: true, + Removed: `Use 'trivy image' or other subcommands. See also https://github.com/aquasecurity/trivy/discussions/2407`, } ) @@ -26,8 +20,7 @@ type SBOMFlagGroup struct { SBOMFormat *Flag[string] // deprecated } -type SBOMOptions struct { -} +type SBOMOptions struct{} func NewSBOMFlagGroup() *SBOMFlagGroup { return &SBOMFlagGroup{ @@ -52,14 +45,5 @@ func (f *SBOMFlagGroup) ToOptions() (SBOMOptions, error) { return SBOMOptions{}, err } - artifactType := f.ArtifactType.Value() - sbomFormat := f.SBOMFormat.Value() - - if artifactType != "" || sbomFormat != "" { - log.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 } diff --git a/pkg/flag/scan_flags.go b/pkg/flag/scan_flags.go index 102e16e2fdd4..07f14a08551a 100644 --- a/pkg/flag/scan_flags.go +++ b/pkg/flag/scan_flags.go @@ -73,7 +73,7 @@ var ( ConfigName: "scan.slow", Default: false, Usage: "scan over time with lower CPU and memory utilization", - Deprecated: true, + Deprecated: `Use "--parallel 1" instead.`, } ParallelFlag = Flag[int]{ Name: "parallel", diff --git a/pkg/javadb/client.go b/pkg/javadb/client.go index 408456e16500..ca055a0c333f 100644 --- a/pkg/javadb/client.go +++ b/pkg/javadb/client.go @@ -79,7 +79,7 @@ func (u *Updater) Update() error { return xerrors.Errorf("Java DB metadata update error: %w", err) } log.Info("The Java DB is cached for 3 days. If you want to update the database more frequently, " + - "the '--reset' flag clears the DB cache.") + `"trivy clean --java-db" command clears the DB cache.`) } return nil @@ -88,7 +88,7 @@ func (u *Updater) Update() error { func Init(cacheDir string, javaDBRepository name.Reference, skip, quiet bool, registryOption ftypes.RegistryOptions) { updater = &Updater{ repo: javaDBRepository, - dbDir: filepath.Join(cacheDir, "java-db"), + dbDir: dbDir(cacheDir), skip: skip, quiet: quiet, registryOption: registryOption, @@ -107,6 +107,14 @@ func Update() error { return err } +func Clear(ctx context.Context, cacheDir string) error { + return os.RemoveAll(dbDir(cacheDir)) +} + +func dbDir(cacheDir string) string { + return filepath.Join(cacheDir, "java-db") +} + type DB struct { driver db.DB } diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go index 950644c0084d..88af220d3cf6 100644 --- a/pkg/policy/policy.go +++ b/pkg/policy/policy.go @@ -237,7 +237,6 @@ func (c *Client) GetMetadata() (*Metadata, error) { } func (c *Client) Clear() error { - log.Info("Removing check bundle...") if err := os.RemoveAll(c.policyDir); err != nil { return xerrors.Errorf("failed to remove check bundle: %w", err) } From eb636c1b34e4747e99b3f322a3b9ad0a7a3a2748 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 06:56:47 +0400 Subject: [PATCH 189/352] chore(deps): bump github.com/hashicorp/go-getter from 1.7.4 to 1.7.5 (#7018) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cbfcdd088abd..771587740950 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/uuid v1.6.0 github.com/google/wire v0.6.0 - github.com/hashicorp/go-getter v1.7.4 + github.com/hashicorp/go-getter v1.7.5 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/hashicorp/go-uuid v1.0.3 diff --git a/go.sum b/go.sum index 17d31b547023..b181d9ecb24f 100644 --- a/go.sum +++ b/go.sum @@ -1452,8 +1452,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.7.4 h1:3yQjWuxICvSpYwqSayAdKRFcvBl1y/vogCxczWSmix0= -github.com/hashicorp/go-getter v1.7.4/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-getter v1.7.5 h1:dT58k9hQ/vbxNMwoI5+xFYAJuv6152UNvdHokfI5wE4= +github.com/hashicorp/go-getter v1.7.5/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= From a76e3286c413de3dec55394fb41dd627dfee37ae Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:18:20 +0600 Subject: [PATCH 190/352] fix(sbom): take pkg name from `purl` for maven pkgs (#7008) --- pkg/sbom/io/decode.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/sbom/io/decode.go b/pkg/sbom/io/decode.go index b740d756c4bd..7544cf215a3e 100644 --- a/pkg/sbom/io/decode.go +++ b/pkg/sbom/io/decode.go @@ -256,6 +256,14 @@ func (m *Decoder) pkgName(pkg *ftypes.Package, c *core.Component) string { return pkg.Name } + // `maven purl type` has no restrictions on using lowercase letters. + // Also, `spdx-maven-plugin` uses `name` instead of `artifactId` for the `package name` field. + // So we need to use `purl` for maven/gradle packages + // See https://github.com/aquasecurity/trivy/issues/7007 for more information. + if p.Type == packageurl.TypeMaven || p.Type == packageurl.TypeGradle { + return pkg.Name + } + // TODO(backward compatibility): Remove after 03/2025 // Bitnami used different pkg.Name and the name from PURL. // For backwards compatibility - we need to use PURL. @@ -265,9 +273,6 @@ func (m *Decoder) pkgName(pkg *ftypes.Package, c *core.Component) string { } if c.Group != "" { - if p.Type == packageurl.TypeMaven || p.Type == packageurl.TypeGradle { - return c.Group + ":" + c.Name - } return c.Group + "/" + c.Name } return c.Name From 8d618e48a2f1b60c2e4c49cdd9deb8eb45c972b0 Mon Sep 17 00:00:00 2001 From: chenk Date: Wed, 26 Jun 2024 10:04:50 +0300 Subject: [PATCH 191/352] feat(k8s)!: node-collector dynamic commands support (#6861) Signed-off-by: chenk --- docs/docs/compliance/compliance.md | 224 +++++++++++++++++- .../configuration/cli/trivy_image.md | 2 +- .../configuration/cli/trivy_kubernetes.md | 4 +- docs/docs/target/container_image.md | 4 +- docs/docs/target/kubernetes.md | 20 +- go.mod | 15 +- go.sum | 34 +-- integration/testdata/helm.json.golden | 2 +- .../testdata/helm_testchart.json.golden | 6 +- .../helm_testchart.overridden.json.golden | 6 +- pkg/commands/app.go | 12 +- pkg/commands/app_test.go | 2 +- pkg/flag/kubernetes_flags.go | 2 +- pkg/flag/options.go | 2 +- pkg/iac/rules/register.go | 2 +- pkg/iac/types/compliance.go | 4 + pkg/k8s/commands/cluster.go | 53 ++++- pkg/types/report.go | 28 ++- 18 files changed, 353 insertions(+), 69 deletions(-) diff --git a/docs/docs/compliance/compliance.md b/docs/docs/compliance/compliance.md index 2301fc3fb279..5ff9c6ac6652 100644 --- a/docs/docs/compliance/compliance.md +++ b/docs/docs/compliance/compliance.md @@ -35,9 +35,231 @@ to specify a built-in compliance report, select it by ID like `trivy --complianc For the list of built-in compliance reports, please see the relevant section: - [Docker compliance](../target/container_image.md#compliance) -- [Kubernetes compliance](../target/kubernetes.md#compliance) +- [Kubernetes compliance](../target/kubernetes.md#compliance) - [AWS compliance](../target/aws.md#compliance) +## Contribute a Built-in Compliance Report + +### Define a Compliance spec, based on CIS benchmark or other specs + +Here is an example for CIS compliance report: + +```yaml +--- +spec: + id: k8s-cis-1.23 + title: CIS Kubernetes Benchmarks v1.23 + description: CIS Kubernetes Benchmarks + platform: k8s + type: cis + version: '1.23' + relatedResources: + - https://www.cisecurity.org/benchmark/kubernetes + controls: + - id: 1.1.1 + name: Ensure that the API server pod specification file permissions are set to + 600 or more restrictive + description: Ensure that the API server pod specification file has permissions + of 600 or more restrictive + checks: + - id: AVD-KCV-0073 + commands: + - id: CMD-0001 + severity: HIGH + +``` + +### Compliance ID + +ID field is the name used to execute the compliance scan via trivy +example: + +```sh +trivy k8s --compliance k8s-cis-1.23 +``` + +ID naming convention: {platform}-{type}-{version} + +### Compliance Platform + +The platform field specifies the type of platform on which to run this compliance report. +Supported platforms: + +- k8s (native kubernetes cluster) +- eks (elastic kubernetes service) +- aks (azure kubernetes service) +- gke (google kubernetes engine) +- rke2 (rancher kubernetes engine v2) +- ocp (OpenShift Container Platform) +- docker (docker engine) +- aws (amazon web services) + +### Compliance Type + +The type field specifies the kind compliance report. + +- cis (Center for Internet Security) +- nsa (National Security Agency) +- pss (Pod Security Standards) + +### Compliance Version + +The version field specifies the version of the compliance report. + +- 1.23 + +### Compliance Check ID + +Specify the check ID that needs to be evaluated based on the information collected from the command data output to assess the control. + +Example of how to define check data under [checks folder](https://github.com/aquasecurity/trivy-checks/tree/main/checks): + +```sh +# METADATA +# title: "Ensure that the --kubeconfig kubelet.conf file permissions are set to 600 or more restrictive" +# description: "Ensure that the kubelet.conf file has permissions of 600 or more restrictive." +# scope: package +# schemas: +# - input: schema["kubernetes"] +# related_resources: +# - https://www.cisecurity.org/benchmark/kubernetes +# custom: +# id: KCV0073 +# avd_id: AVD-KCV-0073 +# severity: HIGH +# short_code: ensure-kubelet.conf-file-permissions-600-or-more-restrictive. +# recommended_action: "Change the kubelet.conf file permissions to 600 or more restrictive if exist" +# input: +# selector: +# - type: kubernetes +package builtin.kubernetes.KCV0073 + +import data.lib.kubernetes + +types := ["master", "worker"] + +validate_kubelet_file_permission(sp) := {"kubeletConfFilePermissions": violation} { + sp.kind == "NodeInfo" + sp.type == types[_] + violation := {permission | permission = sp.info.kubeletConfFilePermissions.values[_]; permission > 600} + count(violation) > 0 +} + +deny[res] { + output := validate_kubelet_file_permission(input) + msg := "Ensure that the --kubeconfig kubelet.conf file permissions are set to 600 or more restrictive" + res := result.new(msg, output) +} +``` + +### Compliance Command ID + +***Note:*** This field is not mandatory, it is relevant to k8s compliance report when node-collector is in use + +Specify the command ID (#ref) that needs to be executed to collect the information required to evaluate the control. + +Example of how to define command data under [commands folder](https://github.com/aquasecurity/trivy-checks/tree/main/commands) + +```yaml +--- +- id: CMD-0001 + key: kubeletConfFilePermissions + title: kubelet.conf file permissions + nodeType: worker + audit: stat -c %a $kubelet.kubeconfig + platfroms: + - k8s + - aks +``` + +#### Command ID + +Find the next command ID by running the command on [trivy-checks project](https://github.com/aquasecurity/trivy-checks). + +```sh +make command-id +``` + +#### Command Key + +- Re-use an existing key or specifiy a new one (make sure key name has no spaces) + +Note: The key value should match the key name evaluated by the Rego check. + +### Command Title + +Represent the purpose of the command + +### Command NodeType + +Specify the node type on which the command is supposed to run. + +- worker +- master + +### Command Audit + +Specify here the shell command to be used please make sure to add error supression (2>/dev/null) + +### Command Platforms + +The list of platforms that support this command. Name should be taken from this list [Platforms](#compliance-platform) + +### Command Config Files + +The commands use a configuration file that helps obtain the paths to binaries and configuration files based on different platforms (e.g., Rancher, native Kubernetes, etc.). + +For example: + +```yaml +kubelet: + bins: + - kubelet + - hyperkube kubelet + confs: + - /etc/kubernetes/kubelet-config.yaml + - /var/lib/kubelet/config.yaml +``` + +### Commands Files Location + +Currently checks files location are :`https://github.com/aquasecurity/trivy-checks/tree/main/checks` + +Command files location: `https://github.com/aquasecurity/trivy-checks/tree/main/commands` +under command file + +Note: command config files will be located under `https://github.com/aquasecurity/trivy-checks/tree/main/commands` as well + +### Node-collector output + +The node collector will read commands and execute each command, and incorporate the output into the NodeInfo resource. + +example: + +```json +{ + "apiVersion": "v1", + "kind": "NodeInfo", + "metadata": { + "creationTimestamp": "2023-01-04T11:37:11+02:00" + }, + "type": "master", + "info": { + "adminConfFileOwnership": { + "values": [ + "root:root" + ] + }, + "adminConfFilePermissions": { + "values": [ + 600 + ] + } + ... + } +} +``` + ## Custom compliance You can create your own custom compliance report. A compliance report is a simple YAML document in the following format: diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 251ea85b6f33..1ef6e1e8cf3c 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -38,7 +38,7 @@ trivy image [flags] IMAGE_NAME --cache-ttl duration cache TTL when using redis as cache backend --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") - --compliance string compliance report to generate (docker-cis) + --compliance string compliance report to generate (docker-cis-1.6.0) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded --custom-headers strings custom headers in client mode diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index a39275750bde..91e9e5fa8e76 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -34,7 +34,7 @@ trivy kubernetes [flags] [CONTEXT] --cache-ttl duration cache TTL when using redis as cache backend --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") - --compliance string compliance report to generate (k8s-nsa,k8s-cis,k8s-pss-baseline,k8s-pss-restricted) + --compliance string compliance report to generate (k8s-nsa-1.0,k8s-cis-1.23,eks-cis-1.4,rke2-cis-1.24,k8s-pss-baseline-0.1,k8s-pss-restricted-0.1) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") @@ -71,7 +71,7 @@ trivy kubernetes [flags] [CONTEXT] --list-all-pkgs output all packages in the JSON report regardless of vulnerability --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) --no-progress suppress progress bar - --node-collector-imageref string indicate the image reference for the node-collector scan job (default "ghcr.io/aquasecurity/node-collector:0.2.1") + --node-collector-imageref string indicate the image reference for the node-collector scan job (default "ghcr.io/aquasecurity/node-collector:0.3.1") --node-collector-namespace string specify the namespace in which the node-collector job should be deployed (default "trivy-temp") --offline-scan do not issue API requests to identify dependencies -o, --output string output file name diff --git a/docs/docs/target/container_image.md b/docs/docs/target/container_image.md index 948cf9678283..94acc954a743 100644 --- a/docs/docs/target/container_image.md +++ b/docs/docs/target/container_image.md @@ -436,14 +436,14 @@ The following reports are available out of the box: | Compliance | Version | Name for command | More info | |----------------------------------------|---------|------------------|---------------------------------------------------------------------------------------------| -| CIS Docker Community Edition Benchmark | 1.1.0 | `docker-cis` | [Link](https://www.aquasec.com/cloud-native-academy/docker-container/docker-cis-benchmark/) | +| CIS Docker Community Edition Benchmark | 1.1.0 | `docker-cis-1.6.0` | [Link](https://www.aquasec.com/cloud-native-academy/docker-container/docker-cis-benchmark/) | ### Examples Scan a container image configuration and generate a compliance summary report: ``` -$ trivy image --compliance docker-cis [YOUR_IMAGE_NAME] +trivy image --compliance docker-cis-1.6.0 [YOUR_IMAGE_NAME] ``` !!! note diff --git a/docs/docs/target/kubernetes.md b/docs/docs/target/kubernetes.md index a92057dc5341..10253aff4c72 100644 --- a/docs/docs/target/kubernetes.md +++ b/docs/docs/target/kubernetes.md @@ -355,12 +355,14 @@ For an overview of Trivy's Compliance feature, including working with custom com The following reports are available out of the box: -| Compliance | Name for command | More info | -|----------------------------------------------|----------------------|---------------------------------------------------------------------------------------------------------------------| -| NSA, CISA Kubernetes Hardening Guidance v1.2 | `k8s-nsa` | [Link](https://media.defense.gov/2022/Aug/29/2003066362/-1/-1/0/CTR_KUBERNETES_HARDENING_GUIDANCE_1.2_20220829.PDF) | -| CIS Benchmark for Kubernetes v1.23 | `k8s-cis` | [Link](https://www.cisecurity.org/benchmark/kubernetes) | -| Pod Security Standards, Baseline | `k8s-pss-baseline` | [Link](https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline) | -| Pod Security Standards, Restricted | `k8s-pss-restricted` | [Link](https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted) | +| Compliance | Name for command | More info | +|----------------------------------------------|--------------------------|---------------------------------------------------------------------------------------------------------------------| +| NSA, CISA Kubernetes Hardening Guidance v1.0 | `k8s-nsa-1.0` | [Link](https://media.defense.gov/2022/Aug/29/2003066362/-1/-1/0/CTR_KUBERNETES_HARDENING_GUIDANCE_1.2_20220829.PDF) | +| CIS Benchmark for Kubernetes v1.23 | `k8s-cis-1.23` | [Link](https://www.cisecurity.org/benchmark/kubernetes) | +| CIS Benchmark for RKE2 v1.24 | `rke2-cis-1.24` | [Link](https://www.cisecurity.org/benchmark/kubernetes) | +| CIS Benchmark for EKS v1.4 | `eks-cis-1.4` | [Link](https://www.cisecurity.org/benchmark/kubernetes) | +| Pod Security Standards, Baseline | `k8s-pss-baseline-0.1` | [Link](https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline) | +| Pod Security Standards, Restricted | `k8s-pss-restricted-0.1` | [Link](https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted) | Examples: @@ -376,7 +378,7 @@ Get the detailed report for checks: ``` -trivy k8s --compliance=k8s-cis --report all +trivy k8s --compliance=k8s-cis-1.23 --report all ``` @@ -384,7 +386,7 @@ Get summary report in JSON format: ``` -trivy k8s --compliance=k8s-cis --report summary --format json +trivy k8s --compliance=k8s-cis-1.23 --report summary --format json ``` @@ -392,7 +394,7 @@ Get detailed report in JSON format: ``` -trivy k8s --compliance=k8s-cis --report all --format json +trivy k8s --compliance=k8s-cis-1.23 --report all --format json ``` diff --git a/go.mod b/go.mod index 771587740950..1f4c4fc4c7ec 100644 --- a/go.mod +++ b/go.mod @@ -25,10 +25,10 @@ require ( github.com/aquasecurity/table v1.8.0 github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac github.com/aquasecurity/tml v0.6.1 - github.com/aquasecurity/trivy-checks v0.11.0 + github.com/aquasecurity/trivy-checks v0.13.0 github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 - github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7 + github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240625102549-87c0f9c7bcf4 github.com/aws/aws-sdk-go-v2 v1.27.2 github.com/aws/aws-sdk-go-v2/config v1.27.18 github.com/aws/aws-sdk-go-v2/credentials v1.17.18 @@ -167,7 +167,7 @@ require ( github.com/antchfx/xpath v1.3.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.53.0 // indirect + github.com/aws/aws-sdk-go v1.53.16 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect @@ -205,6 +205,7 @@ require ( github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect + github.com/dsnet/compress v0.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect @@ -369,12 +370,12 @@ require ( k8s.io/apiextensions-apiserver v0.30.0 // indirect k8s.io/apimachinery v0.30.1 // indirect k8s.io/apiserver v0.30.0 // indirect - k8s.io/cli-runtime v0.30.0 // indirect - k8s.io/client-go v0.30.0 // indirect - k8s.io/component-base v0.30.0 // indirect + k8s.io/cli-runtime v0.30.1 // indirect + k8s.io/client-go v0.30.1 // indirect + k8s.io/component-base v0.30.1 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/kubectl v0.30.0 // indirect + k8s.io/kubectl v0.30.1 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect modernc.org/libc v1.50.9 // indirect modernc.org/mathutil v1.6.0 // indirect diff --git a/go.sum b/go.sum index b181d9ecb24f..6c441559ac1b 100644 --- a/go.sum +++ b/go.sum @@ -769,14 +769,14 @@ github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac h1:dy7xjLO github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac/go.mod h1:nyavBQqxtIkQh99lQE1ssup3i2uIq1+giL7tOSHapYk= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= -github.com/aquasecurity/trivy-checks v0.11.0 h1:hS5gSQyuyIITrY/kCY2AWQMUSwXLpdtbHDPaCs6eSaI= -github.com/aquasecurity/trivy-checks v0.11.0/go.mod h1:IAK3eHcKNxIHo/ckxKoHsXmEpUG45/38grW5bBjL9lw= +github.com/aquasecurity/trivy-checks v0.13.0 h1:na6PTdY4U0uK/fjz3HNRYBxvYSJ8vgTb57a5T8Y5t9w= +github.com/aquasecurity/trivy-checks v0.13.0/go.mod h1:Xec/SMVGV66I7RgUqOX9MEr+YxBqHXDVLTYmpspPi3E= github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTUAkbGqpzt9nJsO24RAdfG+ZSiLFj0G2jO8c= github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7 h1:bLmh/xuC/7abvt9S/xnODTQRu8fW6BhFHS6Cmbn0RNU= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240516051533-4c5a4aad13b7/go.mod h1:HSpAJE8Y5Cjjg0Aw/0lqd3vMihN/FxBEj/f/7yDi/Uc= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240625102549-87c0f9c7bcf4 h1:IKKfTgIxDptIQWB3AQFP55uuFpE9DzsbHrYIPL3VK1w= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240625102549-87c0f9c7bcf4/go.mod h1:U3LFiVzDi7FYUToe2hV0+HrEIcVpnqaajX7cEUha9Bs= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -787,8 +787,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.53.0 h1:MMo1x1ggPPxDfHMXJnQudTbGXYlD4UigUAud1DJxPVo= -github.com/aws/aws-sdk-go v1.53.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.53.16 h1:8oZjKQO/ml1WLUZw5hvF7pvYjPf8o9f57Wldoy/q9Qc= +github.com/aws/aws-sdk-go v1.53.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8= github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk= @@ -1091,6 +1091,9 @@ github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -1538,6 +1541,7 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= @@ -1546,6 +1550,7 @@ github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= @@ -2021,6 +2026,7 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= @@ -2993,18 +2999,18 @@ k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M= k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= -k8s.io/cli-runtime v0.30.0 h1:0vn6/XhOvn1RJ2KJOC6IRR2CGqrpT6QQF4+8pYpWQ48= -k8s.io/cli-runtime v0.30.0/go.mod h1:vATpDMATVTMA79sZ0YUCzlMelf6rUjoBzlp+RnoM+cg= +k8s.io/cli-runtime v0.30.1 h1:kSBBpfrJGS6lllc24KeniI9JN7ckOOJKnmFYH1RpTOw= +k8s.io/cli-runtime v0.30.1/go.mod h1:zhHgbqI4J00pxb6gM3gJPVf2ysDjhQmQtnTxnMScab8= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= -k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= +k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= +k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o= -k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ= +k8s.io/component-base v0.30.1 h1:bvAtlPh1UrdaZL20D9+sWxsJljMi0QZ3Lmw+kmZAaxQ= +k8s.io/component-base v0.30.1/go.mod h1:e/X9kDiOebwlI41AvBHuWdqFriSRrX50CdwA9TFaHLI= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= @@ -3017,8 +3023,8 @@ k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/kubectl v0.30.0 h1:xbPvzagbJ6RNYVMVuiHArC1grrV5vSmmIcSZuCdzRyk= -k8s.io/kubectl v0.30.0/go.mod h1:zgolRw2MQXLPwmic2l/+iHs239L49fhSeICuMhQQXTI= +k8s.io/kubectl v0.30.1 h1:sHFIRI3oP0FFZmBAVEE8ErjnTyXDPkBcvO88mH9RjuY= +k8s.io/kubectl v0.30.1/go.mod h1:7j+L0Cc38RYEcx+WH3y44jRBe1Q1jxdGPKkX0h4iDq0= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= diff --git a/integration/testdata/helm.json.golden b/integration/testdata/helm.json.golden index c9721e205272..518458ac1088 100644 --- a/integration/testdata/helm.json.golden +++ b/integration/testdata/helm.json.golden @@ -21,7 +21,7 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 125, + "Successes": 80, "Failures": 14, "Exceptions": 0 }, diff --git a/integration/testdata/helm_testchart.json.golden b/integration/testdata/helm_testchart.json.golden index ce6df6b17cee..2e659b13e68a 100644 --- a/integration/testdata/helm_testchart.json.golden +++ b/integration/testdata/helm_testchart.json.golden @@ -21,7 +21,7 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 135, + "Successes": 90, "Failures": 4, "Exceptions": 0 }, @@ -341,7 +341,7 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 106, + "Successes": 61, "Failures": 0, "Exceptions": 0 } @@ -351,7 +351,7 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 105, + "Successes": 60, "Failures": 0, "Exceptions": 0 } diff --git a/integration/testdata/helm_testchart.overridden.json.golden b/integration/testdata/helm_testchart.overridden.json.golden index 573d789ef7b4..1b1ada2cd9a3 100644 --- a/integration/testdata/helm_testchart.overridden.json.golden +++ b/integration/testdata/helm_testchart.overridden.json.golden @@ -21,7 +21,7 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 133, + "Successes": 88, "Failures": 6, "Exceptions": 0 }, @@ -568,7 +568,7 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 106, + "Successes": 61, "Failures": 0, "Exceptions": 0 } @@ -578,7 +578,7 @@ "Class": "config", "Type": "helm", "MisconfSummary": { - "Successes": 105, + "Successes": 60, "Failures": 0, "Exceptions": 0 } diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 0cd331e7b9b7..71c85aaa5933 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -247,7 +247,7 @@ func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { reportFlagGroup.ReportFormat = report compliance := flag.ComplianceFlag.Clone() - compliance.Values = []string{types.ComplianceDockerCIS} + compliance.Values = []string{types.ComplianceDockerCIS160} reportFlagGroup.Compliance = compliance // override usage as the accepted values differ for each subcommand. misconfFlagGroup := flag.NewMisconfFlagGroup() @@ -946,10 +946,12 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { reportFlagGroup := flag.NewReportFlagGroup() compliance := flag.ComplianceFlag.Clone() compliance.Values = []string{ - types.ComplianceK8sNsa, - types.ComplianceK8sCIS, - types.ComplianceK8sPSSBaseline, - types.ComplianceK8sPSSRestricted, + types.ComplianceK8sNsa10, + types.ComplianceK8sCIS123, + types.ComplianceEksCIS14, + types.ComplianceRke2CIS124, + types.ComplianceK8sPSSBaseline01, + types.ComplianceK8sPSSRestricted01, } reportFlagGroup.Compliance = compliance // override usage as the accepted values differ for each subcommand. reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' diff --git a/pkg/commands/app_test.go b/pkg/commands/app_test.go index 143de739caf0..7235a3e94c7d 100644 --- a/pkg/commands/app_test.go +++ b/pkg/commands/app_test.go @@ -271,7 +271,7 @@ func TestFlags(t *testing.T) { "--scanners", "license", "--compliance", - "docker-cis", + "docker-cis-1.6.0", }, want: want{ format: types.FormatTable, diff --git a/pkg/flag/kubernetes_flags.go b/pkg/flag/kubernetes_flags.go index 2683fa07b13a..6d0d64f4dc56 100644 --- a/pkg/flag/kubernetes_flags.go +++ b/pkg/flag/kubernetes_flags.go @@ -39,7 +39,7 @@ var ( NodeCollectorImageRef = Flag[string]{ Name: "node-collector-imageref", ConfigName: "kubernetes.node-collector.imageref", - Default: "ghcr.io/aquasecurity/node-collector:0.2.1", + Default: "ghcr.io/aquasecurity/node-collector:0.3.1", Usage: "indicate the image reference for the node-collector scan job", } ExcludeOwned = Flag[bool]{ diff --git a/pkg/flag/options.go b/pkg/flag/options.go index ee18c45c4092..8f15480ccfec 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -384,7 +384,7 @@ func (o *Options) Align() error { o.Scanners = scanners o.ImageConfigScanners = nil // TODO: define image-config-scanners in the spec - if o.Compliance.Spec.ID == types.ComplianceDockerCIS { + if o.Compliance.Spec.ID == types.ComplianceDockerCIS160 { o.Scanners = types.Scanners{types.VulnerabilityScanner} o.ImageConfigScanners = types.Scanners{ types.MisconfigScanner, diff --git a/pkg/iac/rules/register.go b/pkg/iac/rules/register.go index ab847de2e1dc..e07268255417 100755 --- a/pkg/iac/rules/register.go +++ b/pkg/iac/rules/register.go @@ -5,7 +5,7 @@ import ( "gopkg.in/yaml.v3" - "github.com/aquasecurity/trivy-checks/specs" + "github.com/aquasecurity/trivy-checks/pkg/specs" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/scan" dftypes "github.com/aquasecurity/trivy/pkg/iac/types" diff --git a/pkg/iac/types/compliance.go b/pkg/iac/types/compliance.go index 5928537e132a..42636fffe544 100644 --- a/pkg/iac/types/compliance.go +++ b/pkg/iac/types/compliance.go @@ -7,6 +7,9 @@ type ControlStatus string type SpecCheck struct { ID string `yaml:"id"` } +type Command struct { + ID string `yaml:"id"` +} // ComplianceSpec represent the compliance specification type ComplianceSpec struct { @@ -28,6 +31,7 @@ type Control struct { Name string `yaml:"name"` Description string `yaml:"description,omitempty"` Checks []SpecCheck `yaml:"checks"` + Commands []Command `yaml:"commands"` Severity Severity `yaml:"severity"` DefaultStatus ControlStatus `yaml:"defaultStatus,omitempty"` } diff --git a/pkg/k8s/commands/cluster.go b/pkg/k8s/commands/cluster.go index 6b169771f1ca..179a089523df 100644 --- a/pkg/k8s/commands/cluster.go +++ b/pkg/k8s/commands/cluster.go @@ -5,9 +5,11 @@ import ( "golang.org/x/xerrors" + trivy_checks "github.com/aquasecurity/trivy-checks" k8sArtifacts "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" "github.com/aquasecurity/trivy-kubernetes/pkg/k8s" "github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s" + "github.com/aquasecurity/trivy/pkg/commands/operation" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" @@ -35,11 +37,7 @@ func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) err trivyk8s.WithExcludeOwned(opts.ExcludeOwned), } if opts.Scanners.AnyEnabled(types.MisconfigScanner) && !opts.DisableNodeCollector { - artifacts, err = trivyk8s.New(cluster, k8sOpts...).ListArtifactAndNodeInfo(ctx, - trivyk8s.WithScanJobNamespace(opts.NodeCollectorNamespace), - trivyk8s.WithIgnoreLabels(opts.ExcludeNodes), - trivyk8s.WithScanJobImageRef(opts.NodeCollectorImageRef), - trivyk8s.WithTolerations(opts.Tolerations)) + artifacts, err = trivyk8s.New(cluster, k8sOpts...).ListArtifactAndNodeInfo(ctx, nodeCollectorOptions(opts)...) if err != nil { return xerrors.Errorf("get k8s artifacts with node info error: %w", err) } @@ -60,3 +58,48 @@ func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) err runner := newRunner(opts, cluster.GetCurrentContext()) return runner.run(ctx, artifacts) } + +func nodeCollectorOptions(opts flag.Options) []trivyk8s.NodeCollectorOption { + nodeCollectorOptions := []trivyk8s.NodeCollectorOption{ + trivyk8s.WithScanJobNamespace(opts.NodeCollectorNamespace), + trivyk8s.WithIgnoreLabels(opts.ExcludeNodes), + trivyk8s.WithScanJobImageRef(opts.NodeCollectorImageRef), + trivyk8s.WithTolerations(opts.Tolerations)} + + contentPath, err := operation.InitBuiltinPolicies(context.Background(), + opts.CacheDir, + opts.Quiet, + opts.SkipCheckUpdate, + opts.MisconfOptions.ChecksBundleRepository, + opts.RegistryOpts()) + + if err != nil { + log.Error("Falling back to embedded checks", log.Err(err)) + nodeCollectorOptions = append(nodeCollectorOptions, + []trivyk8s.NodeCollectorOption{ + trivyk8s.WithEmbeddedCommandFileSystem(trivy_checks.EmbeddedK8sCommandsFileSystem), + trivyk8s.WithEmbeddedNodeConfigFilesystem(trivy_checks.EmbeddedConfigCommandsFileSystem), + }...) + } + + complianceCommandsIDs := getComplianceCommands(opts) + nodeCollectorOptions = append(nodeCollectorOptions, []trivyk8s.NodeCollectorOption{ + trivyk8s.WithCommandPaths(contentPath), + trivyk8s.WithSpecCommandIds(complianceCommandsIDs), + }...) + return nodeCollectorOptions +} + +func getComplianceCommands(opts flag.Options) []string { + var commands []string + if opts.Compliance.Spec.ID != "" { + for _, control := range opts.Compliance.Spec.Controls { + for _, command := range control.Commands { + if command.ID != "" { + commands = append(commands, command.ID) + } + } + } + } + return commands +} diff --git a/pkg/types/report.go b/pkg/types/report.go index baaeaab0a0c3..6937f8ce7960 100644 --- a/pkg/types/report.go +++ b/pkg/types/report.go @@ -53,13 +53,15 @@ const ( ClassLicenseFile ResultClass = "license-file" // For detected licenses in files ClassCustom ResultClass = "custom" - ComplianceK8sNsa = Compliance("k8s-nsa") - ComplianceK8sCIS = Compliance("k8s-cis") - ComplianceK8sPSSBaseline = Compliance("k8s-pss-baseline") - ComplianceK8sPSSRestricted = Compliance("k8s-pss-restricted") - ComplianceAWSCIS12 = Compliance("aws-cis-1.2") - ComplianceAWSCIS14 = Compliance("aws-cis-1.4") - ComplianceDockerCIS = Compliance("docker-cis") + ComplianceK8sNsa10 = Compliance("k8s-nsa-1.0") + ComplianceK8sCIS123 = Compliance("k8s-cis-1.23") + ComplianceK8sPSSBaseline01 = Compliance("k8s-pss-baseline-0.1") + ComplianceK8sPSSRestricted01 = Compliance("k8s-pss-restricted-0.1") + ComplianceAWSCIS12 = Compliance("aws-cis-1.2") + ComplianceAWSCIS14 = Compliance("aws-cis-1.4") + ComplianceDockerCIS160 = Compliance("docker-cis-1.6.0") + ComplianceEksCIS14 = Compliance("eks-cis-1.4") + ComplianceRke2CIS124 = Compliance("rke2-cis-1.24") FormatTable Format = "table" FormatJSON Format = "json" @@ -91,13 +93,15 @@ var ( FormatGitHub, } SupportedCompliances = []string{ - ComplianceK8sNsa, - ComplianceK8sCIS, - ComplianceK8sPSSBaseline, - ComplianceK8sPSSRestricted, + ComplianceK8sNsa10, + ComplianceK8sCIS123, + ComplianceK8sPSSBaseline01, + ComplianceK8sPSSRestricted01, ComplianceAWSCIS12, ComplianceAWSCIS14, - ComplianceDockerCIS, + ComplianceDockerCIS160, + ComplianceEksCIS14, + ComplianceRke2CIS124, } ) From 3d02a31b44924f9e2495aae087f7ca9de3314db4 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 26 Jun 2024 14:23:00 +0400 Subject: [PATCH 192/352] fix(plugin): respect `--insecure` (#7022) Signed-off-by: knqyf263 --- pkg/commands/app.go | 25 ++++++++++--- pkg/downloader/download.go | 19 ++++++++-- pkg/downloader/downloader_test.go | 61 +++++++++++++++++++++++++++++++ pkg/oci/artifact.go | 3 +- pkg/plugin/index.go | 8 ++-- pkg/plugin/index_test.go | 2 +- pkg/plugin/manager.go | 6 +-- pkg/plugin/plugin.go | 3 +- 8 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 pkg/downloader/downloader_test.go diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 71c85aaa5933..c130deb64876 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -62,6 +62,7 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e // NewApp is the factory method to return Trivy CLI func NewApp() *cobra.Command { + cobra.EnableTraverseRunHooks = true // To execute persistent pre-run hooks from all parents. globalFlags := flag.NewGlobalFlagGroup() rootCmd := NewRootCommand(globalFlags) rootCmd.AddGroup( @@ -89,7 +90,7 @@ func NewApp() *cobra.Command { NewServerCommand(globalFlags), NewConfigCommand(globalFlags), NewConvertCommand(globalFlags), - NewPluginCommand(), + NewPluginCommand(globalFlags), NewModuleCommand(globalFlags), NewKubernetesCommand(globalFlags), NewSBOMCommand(globalFlags), @@ -719,7 +720,11 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return cmd } -func NewPluginCommand() *cobra.Command { +func NewPluginCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + var pluginOptions flag.Options + pluginFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + } cmd := &cobra.Command{ Use: "plugin subcommand", Aliases: []string{"p"}, @@ -727,6 +732,13 @@ func NewPluginCommand() *cobra.Command { Short: "Manage plugins", SilenceErrors: true, SilenceUsage: true, + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + pluginOptions, err = pluginFlags.ToOptions(args) + if err != nil { + return err + } + return nil + }, } cmd.AddCommand( &cobra.Command{ @@ -746,7 +758,7 @@ func NewPluginCommand() *cobra.Command { DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - if _, err := plugin.Install(cmd.Context(), args[0], plugin.Options{}); err != nil { + if _, err := plugin.Install(cmd.Context(), args[0], plugin.Options{Insecure: pluginOptions.Insecure}); err != nil { return xerrors.Errorf("plugin install error: %w", err) } return nil @@ -805,7 +817,10 @@ func NewPluginCommand() *cobra.Command { Short: "Run a plugin on the fly", Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return plugin.Run(cmd.Context(), args[0], plugin.Options{Args: args[1:]}) + return plugin.Run(cmd.Context(), args[0], plugin.Options{ + Args: args[1:], + Insecure: pluginOptions.Insecure, + }) }, }, &cobra.Command{ @@ -816,7 +831,7 @@ func NewPluginCommand() *cobra.Command { SilenceUsage: true, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { - if err := plugin.Update(cmd.Context()); err != nil { + if err := plugin.Update(cmd.Context(), plugin.Options{Insecure: pluginOptions.Insecure}); err != nil { return xerrors.Errorf("plugin update error: %w", err) } return nil diff --git a/pkg/downloader/download.go b/pkg/downloader/download.go index 9b554b6a4eb8..7190d3d3d0a3 100644 --- a/pkg/downloader/download.go +++ b/pkg/downloader/download.go @@ -10,8 +10,8 @@ import ( ) // DownloadToTempDir downloads the configured source to a temp dir. -func DownloadToTempDir(ctx context.Context, url string) (string, error) { - tempDir, err := os.MkdirTemp("", "trivy-plugin") +func DownloadToTempDir(ctx context.Context, url string, insecure bool) (string, error) { + tempDir, err := os.MkdirTemp("", "trivy-download") if err != nil { return "", xerrors.Errorf("failed to create a temp dir: %w", err) } @@ -21,7 +21,7 @@ func DownloadToTempDir(ctx context.Context, url string) (string, error) { return "", xerrors.Errorf("unable to get the current dir: %w", err) } - if err = Download(ctx, url, tempDir, pwd); err != nil { + if err = Download(ctx, url, tempDir, pwd, insecure); err != nil { return "", xerrors.Errorf("download error: %w", err) } @@ -29,11 +29,14 @@ func DownloadToTempDir(ctx context.Context, url string) (string, error) { } // Download downloads the configured source to the destination. -func Download(ctx context.Context, src, dst, pwd string) error { +func Download(ctx context.Context, src, dst, pwd string, insecure bool) error { // go-getter doesn't allow the dst directory already exists if the src is directory. _ = os.RemoveAll(dst) var opts []getter.ClientOption + if insecure { + opts = append(opts, getter.WithInsecure()) + } // Clone the global map so that it will not be accessed concurrently. getters := maps.Clone(getter.Getters) @@ -41,6 +44,14 @@ func Download(ctx context.Context, src, dst, pwd string) error { // Overwrite the file getter so that a file will be copied getters["file"] = &getter.FileGetter{Copy: true} + // Since "httpGetter" is a global pointer and the state is shared, + // once it is executed without "WithInsecure()", + // it cannot enable WithInsecure() afterwards because its state is preserved. + // cf. https://github.com/hashicorp/go-getter/blob/5a63fd9c0d5b8da8a6805e8c283f46f0dacb30b3/get.go#L63-L65 + httpGetter := &getter.HttpGetter{Netrc: true} + getters["http"] = httpGetter + getters["https"] = httpGetter + // Build the client client := &getter.Client{ Ctx: ctx, diff --git a/pkg/downloader/downloader_test.go b/pkg/downloader/downloader_test.go new file mode 100644 index 000000000000..80e7ef530310 --- /dev/null +++ b/pkg/downloader/downloader_test.go @@ -0,0 +1,61 @@ +package downloader_test + +import ( + "context" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/downloader" +) + +func TestDownload(t *testing.T) { + // Set up a test server with a self-signed certificate + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("test content")) + require.NoError(t, err) + })) + defer server.Close() + + tests := []struct { + name string + insecure bool + wantErr bool + }{ + { + "Secure (should fail)", + false, + true, + }, + { + "Insecure (should succeed)", + true, + false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set up the destination path + dst := t.TempDir() + + // Execute the download + err := downloader.Download(context.Background(), server.URL, dst, "", tt.insecure) + + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + + // Check the content of the downloaded file + content, err := os.ReadFile(dst) + require.NoError(t, err) + assert.Equal(t, "test content", string(content)) + }) + } +} diff --git a/pkg/oci/artifact.go b/pkg/oci/artifact.go index bab9ec7065ab..8cd4460ec919 100644 --- a/pkg/oci/artifact.go +++ b/pkg/oci/artifact.go @@ -188,7 +188,8 @@ func (a *Artifact) download(ctx context.Context, layer v1.Layer, fileName, dir s } // Decompress the downloaded file if it is compressed and copy it into the dst - if err = downloader.Download(ctx, f.Name(), dir, dir); err != nil { + // NOTE: it's local copying, the insecure option doesn't matter. + if err = downloader.Download(ctx, f.Name(), dir, dir, false); err != nil { return xerrors.Errorf("download error: %w", err) } diff --git a/pkg/plugin/index.go b/pkg/plugin/index.go index d0df12e2036f..58beeaa5f9c7 100644 --- a/pkg/plugin/index.go +++ b/pkg/plugin/index.go @@ -32,9 +32,9 @@ type Index struct { } `yaml:"plugins"` } -func (m *Manager) Update(ctx context.Context) error { +func (m *Manager) Update(ctx context.Context, opts Options) error { m.logger.InfoContext(ctx, "Updating the plugin index...", log.String("url", m.indexURL)) - if err := downloader.Download(ctx, m.indexURL, filepath.Dir(m.indexPath), ""); err != nil { + if err := downloader.Download(ctx, m.indexURL, filepath.Dir(m.indexPath), "", opts.Insecure); err != nil { return xerrors.Errorf("unable to download the plugin index: %w", err) } return nil @@ -69,10 +69,10 @@ func (m *Manager) Search(ctx context.Context, keyword string) error { // tryIndex returns the repository URL if the plugin name is found in the index. // Otherwise, it returns the input name. -func (m *Manager) tryIndex(ctx context.Context, name string) string { +func (m *Manager) tryIndex(ctx context.Context, name string, opts Options) string { // If the index file does not exist, download it first. if !fsutils.FileExists(m.indexPath) { - if err := m.Update(ctx); err != nil { + if err := m.Update(ctx, opts); err != nil { m.logger.ErrorContext(ctx, "Failed to update the plugin index", log.Err(err)) return name } diff --git a/pkg/plugin/index_test.go b/pkg/plugin/index_test.go index 4fbd4c9411aa..5e3f4cd017bd 100644 --- a/pkg/plugin/index_test.go +++ b/pkg/plugin/index_test.go @@ -26,7 +26,7 @@ func TestManager_Update(t *testing.T) { t.Cleanup(ts.Close) manager := plugin.NewManager(plugin.WithIndexURL(ts.URL + "/index.yaml")) - err := manager.Update(context.Background()) + err := manager.Update(context.Background(), plugin.Options{}) require.NoError(t, err) indexPath := filepath.Join(tempDir, ".trivy", "plugins", "index.yaml") diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index a3a806f8f42b..c0f9bf431c87 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -92,13 +92,13 @@ func Upgrade(ctx context.Context, names []string) error { return defaultManager( func Uninstall(ctx context.Context, name string) error { return defaultManager().Uninstall(ctx, name) } func Information(name string) error { return defaultManager().Information(name) } func List(ctx context.Context) error { return defaultManager().List(ctx) } -func Update(ctx context.Context) error { return defaultManager().Update(ctx) } +func Update(ctx context.Context, opts Options) error { return defaultManager().Update(ctx, opts) } func Search(ctx context.Context, keyword string) error { return defaultManager().Search(ctx, keyword) } // Install installs a plugin func (m *Manager) Install(ctx context.Context, arg string, opts Options) (Plugin, error) { input := m.parseArg(ctx, arg) - input.name = m.tryIndex(ctx, input.name) + input.name = m.tryIndex(ctx, input.name, opts) // If the plugin is already installed, it skips installing the plugin. if p, installed := m.isInstalled(ctx, input.name, input.version); installed { @@ -111,7 +111,7 @@ func (m *Manager) Install(ctx context.Context, arg string, opts Options) (Plugin } func (m *Manager) install(ctx context.Context, src string, opts Options) (Plugin, error) { - tempDir, err := downloader.DownloadToTempDir(ctx, src) + tempDir, err := downloader.DownloadToTempDir(ctx, src, opts.Insecure) if err != nil { return Plugin{}, xerrors.Errorf("download failed: %w", err) } diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 68a50ae31780..56c33644f854 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -57,6 +57,7 @@ type Options struct { Args []string Stdin io.Reader // For output plugin Platform ftypes.Platform + Insecure bool } func (p *Plugin) Cmd(ctx context.Context, opts Options) (*exec.Cmd, error) { @@ -154,7 +155,7 @@ func (p *Plugin) install(ctx context.Context, dst, pwd string, opts Options) err p.Installed.Platform = lo.FromPtr(platform.Selector) log.DebugContext(ctx, "Downloading the execution file...", log.String("uri", platform.URI)) - if err = downloader.Download(ctx, platform.URI, dst, pwd); err != nil { + if err = downloader.Download(ctx, platform.URI, dst, pwd, opts.Insecure); err != nil { return xerrors.Errorf("unable to download the execution file (%s): %w", platform.URI, err) } return nil From 9045f244541c8e0cdd37247c8ab1fb9aafb1472f Mon Sep 17 00:00:00 2001 From: Jiho Lee Date: Wed, 26 Jun 2024 20:32:44 +0900 Subject: [PATCH 193/352] docs: Add sudo on commands, chmod before mv on install docs (#7009) --- docs/getting-started/installation.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index f92d23e43997..61f6e42871bb 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -61,7 +61,7 @@ brew install trivy Arch Linux Package Repository. ```bash -pacman -S trivy +sudo pacman -S trivy ``` References: @@ -165,15 +165,15 @@ The plugin used by both tools is developped [here](https://github.com/zufardhiya 1. Download the file for your operating system/architecture from [GitHub Release assets](https://github.com/aquasecurity/trivy/releases/tag/{{ git.tag }}). 2. Unpack the downloaded archive (`tar -xzf ./trivy.tar.gz`). -3. Put the binary somewhere in your `$PATH` (e.g `mv ./trivy /usr/local/bin/`). -4. Make sure the binary has execution bit turned on (`chmod +x ./trivy`). +3. Make sure the binary has execution bit turned on (`chmod +x ./trivy`). +4. Put the binary somewhere in your `$PATH` (e.g `sudo mv ./trivy /usr/local/bin/`). ### Install Script The process above can be automated by the following script: ```bash -curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin {{ git.tag }} +curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin {{ git.tag }} ``` ### Install from source From 0ccdbfbb6598a52de7cda603ab22e794f710e86c Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Wed, 26 Jun 2024 14:06:49 +0200 Subject: [PATCH 194/352] chore: enable float-compare rule from testifylint (#6967) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- .golangci.yaml | 3 -- .../arm/parser/armjson/parse_number_test.go | 8 +-- .../arm/parser/armjson/parse_object_test.go | 4 +- .../cloudformation/parser/parser_test.go | 2 +- pkg/iac/scanners/json/parser/parser_test.go | 2 +- pkg/parallel/pipeline_test.go | 51 +++++++++---------- 6 files changed, 33 insertions(+), 37 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 17efa8050e11..95c66cc69f73 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -84,9 +84,6 @@ linters-settings: ignore-generated-header: true testifylint: enable-all: true - disable: - - float-compare - linters: disable-all: true enable: diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_number_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_number_test.go index f0f7532c15a4..d2acf3b0a8d9 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_number_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_number_test.go @@ -24,7 +24,7 @@ func Test_Number_IntToFloat(t *testing.T) { metadata := types.NewTestMetadata() err := Unmarshal(example, &output, &metadata) require.NoError(t, err) - assert.Equal(t, 123.0, output) + assert.InEpsilon(t, 123.0, output, 0.0001) } func Test_Number_FloatToFloat(t *testing.T) { @@ -33,7 +33,7 @@ func Test_Number_FloatToFloat(t *testing.T) { metadata := types.NewTestMetadata() err := Unmarshal(example, &output, &metadata) require.NoError(t, err) - assert.Equal(t, 123.456, output) + assert.InEpsilon(t, 123.456, output, 0.0001) } func Test_Number_FloatToInt(t *testing.T) { @@ -42,7 +42,7 @@ func Test_Number_FloatToInt(t *testing.T) { metadata := types.NewTestMetadata() err := Unmarshal(example, &output, &metadata) require.NoError(t, err) - assert.Equal(t, 123, output) + assert.InEpsilon(t, 123, output, 0.0001) } func Test_Number_FloatWithExponent(t *testing.T) { @@ -70,7 +70,7 @@ func Test_Number_FloatWithExponent(t *testing.T) { metadata := types.NewTestMetadata() err := Unmarshal(example, &output, &metadata) require.NoError(t, err) - assert.Equal(t, test.out, output) + assert.InEpsilon(t, test.out, output, 0.0001) }) } diff --git a/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go b/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go index 2971ae73de21..31ea685cef23 100644 --- a/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go +++ b/pkg/iac/scanners/azure/arm/parser/armjson/parse_object_test.go @@ -21,7 +21,7 @@ func Test_Object(t *testing.T) { metadata := types.NewTestMetadata() require.NoError(t, Unmarshal(example, &target, &metadata)) assert.Equal(t, "testing", target.Name) - assert.Equal(t, 3.14, target.Balance) + assert.InEpsilon(t, 3.14, target.Balance, 0.0001) } func Test_ObjectWithPointers(t *testing.T) { @@ -36,7 +36,7 @@ func Test_ObjectWithPointers(t *testing.T) { metadata := types.NewTestMetadata() require.NoError(t, Unmarshal(example, &target, &metadata)) assert.Equal(t, "testing", *target.Name) - assert.Equal(t, 3.14, *target.Balance) + assert.InEpsilon(t, 3.14, *target.Balance, 0.0001) } type nestedParent struct { diff --git a/pkg/iac/scanners/cloudformation/parser/parser_test.go b/pkg/iac/scanners/cloudformation/parser/parser_test.go index 396e12f1bf57..0d37440e2e1a 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser_test.go +++ b/pkg/iac/scanners/cloudformation/parser/parser_test.go @@ -410,7 +410,7 @@ func TestJsonWithNumbers(t *testing.T) { file := files[0] assert.Equal(t, 1, file.Parameters["SomeIntParam"].Default()) - assert.Equal(t, 1.1, file.Parameters["SomeFloatParam"].Default()) + assert.InEpsilon(t, 1.1, file.Parameters["SomeFloatParam"].Default(), 0.0001) res := file.GetResourcesByType("Test::Resource") assert.NotNil(t, res) diff --git a/pkg/iac/scanners/json/parser/parser_test.go b/pkg/iac/scanners/json/parser/parser_test.go index ed7b87492d96..a47868fad0ed 100644 --- a/pkg/iac/scanners/json/parser/parser_test.go +++ b/pkg/iac/scanners/json/parser/parser_test.go @@ -34,7 +34,7 @@ func Test_Parser(t *testing.T) { y, ok := yRaw.(float64) require.True(t, ok) - assert.Equal(t, 123.0, y) + assert.InEpsilon(t, 123.0, y, 0.0001) zRaw, ok := xMsi["z"] require.True(t, ok) diff --git a/pkg/parallel/pipeline_test.go b/pkg/parallel/pipeline_test.go index 60b8cec100ab..4fb008c9e00e 100644 --- a/pkg/parallel/pipeline_test.go +++ b/pkg/parallel/pipeline_test.go @@ -3,7 +3,6 @@ package parallel_test import ( "context" "fmt" - "math" "testing" "github.com/stretchr/testify/assert" @@ -15,13 +14,13 @@ import ( func TestPipeline_Do(t *testing.T) { type field struct { numWorkers int - items []float64 - onItem func(context.Context, float64) (float64, error) + items []int + onItem func(context.Context, int) (int, error) } type testCase struct { name string field field - want float64 + want int wantErr require.ErrorAssertionFunc } tests := []testCase{ @@ -29,7 +28,7 @@ func TestPipeline_Do(t *testing.T) { name: "pow", field: field{ numWorkers: 5, - items: []float64{ + items: []int{ 1, 2, 3, @@ -41,44 +40,44 @@ func TestPipeline_Do(t *testing.T) { 9, 10, }, - onItem: func(_ context.Context, f float64) (float64, error) { - return math.Pow(f, 2), nil + onItem: func(_ context.Context, i int) (int, error) { + return i * i, nil }, }, want: 385, wantErr: require.NoError, }, { - name: "ceil", + name: "double", field: field{ numWorkers: 3, - items: []float64{ - 1.1, - 2.2, - 3.3, - 4.4, - 5.5, - -1.1, - -2.2, - -3.3, + items: []int{ + 1, + 2, + 3, + 4, + 5, + -1, + -2, + -3, }, - onItem: func(_ context.Context, f float64) (float64, error) { - return math.Round(f), nil + onItem: func(_ context.Context, i int) (int, error) { + return i * 2, nil }, }, - want: 10, + want: 18, wantErr: require.NoError, }, { name: "error in series", field: field{ numWorkers: 1, - items: []float64{ + items: []int{ 1, 2, 3, }, - onItem: func(_ context.Context, f float64) (float64, error) { + onItem: func(_ context.Context, _ int) (int, error) { return 0, fmt.Errorf("error") }, }, @@ -88,11 +87,11 @@ func TestPipeline_Do(t *testing.T) { name: "error in parallel", field: field{ numWorkers: 3, - items: []float64{ + items: []int{ 1, 2, }, - onItem: func(_ context.Context, f float64) (float64, error) { + onItem: func(_ context.Context, _ int) (int, error) { return 0, fmt.Errorf("error") }, }, @@ -101,8 +100,8 @@ func TestPipeline_Do(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var got float64 - p := parallel.NewPipeline(tt.field.numWorkers, false, tt.field.items, tt.field.onItem, func(f float64) error { + var got int + p := parallel.NewPipeline(tt.field.numWorkers, false, tt.field.items, tt.field.onItem, func(f int) error { got += f return nil }) From e9fc3e3397564512038ddeca2adce0efcb3f93c5 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 27 Jun 2024 10:13:32 +0400 Subject: [PATCH 195/352] fix(cli): show info message only when --scanners is available (#7032) Signed-off-by: knqyf263 --- pkg/flag/options.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 8f15480ccfec..70470dc05e2f 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -364,8 +364,10 @@ type Options struct { } // Align takes consistency of options -func (o *Options) Align() error { - o.enableSBOM() +func (o *Options) Align(f *Flags) error { + if f.ScanFlagGroup != nil && f.ScanFlagGroup.Scanners != nil { + o.enableSBOM() + } if o.Compliance.Spec.ID != "" { if viper.IsSet(ScannersFlag.ConfigName) { @@ -749,7 +751,7 @@ func (f *Flags) ToOptions(args []string) (Options, error) { } } - if err := opts.Align(); err != nil { + if err := opts.Align(f); err != nil { return Options{}, xerrors.Errorf("align options error: %w", err) } From 4be02bab8c45f4f387e32b7a3f718ea7a56d2192 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 27 Jun 2024 11:04:01 +0400 Subject: [PATCH 196/352] refactor: use google/wire for cache (#7024) Signed-off-by: knqyf263 --- pkg/cache/cache.go | 2 +- pkg/cache/client.go | 167 ++++++------------------------ pkg/cache/client_test.go | 150 +++++++++++++-------------- pkg/cache/fs.go | 7 +- pkg/cache/fs_test.go | 3 +- pkg/cache/redis.go | 117 +++++++++++++++++++-- pkg/cache/redis_test.go | 91 ++++++++-------- pkg/cache/remote.go | 16 ++- pkg/cache/remote_test.go | 24 ++++- pkg/commands/artifact/inject.go | 37 +++---- pkg/commands/artifact/run.go | 67 +++--------- pkg/commands/artifact/scanner.go | 29 +++--- pkg/commands/artifact/wire_gen.go | 122 +++++++++++++++------- pkg/commands/clean/run.go | 4 +- pkg/commands/clean/run_test.go | 4 + pkg/commands/server/run.go | 5 +- pkg/flag/cache_flags.go | 29 +++--- pkg/flag/global_flags.go | 3 + pkg/flag/options.go | 23 ++++ pkg/k8s/wire_gen.go | 2 +- pkg/rpc/server/wire_gen.go | 2 +- pkg/scanner/scan.go | 10 ++ 22 files changed, 496 insertions(+), 418 deletions(-) diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index b2f5fa704ae7..1280c84fe156 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -5,7 +5,7 @@ import ( ) const ( - cacheDirName = "fanal" + scanCacheDirName = "fanal" // artifactBucket stores artifact information with artifact ID such as image ID artifactBucket = "artifact" diff --git a/pkg/cache/client.go b/pkg/cache/client.go index ab9dd4799428..46bced1771aa 100644 --- a/pkg/cache/client.go +++ b/pkg/cache/client.go @@ -1,166 +1,65 @@ package cache import ( - "crypto/tls" - "crypto/x509" - "fmt" - "os" "strings" "time" - "github.com/go-redis/redis/v8" - "github.com/samber/lo" "golang.org/x/xerrors" - - "github.com/aquasecurity/trivy/pkg/log" ) const ( - TypeFS Type = "fs" - TypeRedis Type = "redis" + TypeUnknown Type = "unknown" + TypeFS Type = "fs" + TypeRedis Type = "redis" ) type Type string type Options struct { - Type Type - TTL time.Duration - Redis RedisOptions -} - -func NewOptions(backend, redisCACert, redisCert, redisKey string, redisTLS bool, ttl time.Duration) (Options, error) { - t, err := NewType(backend) - if err != nil { - return Options{}, xerrors.Errorf("cache type error: %w", err) - } - - var redisOpts RedisOptions - if t == TypeRedis { - redisTLSOpts, err := NewRedisTLSOptions(redisCACert, redisCert, redisKey) - if err != nil { - return Options{}, xerrors.Errorf("redis TLS option error: %w", err) - } - redisOpts = RedisOptions{ - Backend: backend, - TLS: redisTLS, - TLSOptions: redisTLSOpts, - } - } else if ttl != 0 { - log.Warn("'--cache-ttl' is only available with Redis cache backend") - } - - return Options{ - Type: t, - TTL: ttl, - Redis: redisOpts, - }, nil + Backend string + CacheDir string + RedisCACert string + RedisCert string + RedisKey string + RedisTLS bool + TTL time.Duration } -type RedisOptions struct { - Backend string - TLS bool - TLSOptions RedisTLSOptions -} - -// BackendMasked returns the redis connection string masking credentials -func (o *RedisOptions) BackendMasked() string { - endIndex := strings.Index(o.Backend, "@") - if endIndex == -1 { - return o.Backend - } - - startIndex := strings.Index(o.Backend, "//") - - return fmt.Sprintf("%s****%s", o.Backend[:startIndex+2], o.Backend[endIndex:]) -} - -// RedisTLSOptions holds the options for redis cache -type RedisTLSOptions struct { - CACert string - Cert string - Key string -} - -func NewRedisTLSOptions(caCert, cert, key string) (RedisTLSOptions, error) { - opts := RedisTLSOptions{ - CACert: caCert, - Cert: cert, - Key: key, - } - - // If one of redis option not nil, make sure CA, cert, and key provided - if !lo.IsEmpty(opts) { - if opts.CACert == "" || opts.Cert == "" || opts.Key == "" { - return RedisTLSOptions{}, xerrors.Errorf("you must provide Redis CA, cert and key file path when using TLS") - } - } - return opts, nil -} - -func NewType(backend string) (Type, error) { +func NewType(backend string) Type { // "redis://" or "fs" are allowed for now // An empty value is also allowed for testability switch { case strings.HasPrefix(backend, "redis://"): - return TypeRedis, nil + return TypeRedis case backend == "fs", backend == "": - return TypeFS, nil + return TypeFS default: - return "", xerrors.Errorf("unknown cache backend: %s", backend) + return TypeUnknown } } // New returns a new cache client -func New(dir string, opts Options) (Cache, error) { - if opts.Type == TypeRedis { - log.Info("Redis cache", log.String("url", opts.Redis.BackendMasked())) - options, err := redis.ParseURL(opts.Redis.Backend) +func New(opts Options) (Cache, func(), error) { + cleanup := func() {} // To avoid panic + + var cache Cache + t := NewType(opts.Backend) + switch t { + case TypeRedis: + redisCache, err := NewRedisCache(opts.Backend, opts.RedisCACert, opts.RedisCert, opts.RedisKey, opts.RedisTLS, opts.TTL) if err != nil { - return nil, err + return nil, cleanup, xerrors.Errorf("unable to initialize redis cache: %w", err) } - - if tlsOpts := opts.Redis.TLSOptions; !lo.IsEmpty(tlsOpts) { - caCert, cert, err := GetTLSConfig(tlsOpts.CACert, tlsOpts.Cert, tlsOpts.Key) - if err != nil { - return nil, err - } - - options.TLSConfig = &tls.Config{ - RootCAs: caCert, - Certificates: []tls.Certificate{cert}, - MinVersion: tls.VersionTLS12, - } - } else if opts.Redis.TLS { - options.TLSConfig = &tls.Config{ - MinVersion: tls.VersionTLS12, - } + cache = redisCache + case TypeFS: + // standalone mode + fsCache, err := NewFSCache(opts.CacheDir) + if err != nil { + return nil, cleanup, xerrors.Errorf("unable to initialize fs cache: %w", err) } - - return NewRedisCache(options, opts.TTL), nil - } - - // standalone mode - fsCache, err := NewFSCache(dir) - if err != nil { - return nil, xerrors.Errorf("unable to initialize fs cache: %w", err) - } - return fsCache, nil -} - -// GetTLSConfig gets tls config from CA, Cert and Key file -func GetTLSConfig(caCertPath, certPath, keyPath string) (*x509.CertPool, tls.Certificate, error) { - cert, err := tls.LoadX509KeyPair(certPath, keyPath) - if err != nil { - return nil, tls.Certificate{}, err - } - - caCert, err := os.ReadFile(caCertPath) - if err != nil { - return nil, tls.Certificate{}, err + cache = fsCache + default: + return nil, cleanup, xerrors.Errorf("unknown cache type: %s", t) } - - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) - - return caCertPool, cert, nil + return cache, func() { _ = cache.Close() }, nil } diff --git a/pkg/cache/client_test.go b/pkg/cache/client_test.go index f22ce4f93e2f..c72eb3de4d13 100644 --- a/pkg/cache/client_test.go +++ b/pkg/cache/client_test.go @@ -2,7 +2,6 @@ package cache_test import ( "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -10,120 +9,113 @@ import ( "github.com/aquasecurity/trivy/pkg/cache" ) -func TestNewOptions(t *testing.T) { - type args struct { - backend string - redisCACert string - redisCert string - redisKey string - redisTLS bool - ttl time.Duration - } +func TestNew(t *testing.T) { tests := []struct { - name string - args args - want cache.Options - assertion require.ErrorAssertionFunc + name string + opts cache.Options + wantType any + wantErr string }{ { - name: "fs", - args: args{backend: "fs"}, - want: cache.Options{Type: cache.TypeFS}, - assertion: require.NoError, + name: "fs backend", + opts: cache.Options{ + Backend: "fs", + CacheDir: "/tmp/cache", + }, + wantType: cache.FSCache{}, }, { - name: "redis", - args: args{backend: "redis://localhost:6379"}, - want: cache.Options{ - Type: cache.TypeRedis, - Redis: cache.RedisOptions{Backend: "redis://localhost:6379"}, + name: "redis backend", + opts: cache.Options{ + Backend: "redis://localhost:6379", }, - assertion: require.NoError, + wantType: cache.RedisCache{}, }, { - name: "redis tls", - args: args{ - backend: "redis://localhost:6379", - redisCACert: "ca-cert.pem", - redisCert: "cert.pem", - redisKey: "key.pem", - }, - want: cache.Options{ - Type: cache.TypeRedis, - Redis: cache.RedisOptions{ - Backend: "redis://localhost:6379", - TLSOptions: cache.RedisTLSOptions{ - CACert: "ca-cert.pem", - Cert: "cert.pem", - Key: "key.pem", - }, - }, + name: "unknown backend", + opts: cache.Options{ + Backend: "unknown", }, - assertion: require.NoError, + wantErr: "unknown cache type", }, { - name: "redis tls with public certificates", - args: args{ - backend: "redis://localhost:6379", - redisTLS: true, + name: "invalid redis URL", + opts: cache.Options{ + Backend: "redis://invalid-url:foo/bar", }, - want: cache.Options{ - Type: cache.TypeRedis, - Redis: cache.RedisOptions{ - Backend: "redis://localhost:6379", - TLS: true, - }, - }, - assertion: require.NoError, + wantErr: "failed to parse Redis URL", }, { - name: "unknown backend", - args: args{backend: "unknown"}, - assertion: func(t require.TestingT, err error, msgs ...any) { - require.ErrorContains(t, err, "unknown cache backend") + name: "incomplete TLS options", + opts: cache.Options{ + Backend: "redis://localhost:6379", + RedisCACert: "testdata/ca-cert.pem", + RedisTLS: true, }, + wantErr: "you must provide Redis CA, cert and key file path when using TLS", }, { - name: "sad redis tls", - args: args{ - backend: "redis://localhost:6379", - redisCACert: "ca-cert.pem", - }, - assertion: func(t require.TestingT, err error, msgs ...any) { - require.ErrorContains(t, err, "you must provide Redis CA, cert and key file path when using TLS") + name: "invalid TLS file paths", + opts: cache.Options{ + Backend: "redis://localhost:6379", + RedisCACert: "testdata/non-existent-ca-cert.pem", + RedisCert: "testdata/non-existent-cert.pem", + RedisKey: "testdata/non-existent-key.pem", + RedisTLS: true, }, + wantErr: "failed to get TLS config", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := cache.NewOptions(tt.args.backend, tt.args.redisCACert, tt.args.redisCert, tt.args.redisKey, tt.args.redisTLS, tt.args.ttl) - tt.assertion(t, err) - assert.Equal(t, tt.want, got) + c, cleanup, err := cache.New(tt.opts) + defer cleanup() + + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + require.NoError(t, err) + assert.NotNil(t, c) + assert.IsType(t, tt.wantType, c) }) } } -func TestRedisOptions_BackendMasked(t *testing.T) { +func TestNewType(t *testing.T) { tests := []struct { - name string - fields cache.RedisOptions - want string + name string + backend string + wantType cache.Type }{ { - name: "redis cache backend masked", - fields: cache.RedisOptions{Backend: "redis://root:password@localhost:6379"}, - want: "redis://****@localhost:6379", + name: "redis backend", + backend: "redis://localhost:6379", + wantType: cache.TypeRedis, + }, + { + name: "fs backend", + backend: "fs", + wantType: cache.TypeFS, }, { - name: "redis cache backend masked does nothing", - fields: cache.RedisOptions{Backend: "redis://localhost:6379"}, - want: "redis://localhost:6379", + name: "empty backend", + backend: "", + wantType: cache.TypeFS, + }, + { + name: "unknown backend", + backend: "unknown", + wantType: cache.TypeUnknown, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, tt.fields.BackendMasked()) + got := cache.NewType(tt.backend) + assert.Equal(t, tt.wantType, got) }) } } diff --git a/pkg/cache/fs.go b/pkg/cache/fs.go index 08fa696e6555..edfac70b04e5 100644 --- a/pkg/cache/fs.go +++ b/pkg/cache/fs.go @@ -20,7 +20,7 @@ type FSCache struct { } func NewFSCache(cacheDir string) (FSCache, error) { - dir := filepath.Join(cacheDir, cacheDirName) + dir := filepath.Join(cacheDir, scanCacheDirName) if err := os.MkdirAll(dir, 0700); err != nil { return FSCache{}, xerrors.Errorf("failed to create cache dir: %w", err) } @@ -31,7 +31,10 @@ func NewFSCache(cacheDir string) (FSCache, error) { } err = db.Update(func(tx *bolt.Tx) error { - for _, bucket := range []string{artifactBucket, blobBucket} { + for _, bucket := range []string{ + artifactBucket, + blobBucket, + } { if _, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil { return xerrors.Errorf("unable to create %s bucket: %w", bucket, err) } diff --git a/pkg/cache/fs_test.go b/pkg/cache/fs_test.go index 4eb059f5c508..9323391a3af4 100644 --- a/pkg/cache/fs_test.go +++ b/pkg/cache/fs_test.go @@ -373,7 +373,7 @@ func TestFSCache_PutArtifact(t *testing.T) { require.NoError(t, err, tt.name) } - fs.db.View(func(tx *bolt.Tx) error { + err = fs.db.View(func(tx *bolt.Tx) error { // check decompressedDigestBucket imageBucket := tx.Bucket([]byte(artifactBucket)) b := imageBucket.Get([]byte(tt.args.imageID)) @@ -381,6 +381,7 @@ func TestFSCache_PutArtifact(t *testing.T) { return nil }) + require.NoError(t, err) }) } } diff --git a/pkg/cache/redis.go b/pkg/cache/redis.go index af9d2622b531..2a4a12bda3f7 100644 --- a/pkg/cache/redis.go +++ b/pkg/cache/redis.go @@ -2,33 +2,118 @@ package cache import ( "context" + "crypto/tls" + "crypto/x509" "encoding/json" "fmt" + "os" + "strings" "time" "github.com/go-redis/redis/v8" "github.com/hashicorp/go-multierror" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ Cache = &RedisCache{} +var _ Cache = (*RedisCache)(nil) -const ( - redisPrefix = "fanal" -) +const redisPrefix = "fanal" + +type RedisOptions struct { + Backend string + TLS bool + TLSOptions RedisTLSOptions +} + +func NewRedisOptions(backend, caCert, cert, key string, enableTLS bool) (RedisOptions, error) { + tlsOpts, err := NewRedisTLSOptions(caCert, cert, key) + if err != nil { + return RedisOptions{}, xerrors.Errorf("redis TLS option error: %w", err) + } + + return RedisOptions{ + Backend: backend, + TLS: enableTLS, + TLSOptions: tlsOpts, + }, nil +} + +// BackendMasked returns the redis connection string masking credentials +func (o *RedisOptions) BackendMasked() string { + endIndex := strings.Index(o.Backend, "@") + if endIndex == -1 { + return o.Backend + } + + startIndex := strings.Index(o.Backend, "//") + + return fmt.Sprintf("%s****%s", o.Backend[:startIndex+2], o.Backend[endIndex:]) +} + +// RedisTLSOptions holds the options for redis cache +type RedisTLSOptions struct { + CACert string + Cert string + Key string +} + +func NewRedisTLSOptions(caCert, cert, key string) (RedisTLSOptions, error) { + opts := RedisTLSOptions{ + CACert: caCert, + Cert: cert, + Key: key, + } + + // If one of redis option not nil, make sure CA, cert, and key provided + if !lo.IsEmpty(opts) { + if opts.CACert == "" || opts.Cert == "" || opts.Key == "" { + return RedisTLSOptions{}, xerrors.Errorf("you must provide Redis CA, cert and key file path when using TLS") + } + } + return opts, nil +} type RedisCache struct { client *redis.Client expiration time.Duration } -func NewRedisCache(options *redis.Options, expiration time.Duration) RedisCache { +func NewRedisCache(backend, caCertPath, certPath, keyPath string, enableTLS bool, ttl time.Duration) (RedisCache, error) { + opts, err := NewRedisOptions(backend, caCertPath, certPath, keyPath, enableTLS) + if err != nil { + return RedisCache{}, xerrors.Errorf("failed to create Redis options: %w", err) + } + + log.Info("Redis scan cache", log.String("url", opts.BackendMasked())) + options, err := redis.ParseURL(opts.Backend) + if err != nil { + return RedisCache{}, xerrors.Errorf("failed to parse Redis URL: %w", err) + } + + if tlsOpts := opts.TLSOptions; !lo.IsEmpty(tlsOpts) { + caCert, cert, err := GetTLSConfig(tlsOpts.CACert, tlsOpts.Cert, tlsOpts.Key) + if err != nil { + return RedisCache{}, xerrors.Errorf("failed to get TLS config: %w", err) + } + + options.TLSConfig = &tls.Config{ + RootCAs: caCert, + Certificates: []tls.Certificate{cert}, + MinVersion: tls.VersionTLS12, + } + } else if opts.TLS { + options.TLSConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + } + } return RedisCache{ client: redis.NewClient(options), - expiration: expiration, - } + expiration: ttl, + }, nil } func (c RedisCache) PutArtifact(artifactID string, artifactConfig types.ArtifactInfo) error { @@ -145,3 +230,21 @@ func (c RedisCache) Clear() error { } return nil } + +// GetTLSConfig gets tls config from CA, Cert and Key file +func GetTLSConfig(caCertPath, certPath, keyPath string) (*x509.CertPool, tls.Certificate, error) { + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, tls.Certificate{}, err + } + + caCert, err := os.ReadFile(caCertPath) + if err != nil { + return nil, tls.Certificate{}, err + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + return caCertPool, cert, nil +} diff --git a/pkg/cache/redis_test.go b/pkg/cache/redis_test.go index 46716a7d7bfe..3cc8bbd702ad 100644 --- a/pkg/cache/redis_test.go +++ b/pkg/cache/redis_test.go @@ -7,7 +7,6 @@ import ( "time" "github.com/alicebob/miniredis/v2" - "github.com/go-redis/redis/v8" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -67,18 +66,15 @@ func TestRedisCache_PutArtifact(t *testing.T) { addr = "dummy:16379" } - c := cache.NewRedisCache(&redis.Options{ - Addr: addr, - }, 0) + c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", addr), "", "", "", false, 0) + require.NoError(t, err) err = c.PutArtifact(tt.args.artifactID, tt.args.artifactConfig) if tt.wantErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr) + require.ErrorContains(t, err, tt.wantErr) return - } else { - require.NoError(t, err) } + require.NoError(t, err) got, err := s.Get(tt.wantKey) require.NoError(t, err) @@ -156,18 +152,15 @@ func TestRedisCache_PutBlob(t *testing.T) { addr = "dummy:16379" } - c := cache.NewRedisCache(&redis.Options{ - Addr: addr, - }, 0) + c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", addr), "", "", "", false, 0) + require.NoError(t, err) err = c.PutBlob(tt.args.blobID, tt.args.blobConfig) if tt.wantErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr) + require.ErrorContains(t, err, tt.wantErr) return - } else { - require.NoError(t, err) } + require.NoError(t, err) got, err := s.Get(tt.wantKey) require.NoError(t, err) @@ -241,18 +234,15 @@ func TestRedisCache_GetArtifact(t *testing.T) { addr = "dummy:16379" } - c := cache.NewRedisCache(&redis.Options{ - Addr: addr, - }, 0) + c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", addr), "", "", "", false, 0) + require.NoError(t, err) got, err := c.GetArtifact(tt.artifactID) if tt.wantErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr) + require.ErrorContains(t, err, tt.wantErr) return - } else { - require.NoError(t, err) } + require.NoError(t, err) assert.Equal(t, tt.want, got) }) @@ -334,14 +324,12 @@ func TestRedisCache_GetBlob(t *testing.T) { addr = "dummy:16379" } - c := cache.NewRedisCache(&redis.Options{ - Addr: addr, - }, 0) + c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", addr), "", "", "", false, 0) + require.NoError(t, err) got, err := c.GetBlob(tt.blobID) if tt.wantErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr) + require.ErrorContains(t, err, tt.wantErr) return } @@ -445,14 +433,12 @@ func TestRedisCache_MissingBlobs(t *testing.T) { addr = "dummy:6379" } - c := cache.NewRedisCache(&redis.Options{ - Addr: addr, - }, 0) + c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", addr), "", "", "", false, 0) + require.NoError(t, err) missingArtifact, missingBlobIDs, err := c.MissingBlobs(tt.args.artifactID, tt.args.blobIDs) if tt.wantErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr) + require.ErrorContains(t, err, tt.wantErr) return } @@ -470,9 +456,9 @@ func TestRedisCache_Close(t *testing.T) { defer s.Close() t.Run("close", func(t *testing.T) { - c := cache.NewRedisCache(&redis.Options{ - Addr: s.Addr(), - }, 0) + c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", s.Addr()), "", "", "", false, 0) + require.NoError(t, err) + closeErr := c.Close() require.NoError(t, closeErr) time.Sleep(3 * time.Second) // give it some time @@ -492,9 +478,9 @@ func TestRedisCache_Clear(t *testing.T) { s.Set("foo", "bar") t.Run("clear", func(t *testing.T) { - c := cache.NewRedisCache(&redis.Options{ - Addr: s.Addr(), - }, 0) + c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", s.Addr()), "", "", "", false, 0) + require.NoError(t, err) + require.NoError(t, c.Clear()) for i := 0; i < 200; i++ { assert.False(t, s.Exists(fmt.Sprintf("fanal::key%d", i))) @@ -546,9 +532,8 @@ func TestRedisCache_DeleteBlobs(t *testing.T) { addr = "dummy:16379" } - c := cache.NewRedisCache(&redis.Options{ - Addr: addr, - }, 0) + c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", addr), "", "", "", false, 0) + require.NoError(t, err) err = c.DeleteBlobs(tt.args.blobIDs) if tt.wantErr != "" { @@ -560,3 +545,27 @@ func TestRedisCache_DeleteBlobs(t *testing.T) { }) } } + +func TestRedisOptions_BackendMasked(t *testing.T) { + tests := []struct { + name string + fields cache.RedisOptions + want string + }{ + { + name: "redis cache backend masked", + fields: cache.RedisOptions{Backend: "redis://root:password@localhost:6379"}, + want: "redis://****@localhost:6379", + }, + { + name: "redis cache backend masked does nothing", + fields: cache.RedisOptions{Backend: "redis://localhost:6379"}, + want: "redis://localhost:6379", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.fields.BackendMasked()) + }) + } +} diff --git a/pkg/cache/remote.go b/pkg/cache/remote.go index f55d877d5f3d..44c9f63c92d8 100644 --- a/pkg/cache/remote.go +++ b/pkg/cache/remote.go @@ -13,6 +13,14 @@ import ( rpcCache "github.com/aquasecurity/trivy/rpc/cache" ) +var _ ArtifactCache = (*RemoteCache)(nil) + +type RemoteOptions struct { + ServerAddr string + CustomHeaders http.Header + Insecure bool +} + // RemoteCache implements remote cache type RemoteCache struct { ctx context.Context // for custom header @@ -20,18 +28,18 @@ type RemoteCache struct { } // NewRemoteCache is the factory method for RemoteCache -func NewRemoteCache(url string, customHeaders http.Header, insecure bool) ArtifactCache { - ctx := client.WithCustomHeaders(context.Background(), customHeaders) +func NewRemoteCache(opts RemoteOptions) *RemoteCache { + ctx := client.WithCustomHeaders(context.Background(), opts.CustomHeaders) httpClient := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{ - InsecureSkipVerify: insecure, + InsecureSkipVerify: opts.Insecure, }, }, } - c := rpcCache.NewCacheProtobufClient(url, httpClient) + c := rpcCache.NewCacheProtobufClient(opts.ServerAddr, httpClient) return &RemoteCache{ ctx: ctx, client: c, diff --git a/pkg/cache/remote_test.go b/pkg/cache/remote_test.go index bbfd72e3b20d..3e1363d5dd4d 100644 --- a/pkg/cache/remote_test.go +++ b/pkg/cache/remote_test.go @@ -145,7 +145,11 @@ func TestRemoteCache_PutArtifact(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) + c := cache.NewRemoteCache(cache.RemoteOptions{ + ServerAddr: ts.URL, + CustomHeaders: tt.args.customHeaders, + Insecure: false, + }) err := c.PutArtifact(tt.args.imageID, tt.args.imageInfo) if tt.wantErr != "" { require.Error(t, err, tt.name) @@ -206,7 +210,11 @@ func TestRemoteCache_PutBlob(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) + c := cache.NewRemoteCache(cache.RemoteOptions{ + ServerAddr: ts.URL, + CustomHeaders: tt.args.customHeaders, + Insecure: false, + }) err := c.PutBlob(tt.args.diffID, tt.args.layerInfo) if tt.wantErr != "" { require.Error(t, err, tt.name) @@ -284,7 +292,11 @@ func TestRemoteCache_MissingBlobs(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) + c := cache.NewRemoteCache(cache.RemoteOptions{ + ServerAddr: ts.URL, + CustomHeaders: tt.args.customHeaders, + Insecure: false, + }) gotMissingImage, gotMissingLayerIDs, err := c.MissingBlobs(tt.args.imageID, tt.args.layerIDs) if tt.wantErr != "" { require.Error(t, err, tt.name) @@ -334,7 +346,11 @@ func TestRemoteCache_PutArtifactInsecure(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := cache.NewRemoteCache(ts.URL, nil, tt.args.insecure) + c := cache.NewRemoteCache(cache.RemoteOptions{ + ServerAddr: ts.URL, + CustomHeaders: nil, + Insecure: tt.args.insecure, + }) err := c.PutArtifact(tt.args.imageID, tt.args.imageInfo) if tt.wantErr != "" { require.Error(t, err) diff --git a/pkg/commands/artifact/inject.go b/pkg/commands/artifact/inject.go index 174af5045f74..da2c05ac91e4 100644 --- a/pkg/commands/artifact/inject.go +++ b/pkg/commands/artifact/inject.go @@ -21,8 +21,7 @@ import ( // initializeImageScanner is for container image scanning in standalone mode // e.g. dockerd, container registry, podman, etc. -func initializeImageScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, imageOpt types.ImageOptions, artifactOption artifact.Option) ( +func initializeImageScanner(ctx context.Context, imageName string, imageOpt types.ImageOptions, cacheOptions cache.Options, artifactOption artifact.Option) ( scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneDockerSet) return scanner.Scanner{}, nil, nil @@ -30,33 +29,29 @@ func initializeImageScanner(ctx context.Context, imageName string, artifactCache // initializeArchiveScanner is for container image archive scanning in standalone mode // e.g. docker save -o alpine.tar alpine:3.15 -func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, error) { +func initializeArchiveScanner(ctx context.Context, filePath string, cacheOptions cache.Options, artifactOption artifact.Option) ( + scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneArchiveSet) - return scanner.Scanner{}, nil + return scanner.Scanner{}, nil, nil } // initializeFilesystemScanner is for filesystem scanning in standalone mode -func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { +func initializeFilesystemScanner(ctx context.Context, path string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneFilesystemSet) return scanner.Scanner{}, nil, nil } -func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { +func initializeRepositoryScanner(ctx context.Context, url string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneRepositorySet) return scanner.Scanner{}, nil, nil } -func initializeSBOMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { +func initializeSBOMScanner(ctx context.Context, filePath string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneSBOMSet) return scanner.Scanner{}, nil, nil } -func initializeVMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) ( +func initializeVMScanner(ctx context.Context, filePath string, cacheOptions cache.Options, artifactOption artifact.Option) ( scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneVMSet) return scanner.Scanner{}, nil, nil @@ -68,7 +63,7 @@ func initializeVMScanner(ctx context.Context, filePath string, artifactCache cac // initializeRemoteImageScanner is for container image scanning in client/server mode // e.g. dockerd, container registry, podman, etc. -func initializeRemoteImageScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, +func initializeRemoteImageScanner(ctx context.Context, imageName string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, imageOpt types.ImageOptions, artifactOption artifact.Option) ( scanner.Scanner, func(), error) { wire.Build(scanner.RemoteDockerSet) @@ -77,21 +72,21 @@ func initializeRemoteImageScanner(ctx context.Context, imageName string, artifac // initializeRemoteArchiveScanner is for container image archive scanning in client/server mode // e.g. docker save -o alpine.tar alpine:3.15 -func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, - remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, error) { +func initializeRemoteArchiveScanner(ctx context.Context, filePath string, remoteCacheOptions cache.RemoteOptions, + remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { wire.Build(scanner.RemoteArchiveSet) - return scanner.Scanner{}, nil + return scanner.Scanner{}, nil, nil } // initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode -func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, +func initializeRemoteFilesystemScanner(ctx context.Context, path string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { wire.Build(scanner.RemoteFilesystemSet) return scanner.Scanner{}, nil, nil } // initializeRemoteRepositoryScanner is for repository scanning in client/server mode -func initializeRemoteRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, +func initializeRemoteRepositoryScanner(ctx context.Context, url string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) ( scanner.Scanner, func(), error) { wire.Build(scanner.RemoteRepositorySet) @@ -99,14 +94,14 @@ func initializeRemoteRepositoryScanner(ctx context.Context, url string, artifact } // initializeRemoteSBOMScanner is for sbom scanning in client/server mode -func initializeRemoteSBOMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, +func initializeRemoteSBOMScanner(ctx context.Context, path string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { wire.Build(scanner.RemoteSBOMSet) return scanner.Scanner{}, nil, nil } // initializeRemoteVMScanner is for vm scanning in client/server mode -func initializeRemoteVMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, +func initializeRemoteVMScanner(ctx context.Context, path string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { wire.Build(scanner.RemoteVMSet) return scanner.Scanner{}, nil, nil diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index bb2e144c5050..db73cf58b391 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -57,8 +57,8 @@ type ScannerConfig struct { Target string // Cache - ArtifactCache cache.ArtifactCache - LocalArtifactCache cache.LocalArtifactCache + CacheOptions cache.Options + RemoteCacheOptions cache.RemoteOptions // Client/Server options ServerOption client.ScannerOption @@ -89,37 +89,31 @@ type Runner interface { } type runner struct { - cache cache.ArtifactCache - localCache cache.LocalArtifactCache - dbOpen bool + initializeScanner InitializeScanner + dbOpen bool // WASM modules module *module.Manager } -type runnerOption func(*runner) +type RunnerOption func(*runner) -// WithCacheClient takes a custom cache implementation +// WithInitializeScanner takes a custom scanner initialization function. // It is useful when Trivy is imported as a library. -func WithCacheClient(c cache.Cache) runnerOption { +func WithInitializeScanner(f InitializeScanner) RunnerOption { return func(r *runner) { - r.cache = c - r.localCache = c + r.initializeScanner = f } } // NewRunner initializes Runner that provides scanning functionalities. // It is possible to return SkipScan and it must be handled by caller. -func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...runnerOption) (Runner, error) { +func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...RunnerOption) (Runner, error) { r := &runner{} for _, opt := range opts { opt(r) } - if err := r.initCache(cliOptions); err != nil { - return nil, xerrors.Errorf("cache error: %w", err) - } - // Update the vulnerability database if needed. if err := r.initDB(ctx, cliOptions); err != nil { return nil, xerrors.Errorf("DB error: %w", err) @@ -142,10 +136,6 @@ func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...runnerOptio // Close closes everything func (r *runner) Close(ctx context.Context) error { var errs error - if err := r.localCache.Close(); err != nil { - errs = multierror.Append(errs, err) - } - if r.dbOpen { if err := db.Close(); err != nil { errs = multierror.Append(errs, err) @@ -258,6 +248,9 @@ func (r *runner) ScanVM(ctx context.Context, opts flag.Options) (types.Report, e } func (r *runner) scanArtifact(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner) (types.Report, error) { + if r.initializeScanner != nil { + initializeScanner = r.initializeScanner + } report, err := r.scan(ctx, opts, initializeScanner) if err != nil { return types.Report{}, xerrors.Errorf("scan error: %w", err) @@ -335,31 +328,6 @@ func (r *runner) initJavaDB(opts flag.Options) error { return nil } -func (r *runner) initCache(opts flag.Options) error { - // Skip initializing cache when custom cache is passed - if r.cache != nil { - return nil - } - - // client/server mode - if opts.ServerAddr != "" { - r.cache = cache.NewRemoteCache(opts.ServerAddr, opts.CustomHeaders, opts.Insecure) - r.localCache = cache.NewNopCache() // No need to use local cache in client/server mode - return nil - } - - // standalone mode - cacheClient, err := cache.New(opts.CacheDir, opts.CacheOptions.CacheBackendOptions) - if err != nil { - return xerrors.Errorf("unable to initialize the cache: %w", err) - } - log.Debug("Cache dir", log.String("dir", opts.CacheDir)) - - r.cache = cacheClient - r.localCache = cacheClient - return nil -} - // Run performs artifact scanning func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err error) { ctx, cancel := context.WithTimeout(ctx, opts.Timeout) @@ -588,8 +556,8 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan return ScannerConfig{ Target: target, - ArtifactCache: r.cache, - LocalArtifactCache: r.localCache, + CacheOptions: opts.CacheOpts(), + RemoteCacheOptions: opts.RemoteCacheOpts(), ServerOption: client.ScannerOption{ RemoteURL: opts.ServerAddr, CustomHeaders: opts.CustomHeaders, @@ -607,10 +575,9 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan RepoTag: opts.RepoTag, SBOMSources: opts.SBOMSources, RekorURL: opts.RekorURL, - //Platform: opts.Platform, - AWSRegion: opts.Region, - AWSEndpoint: opts.Endpoint, - FileChecksum: fileChecksum, + AWSRegion: opts.Region, + AWSEndpoint: opts.Endpoint, + FileChecksum: fileChecksum, // For image scanning ImageOption: ftypes.ImageOptions{ diff --git a/pkg/commands/artifact/scanner.go b/pkg/commands/artifact/scanner.go index cf7a58c52693..88430a09961b 100644 --- a/pkg/commands/artifact/scanner.go +++ b/pkg/commands/artifact/scanner.go @@ -11,8 +11,7 @@ import ( // imageStandaloneScanner initializes a container image scanner in standalone mode // $ trivy image alpine:3.15 func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { - s, cleanup, err := initializeImageScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, - conf.ArtifactOption.ImageOption, conf.ArtifactOption) + s, cleanup, err := initializeImageScanner(ctx, conf.Target, conf.ArtifactOption.ImageOption, conf.CacheOptions, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize an image scanner: %w", err) } @@ -22,18 +21,18 @@ func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Sc // archiveStandaloneScanner initializes an image archive scanner in standalone mode // $ trivy image --input alpine.tar func archiveStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { - s, err := initializeArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) + s, cleanup, err := initializeArchiveScanner(ctx, conf.Target, conf.CacheOptions, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize the archive scanner: %w", err) } - return s, func() {}, nil + return s, cleanup, nil } // imageRemoteScanner initializes a container image scanner in client/server mode // $ trivy image --server localhost:4954 alpine:3.15 func imageRemoteScanner(ctx context.Context, conf ScannerConfig) ( scanner.Scanner, func(), error) { - s, cleanup, err := initializeRemoteImageScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, + s, cleanup, err := initializeRemoteImageScanner(ctx, conf.Target, conf.RemoteCacheOptions, conf.ServerOption, conf.ArtifactOption.ImageOption, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize a remote image scanner: %w", err) @@ -45,16 +44,16 @@ func imageRemoteScanner(ctx context.Context, conf ScannerConfig) ( // $ trivy image --server localhost:4954 --input alpine.tar func archiveRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { // Scan tar file - s, err := initializeRemoteArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, conf.ArtifactOption) + s, cleanup, err := initializeRemoteArchiveScanner(ctx, conf.Target, conf.RemoteCacheOptions, conf.ServerOption, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the remote archive scanner: %w", err) } - return s, func() {}, nil + return s, cleanup, nil } // 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) + s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.CacheOptions, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err) } @@ -63,7 +62,7 @@ func filesystemStandaloneScanner(ctx context.Context, conf ScannerConfig) (scann // 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.ServerOption, conf.ArtifactOption) + s, cleanup, err := initializeRemoteFilesystemScanner(ctx, conf.Target, conf.RemoteCacheOptions, conf.ServerOption, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote filesystem scanner: %w", err) } @@ -72,7 +71,7 @@ func filesystemRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.S // repositoryStandaloneScanner 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) + s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.CacheOptions, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a repository scanner: %w", err) } @@ -81,7 +80,7 @@ func repositoryStandaloneScanner(ctx context.Context, conf ScannerConfig) (scann // repositoryRemoteScanner initializes a repository scanner in client/server mode func repositoryRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { - s, cleanup, err := initializeRemoteRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, + s, cleanup, err := initializeRemoteRepositoryScanner(ctx, conf.Target, conf.RemoteCacheOptions, conf.ServerOption, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote repository scanner: %w", err) @@ -91,7 +90,7 @@ func repositoryRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.S // 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) + s, cleanup, err := initializeSBOMScanner(ctx, conf.Target, conf.CacheOptions, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err) } @@ -100,7 +99,7 @@ func sbomStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Sca // 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.ServerOption, conf.ArtifactOption) + s, cleanup, err := initializeRemoteSBOMScanner(ctx, conf.Target, conf.RemoteCacheOptions, conf.ServerOption, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote cycloneDX scanner: %w", err) } @@ -109,7 +108,7 @@ func sbomRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner // vmStandaloneScanner initializes a VM scanner in standalone mode func vmStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { - s, cleanup, err := initializeVMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) + s, cleanup, err := initializeVMScanner(ctx, conf.Target, conf.CacheOptions, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a vm scanner: %w", err) } @@ -118,7 +117,7 @@ func vmStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scann // vmRemoteScanner initializes a VM scanner in client/server mode func vmRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { - s, cleanup, err := initializeRemoteVMScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, conf.ArtifactOption) + s, cleanup, err := initializeRemoteVMScanner(ctx, conf.Target, conf.RemoteCacheOptions, conf.ServerOption, conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote vm scanner: %w", err) } diff --git a/pkg/commands/artifact/wire_gen.go b/pkg/commands/artifact/wire_gen.go index 85e66b8214f2..f47c0b0f5649 100644 --- a/pkg/commands/artifact/wire_gen.go +++ b/pkg/commands/artifact/wire_gen.go @@ -9,6 +9,7 @@ package artifact import ( "context" "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/applier" "github.com/aquasecurity/trivy/pkg/fanal/artifact" image2 "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" @@ -16,7 +17,6 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/artifact/repo" "github.com/aquasecurity/trivy/pkg/fanal/artifact/sbom" "github.com/aquasecurity/trivy/pkg/fanal/artifact/vm" - "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/image" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" @@ -32,32 +32,43 @@ import ( // initializeImageScanner is for container image scanning in standalone mode // e.g. dockerd, container registry, podman, etc. -func initializeImageScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, imageOpt types.ImageOptions, artifactOption artifact.Option) (scanner.Scanner, func(), error) { - applierApplier := applier.NewApplier(localArtifactCache) +func initializeImageScanner(ctx context.Context, imageName string, imageOpt types.ImageOptions, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + cacheCache, cleanup, err := cache.New(cacheOptions) + if err != nil { + return scanner.Scanner{}, nil, err + } + applierApplier := applier.NewApplier(cacheCache) ospkgScanner := ospkg.NewScanner() langpkgScanner := langpkg.NewScanner() config := db.Config{} client := vulnerability.NewClient(config) localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) - typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, imageOpt) + typesImage, cleanup2, err := image.NewContainerImage(ctx, imageName, imageOpt) if err != nil { + cleanup() return scanner.Scanner{}, nil, err } - artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption) + artifactArtifact, err := image2.NewArtifact(typesImage, cacheCache, artifactOption) if err != nil { + cleanup2() cleanup() return scanner.Scanner{}, nil, err } scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) return scannerScanner, func() { + cleanup2() cleanup() }, nil } // initializeArchiveScanner is for container image archive scanning in standalone mode // e.g. docker save -o alpine.tar alpine:3.15 -func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, error) { - applierApplier := applier.NewApplier(localArtifactCache) +func initializeArchiveScanner(ctx context.Context, filePath string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + cacheCache, cleanup, err := cache.New(cacheOptions) + if err != nil { + return scanner.Scanner{}, nil, err + } + applierApplier := applier.NewApplier(cacheCache) ospkgScanner := ospkg.NewScanner() langpkgScanner := langpkg.NewScanner() config := db.Config{} @@ -65,95 +76,124 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) typesImage, err := image.NewArchiveImage(filePath) if err != nil { - return scanner.Scanner{}, err + cleanup() + return scanner.Scanner{}, nil, err } - artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption) + artifactArtifact, err := image2.NewArtifact(typesImage, cacheCache, artifactOption) if err != nil { - return scanner.Scanner{}, err + cleanup() + return scanner.Scanner{}, nil, err } scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) - return scannerScanner, nil + return scannerScanner, func() { + cleanup() + }, nil } // initializeFilesystemScanner is for filesystem scanning in standalone mode -func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { - applierApplier := applier.NewApplier(localArtifactCache) +func initializeFilesystemScanner(ctx context.Context, path string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + cacheCache, cleanup, err := cache.New(cacheOptions) + if err != nil { + return scanner.Scanner{}, nil, err + } + applierApplier := applier.NewApplier(cacheCache) ospkgScanner := ospkg.NewScanner() langpkgScanner := langpkg.NewScanner() config := db.Config{} client := vulnerability.NewClient(config) localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) fs := walker.NewFS() - artifactArtifact, err := local2.NewArtifact(path, artifactCache, fs, artifactOption) + artifactArtifact, err := local2.NewArtifact(path, cacheCache, fs, artifactOption) if err != nil { + cleanup() return scanner.Scanner{}, nil, err } scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) return scannerScanner, func() { + cleanup() }, nil } -func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { - applierApplier := applier.NewApplier(localArtifactCache) +func initializeRepositoryScanner(ctx context.Context, url string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + cacheCache, cleanup, err := cache.New(cacheOptions) + if err != nil { + return scanner.Scanner{}, nil, err + } + applierApplier := applier.NewApplier(cacheCache) ospkgScanner := ospkg.NewScanner() langpkgScanner := langpkg.NewScanner() config := db.Config{} client := vulnerability.NewClient(config) localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) fs := walker.NewFS() - artifactArtifact, cleanup, err := repo.NewArtifact(url, artifactCache, fs, artifactOption) + artifactArtifact, cleanup2, err := repo.NewArtifact(url, cacheCache, fs, artifactOption) if err != nil { + cleanup() return scanner.Scanner{}, nil, err } scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) return scannerScanner, func() { + cleanup2() cleanup() }, nil } -func initializeSBOMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { - applierApplier := applier.NewApplier(localArtifactCache) +func initializeSBOMScanner(ctx context.Context, filePath string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + cacheCache, cleanup, err := cache.New(cacheOptions) + if err != nil { + return scanner.Scanner{}, nil, err + } + applierApplier := applier.NewApplier(cacheCache) ospkgScanner := ospkg.NewScanner() langpkgScanner := langpkg.NewScanner() config := db.Config{} client := vulnerability.NewClient(config) localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) - artifactArtifact, err := sbom.NewArtifact(filePath, artifactCache, artifactOption) + artifactArtifact, err := sbom.NewArtifact(filePath, cacheCache, artifactOption) if err != nil { + cleanup() return scanner.Scanner{}, nil, err } scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) return scannerScanner, func() { + cleanup() }, nil } -func initializeVMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { - applierApplier := applier.NewApplier(localArtifactCache) +func initializeVMScanner(ctx context.Context, filePath string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + cacheCache, cleanup, err := cache.New(cacheOptions) + if err != nil { + return scanner.Scanner{}, nil, err + } + applierApplier := applier.NewApplier(cacheCache) ospkgScanner := ospkg.NewScanner() langpkgScanner := langpkg.NewScanner() config := db.Config{} client := vulnerability.NewClient(config) localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) walkerVM := walker.NewVM() - artifactArtifact, err := vm.NewArtifact(filePath, artifactCache, walkerVM, artifactOption) + artifactArtifact, err := vm.NewArtifact(filePath, cacheCache, walkerVM, artifactOption) if err != nil { + cleanup() return scanner.Scanner{}, nil, err } scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) return scannerScanner, func() { + cleanup() }, nil } // initializeRemoteImageScanner is for container image scanning in client/server mode // e.g. dockerd, container registry, podman, etc. -func initializeRemoteImageScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, imageOpt types.ImageOptions, artifactOption artifact.Option) (scanner.Scanner, func(), error) { +func initializeRemoteImageScanner(ctx context.Context, imageName string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, imageOpt types.ImageOptions, artifactOption artifact.Option) (scanner.Scanner, func(), error) { v := _wireValue clientScanner := client.NewScanner(remoteScanOptions, v...) typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, imageOpt) if err != nil { return scanner.Scanner{}, nil, err } - artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption) + remoteCache := cache.NewRemoteCache(remoteCacheOptions) + artifactArtifact, err := image2.NewArtifact(typesImage, remoteCache, artifactOption) if err != nil { cleanup() return scanner.Scanner{}, nil, err @@ -170,27 +210,30 @@ var ( // initializeRemoteArchiveScanner is for container image archive scanning in client/server mode // e.g. docker save -o alpine.tar alpine:3.15 -func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, error) { +func initializeRemoteArchiveScanner(ctx context.Context, filePath string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { v := _wireValue clientScanner := client.NewScanner(remoteScanOptions, v...) typesImage, err := image.NewArchiveImage(filePath) if err != nil { - return scanner.Scanner{}, err + return scanner.Scanner{}, nil, err } - artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption) + remoteCache := cache.NewRemoteCache(remoteCacheOptions) + artifactArtifact, err := image2.NewArtifact(typesImage, remoteCache, artifactOption) if err != nil { - return scanner.Scanner{}, err + return scanner.Scanner{}, nil, err } scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact) - return scannerScanner, nil + return scannerScanner, func() { + }, nil } // initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode -func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { +func initializeRemoteFilesystemScanner(ctx context.Context, path string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { v := _wireValue clientScanner := client.NewScanner(remoteScanOptions, v...) + remoteCache := cache.NewRemoteCache(remoteCacheOptions) fs := walker.NewFS() - artifactArtifact, err := local2.NewArtifact(path, artifactCache, fs, artifactOption) + artifactArtifact, err := local2.NewArtifact(path, remoteCache, fs, artifactOption) if err != nil { return scanner.Scanner{}, nil, err } @@ -200,11 +243,12 @@ func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifac } // initializeRemoteRepositoryScanner is for repository scanning in client/server mode -func initializeRemoteRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { +func initializeRemoteRepositoryScanner(ctx context.Context, url string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { v := _wireValue clientScanner := client.NewScanner(remoteScanOptions, v...) + remoteCache := cache.NewRemoteCache(remoteCacheOptions) fs := walker.NewFS() - artifactArtifact, cleanup, err := repo.NewArtifact(url, artifactCache, fs, artifactOption) + artifactArtifact, cleanup, err := repo.NewArtifact(url, remoteCache, fs, artifactOption) if err != nil { return scanner.Scanner{}, nil, err } @@ -215,10 +259,11 @@ func initializeRemoteRepositoryScanner(ctx context.Context, url string, artifact } // initializeRemoteSBOMScanner is for sbom scanning in client/server mode -func initializeRemoteSBOMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { +func initializeRemoteSBOMScanner(ctx context.Context, path string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { v := _wireValue clientScanner := client.NewScanner(remoteScanOptions, v...) - artifactArtifact, err := sbom.NewArtifact(path, artifactCache, artifactOption) + remoteCache := cache.NewRemoteCache(remoteCacheOptions) + artifactArtifact, err := sbom.NewArtifact(path, remoteCache, artifactOption) if err != nil { return scanner.Scanner{}, nil, err } @@ -228,11 +273,12 @@ func initializeRemoteSBOMScanner(ctx context.Context, path string, artifactCache } // initializeRemoteVMScanner is for vm scanning in client/server mode -func initializeRemoteVMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { +func initializeRemoteVMScanner(ctx context.Context, path string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { v := _wireValue clientScanner := client.NewScanner(remoteScanOptions, v...) + remoteCache := cache.NewRemoteCache(remoteCacheOptions) walkerVM := walker.NewVM() - artifactArtifact, err := vm.NewArtifact(path, artifactCache, walkerVM, artifactOption) + artifactArtifact, err := vm.NewArtifact(path, remoteCache, walkerVM, artifactOption) if err != nil { return scanner.Scanner{}, nil, err } diff --git a/pkg/commands/clean/run.go b/pkg/commands/clean/run.go index f2ac57539d54..fb20799a571b 100644 --- a/pkg/commands/clean/run.go +++ b/pkg/commands/clean/run.go @@ -62,10 +62,12 @@ func cleanAll(ctx context.Context, opts flag.Options) error { func cleanScanCache(ctx context.Context, opts flag.Options) error { log.InfoContext(ctx, "Removing scan cache...") - c, err := cache.New(opts.CacheDir, opts.CacheBackendOptions) + c, cleanup, err := cache.New(opts.CacheOpts()) if err != nil { return xerrors.Errorf("failed to instantiate cache client: %w", err) } + defer cleanup() + if err = c.Clear(); err != nil { return xerrors.Errorf("clear scan cache: %w", err) } diff --git a/pkg/commands/clean/run_test.go b/pkg/commands/clean/run_test.go index a26fef86a572..9b301d238219 100644 --- a/pkg/commands/clean/run_test.go +++ b/pkg/commands/clean/run_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/commands/clean" "github.com/aquasecurity/trivy/pkg/flag" ) @@ -100,6 +101,9 @@ func TestRun(t *testing.T) { GlobalOptions: flag.GlobalOptions{ CacheDir: tempDir, }, + CacheOptions: flag.CacheOptions{ + CacheBackend: string(cache.TypeFS), + }, CleanOptions: tt.cleanOpts, } diff --git a/pkg/commands/server/run.go b/pkg/commands/server/run.go index 917f1b4aa459..c5f7b0da2f0b 100644 --- a/pkg/commands/server/run.go +++ b/pkg/commands/server/run.go @@ -19,12 +19,11 @@ func Run(ctx context.Context, opts flag.Options) (err error) { log.InitLogger(opts.Debug, opts.Quiet) // configure cache dir - cacheClient, err := cache.New(opts.CacheDir, opts.CacheOptions.CacheBackendOptions) + cacheClient, cleanup, err := cache.New(opts.CacheOpts()) if err != nil { return xerrors.Errorf("server cache error: %w", err) } - defer cacheClient.Close() - log.Debug("Cache", log.String("dir", opts.CacheDir)) + defer cleanup() // download the database file if err = operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, diff --git a/pkg/flag/cache_flags.go b/pkg/flag/cache_flags.go index 73a31fd2684d..786a0c9c7ffe 100644 --- a/pkg/flag/cache_flags.go +++ b/pkg/flag/cache_flags.go @@ -2,10 +2,6 @@ package flag import ( "time" - - "golang.org/x/xerrors" - - "github.com/aquasecurity/trivy/pkg/cache" ) // e.g. config yaml: @@ -71,14 +67,19 @@ type CacheFlagGroup struct { } type CacheOptions struct { - ClearCache bool - CacheBackendOptions cache.Options + ClearCache bool + + CacheBackend string + CacheTTL time.Duration + RedisTLS bool + RedisCACert string + RedisCert string + RedisKey string } // NewCacheFlagGroup returns a default CacheFlagGroup func NewCacheFlagGroup() *CacheFlagGroup { return &CacheFlagGroup{ - ClearCache: ClearCacheFlag.Clone(), CacheBackend: CacheBackendFlag.Clone(), CacheTTL: CacheTTLFlag.Clone(), RedisTLS: RedisTLSFlag.Clone(), @@ -109,14 +110,12 @@ func (fg *CacheFlagGroup) ToOptions() (CacheOptions, error) { return CacheOptions{}, err } - backendOpts, err := cache.NewOptions(fg.CacheBackend.Value(), fg.RedisCACert.Value(), fg.RedisCert.Value(), - fg.RedisKey.Value(), fg.RedisTLS.Value(), fg.CacheTTL.Value()) - if err != nil { - return CacheOptions{}, xerrors.Errorf("failed to initialize cache options: %w", err) - } - return CacheOptions{ - ClearCache: fg.ClearCache.Value(), - CacheBackendOptions: backendOpts, + CacheBackend: fg.CacheBackend.Value(), + CacheTTL: fg.CacheTTL.Value(), + RedisTLS: fg.RedisTLS.Value(), + RedisCACert: fg.RedisCACert.Value(), + RedisCert: fg.RedisCert.Value(), + RedisKey: fg.RedisKey.Value(), }, nil } diff --git a/pkg/flag/global_flags.go b/pkg/flag/global_flags.go index ef19a09dd4e8..ebd79bd5a06c 100644 --- a/pkg/flag/global_flags.go +++ b/pkg/flag/global_flags.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/aquasecurity/trivy/pkg/cache" + "github.com/aquasecurity/trivy/pkg/log" ) var ( @@ -144,6 +145,8 @@ func (f *GlobalFlagGroup) ToOptions() (GlobalOptions, error) { // Keep TRIVY_NON_SSL for backward compatibility insecure := f.Insecure.Value() || os.Getenv("TRIVY_NON_SSL") != "" + log.Debug("Cache dir", log.String("dir", f.CacheDir.Value())) + return GlobalOptions{ ConfigFile: f.ConfigFile.Value(), ShowVersion: f.ShowVersion.Value(), diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 70470dc05e2f..33190fb76fbe 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -17,6 +17,7 @@ import ( "github.com/spf13/viper" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" @@ -448,6 +449,28 @@ func (o *Options) FilterOpts() result.FilterOption { } } +// CacheOpts returns options for scan cache +func (o *Options) CacheOpts() cache.Options { + return cache.Options{ + Backend: o.CacheBackend, + CacheDir: o.CacheDir, + RedisCACert: o.RedisCACert, + RedisCert: o.RedisCert, + RedisKey: o.RedisKey, + RedisTLS: o.RedisTLS, + TTL: o.CacheTTL, + } +} + +// RemoteCacheOpts returns options for remote scan cache +func (o *Options) RemoteCacheOpts() cache.RemoteOptions { + return cache.RemoteOptions{ + ServerAddr: o.ServerAddr, + CustomHeaders: o.CustomHeaders, + Insecure: o.Insecure, + } +} + // SetOutputWriter sets an output writer. func (o *Options) SetOutputWriter(w io.Writer) { o.outputWriter = w diff --git a/pkg/k8s/wire_gen.go b/pkg/k8s/wire_gen.go index 134fa8c1ec49..e6c4f7e0dff7 100644 --- a/pkg/k8s/wire_gen.go +++ b/pkg/k8s/wire_gen.go @@ -8,8 +8,8 @@ package k8s import ( "github.com/aquasecurity/trivy-db/pkg/db" - "github.com/aquasecurity/trivy/pkg/fanal/applier" "github.com/aquasecurity/trivy/pkg/cache" + "github.com/aquasecurity/trivy/pkg/fanal/applier" "github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/aquasecurity/trivy/pkg/scanner/local" "github.com/aquasecurity/trivy/pkg/scanner/ospkg" diff --git a/pkg/rpc/server/wire_gen.go b/pkg/rpc/server/wire_gen.go index 4d667cbe9dfd..bcba35941b9e 100644 --- a/pkg/rpc/server/wire_gen.go +++ b/pkg/rpc/server/wire_gen.go @@ -8,8 +8,8 @@ package server import ( "github.com/aquasecurity/trivy-db/pkg/db" - "github.com/aquasecurity/trivy/pkg/fanal/applier" "github.com/aquasecurity/trivy/pkg/cache" + "github.com/aquasecurity/trivy/pkg/fanal/applier" "github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/aquasecurity/trivy/pkg/scanner/local" "github.com/aquasecurity/trivy/pkg/scanner/ospkg" diff --git a/pkg/scanner/scan.go b/pkg/scanner/scan.go index 7094e38c71fe..f1e4cf68c515 100644 --- a/pkg/scanner/scan.go +++ b/pkg/scanner/scan.go @@ -6,6 +6,7 @@ import ( "github.com/google/wire" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/fanal/artifact" aimage "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" @@ -28,6 +29,11 @@ import ( // StandaloneSuperSet is used in the standalone mode var StandaloneSuperSet = wire.NewSet( + // Cache + cache.New, + wire.Bind(new(cache.ArtifactCache), new(cache.Cache)), + wire.Bind(new(cache.LocalArtifactCache), new(cache.Cache)), + local.SuperSet, wire.Bind(new(Driver), new(local.Scanner)), NewScanner, @@ -77,6 +83,10 @@ var StandaloneVMSet = wire.NewSet( // RemoteSuperSet is used in the client mode var RemoteSuperSet = wire.NewSet( + // Cache + cache.NewRemoteCache, + wire.Bind(new(cache.ArtifactCache), new(*cache.RemoteCache)), // No need for LocalArtifactCache + client.NewScanner, wire.Value([]client.Option(nil)), wire.Bind(new(Driver), new(client.Scanner)), From 9e4927ee1e6583993749ee5d15007d5e0fa86ead Mon Sep 17 00:00:00 2001 From: Matheus Moraes Date: Thu, 27 Jun 2024 07:37:42 -0300 Subject: [PATCH 197/352] chore(deps): bump trivy-kubernetes version (#7012) --- go.mod | 14 +++++++------- go.sum | 27 ++++++++++++++------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 1f4c4fc4c7ec..306936299db6 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/aquasecurity/trivy-checks v0.13.0 github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 - github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240625102549-87c0f9c7bcf4 + github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240626072731-f39bd949a3ac github.com/aws/aws-sdk-go-v2 v1.27.2 github.com/aws/aws-sdk-go-v2/config v1.27.18 github.com/aws/aws-sdk-go-v2/credentials v1.17.18 @@ -51,7 +51,7 @@ require ( github.com/go-openapi/strfmt v0.23.0 github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/go-containerregistry v0.19.1 + github.com/google/go-containerregistry v0.19.2 github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/uuid v1.6.0 github.com/google/wire v0.6.0 @@ -126,7 +126,7 @@ require ( google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.15.1 - k8s.io/api v0.30.1 + k8s.io/api v0.30.2 k8s.io/utils v0.0.0-20231127182322-b307cd553661 modernc.org/sqlite v1.30.0 sigs.k8s.io/yaml v1.4.0 @@ -167,7 +167,7 @@ require ( github.com/antchfx/xpath v1.3.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.53.16 // indirect + github.com/aws/aws-sdk-go v1.54.6 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect @@ -368,10 +368,10 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.30.0 // indirect - k8s.io/apimachinery v0.30.1 // indirect + k8s.io/apimachinery v0.30.2 // indirect k8s.io/apiserver v0.30.0 // indirect - k8s.io/cli-runtime v0.30.1 // indirect - k8s.io/client-go v0.30.1 // indirect + k8s.io/cli-runtime v0.30.2 // indirect + k8s.io/client-go v0.30.2 // indirect k8s.io/component-base v0.30.1 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect diff --git a/go.sum b/go.sum index 6c441559ac1b..86dcd324a222 100644 --- a/go.sum +++ b/go.sum @@ -775,8 +775,8 @@ github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTU github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240625102549-87c0f9c7bcf4 h1:IKKfTgIxDptIQWB3AQFP55uuFpE9DzsbHrYIPL3VK1w= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240625102549-87c0f9c7bcf4/go.mod h1:U3LFiVzDi7FYUToe2hV0+HrEIcVpnqaajX7cEUha9Bs= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240626072731-f39bd949a3ac h1:UIC/YwwaBLS+QQOw76r4ECIh8073TF0EGWidSYVl+GQ= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240626072731-f39bd949a3ac/go.mod h1:HOhrqoyIeTxpwnKr1EyWtQ+rt2XahV8b0UDBrRpSfEQ= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -787,8 +787,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.53.16 h1:8oZjKQO/ml1WLUZw5hvF7pvYjPf8o9f57Wldoy/q9Qc= -github.com/aws/aws-sdk-go v1.53.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g= +github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8= github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk= @@ -1349,8 +1349,9 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/go-containerregistry v0.19.2 h1:TannFKE1QSajsP6hPWb5oJNgKe1IKjHukIKDUmvsV6w= +github.com/google/go-containerregistry v0.19.2/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -2985,27 +2986,27 @@ honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= -k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= +k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= +k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= -k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= +k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M= k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= -k8s.io/cli-runtime v0.30.1 h1:kSBBpfrJGS6lllc24KeniI9JN7ckOOJKnmFYH1RpTOw= -k8s.io/cli-runtime v0.30.1/go.mod h1:zhHgbqI4J00pxb6gM3gJPVf2ysDjhQmQtnTxnMScab8= +k8s.io/cli-runtime v0.30.2 h1:ooM40eEJusbgHNEqnHziN9ZpLN5U4WcQGsdLKVxpkKE= +k8s.io/cli-runtime v0.30.2/go.mod h1:Y4g/2XezFyTATQUbvV5WaChoUGhojv/jZAtdp5Zkm0A= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= +k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= +k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= From 137c9164238ffd989a0c5ed24f23a55bbf341f6e Mon Sep 17 00:00:00 2001 From: chenk Date: Thu, 27 Jun 2024 14:48:43 +0300 Subject: [PATCH 198/352] fix: use embedded when command path not found (#7037) Signed-off-by: chenk --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 306936299db6..e1607b6b8b39 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/aquasecurity/trivy-checks v0.13.0 github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 - github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240626072731-f39bd949a3ac + github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240627095026-cf9d48837f6d github.com/aws/aws-sdk-go-v2 v1.27.2 github.com/aws/aws-sdk-go-v2/config v1.27.18 github.com/aws/aws-sdk-go-v2/credentials v1.17.18 diff --git a/go.sum b/go.sum index 86dcd324a222..d5318916faeb 100644 --- a/go.sum +++ b/go.sum @@ -775,8 +775,8 @@ github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTU github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240626072731-f39bd949a3ac h1:UIC/YwwaBLS+QQOw76r4ECIh8073TF0EGWidSYVl+GQ= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240626072731-f39bd949a3ac/go.mod h1:HOhrqoyIeTxpwnKr1EyWtQ+rt2XahV8b0UDBrRpSfEQ= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240627095026-cf9d48837f6d h1:z5Ug+gqNjgHzCo7rmv6wKTmyJ8E3bAVEU2AASo3740s= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240627095026-cf9d48837f6d/go.mod h1:HOhrqoyIeTxpwnKr1EyWtQ+rt2XahV8b0UDBrRpSfEQ= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= From 4f8b3996e4a1eb00124966688230360ab24d3673 Mon Sep 17 00:00:00 2001 From: Christoffer Nissen Date: Thu, 27 Jun 2024 14:51:43 +0200 Subject: [PATCH 199/352] =?UTF-8?q?docs:=20=E2=9C=A8=20Updated=20ecosystem?= =?UTF-8?q?=20docs=20with=20reference=20to=20new=20community=20app=20(#704?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ecosystem/prod.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/ecosystem/prod.md b/docs/ecosystem/prod.md index 8f9b8fb71be0..4d037ebdc78d 100644 --- a/docs/ecosystem/prod.md +++ b/docs/ecosystem/prod.md @@ -29,3 +29,11 @@ You can use Kyverno to ensure and enforce that deployed workloads' images are sc Trivy is integrated into Zora as a vulnerability scanner plugin. 👉 Get it at: + +## Helmper (Community) + +[Helmper](https://christoffernissen.github.io/helmper/) is a go program that reads Helm Charts from remote OCI registries and pushes the Helm Charts and the Helm Charts container images to your OCI registries with optional OS level vulnerability patching + +Trivy is integrated into Helmper as a vulnerability scanner in combination with Copacetic to fix detected vulnerabilities. + +👉 Get it at: From edc556b85e3554c31e19b1ece189effb9ba2be12 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:04:07 +0600 Subject: [PATCH 200/352] feat(php): add installed.json file support (#4865) --- docs/docs/coverage/language/index.md | 3 +- docs/docs/coverage/language/php.md | 18 +- integration/repo_test.go | 10 + .../testdata/composer.vendor.json.golden | 131 +++++++++++ .../repo/composer-vendor/installed.json | 222 ++++++++++++++++++ pkg/detector/library/driver.go | 2 +- pkg/fanal/analyzer/const.go | 5 +- .../language/php/composer/composer.go | 4 +- .../language/php/composer/composer_test.go | 8 +- .../composer-vendor/happy/installed.json | 131 +++++++++++ .../sad/installed.json} | 0 .../{ => composer}/happy/composer.json | 0 .../{ => composer}/happy/composer.lock | 0 .../no-composer-json/composer.lock | 0 .../sad/composer.lock} | 0 .../wrong-composer-json/composer.json | 1 + .../wrong-composer-json/composer.lock | 0 .../analyzer/language/php/composer/vendor.go | 39 +++ .../language/php/composer/vendor_test.go | 120 ++++++++++ pkg/fanal/artifact/image/image_test.go | 110 ++++----- pkg/fanal/types/const.go | 70 +++--- pkg/purl/purl.go | 2 + 22 files changed, 771 insertions(+), 105 deletions(-) create mode 100644 integration/testdata/composer.vendor.json.golden create mode 100644 integration/testdata/fixtures/repo/composer-vendor/installed.json create mode 100644 pkg/fanal/analyzer/language/php/composer/testdata/composer-vendor/happy/installed.json rename pkg/fanal/analyzer/language/php/composer/testdata/{sad/composer.lock => composer-vendor/sad/installed.json} (100%) rename pkg/fanal/analyzer/language/php/composer/testdata/{ => composer}/happy/composer.json (100%) rename pkg/fanal/analyzer/language/php/composer/testdata/{ => composer}/happy/composer.lock (100%) rename pkg/fanal/analyzer/language/php/composer/testdata/{ => composer}/no-composer-json/composer.lock (100%) rename pkg/fanal/analyzer/language/php/composer/testdata/{wrong-composer-json/composer.json => composer/sad/composer.lock} (100%) create mode 100644 pkg/fanal/analyzer/language/php/composer/testdata/composer/wrong-composer-json/composer.json rename pkg/fanal/analyzer/language/php/composer/testdata/{ => composer}/wrong-composer-json/composer.lock (100%) create mode 100644 pkg/fanal/analyzer/language/php/composer/vendor.go create mode 100644 pkg/fanal/analyzer/language/php/composer/vendor_test.go diff --git a/docs/docs/coverage/language/index.md b/docs/docs/coverage/language/index.md index b9461b4e013b..df8203f93691 100644 --- a/docs/docs/coverage/language/index.md +++ b/docs/docs/coverage/language/index.md @@ -26,7 +26,8 @@ On the other hand, when the target is a post-build artifact, like a container im | | egg package[^1] | ✅ | ✅ | - | - | | | wheel package[^2] | ✅ | ✅ | - | - | | | conda package[^3] | ✅ | ✅ | - | - | -| [PHP](php.md) | composer.lock | ✅ | ✅ | ✅ | ✅ | +| [PHP](php.md) | composer.lock | - | - | ✅ | ✅ | +| | installed.json | ✅ | ✅ | - | - | | [Node.js](nodejs.md) | package-lock.json | - | - | ✅ | ✅ | | | yarn.lock | - | - | ✅ | ✅ | | | pnpm-lock.yaml | - | - | ✅ | ✅ | diff --git a/docs/docs/coverage/language/php.md b/docs/docs/coverage/language/php.md index 6fa138c35290..9fe38bf4990d 100644 --- a/docs/docs/coverage/language/php.md +++ b/docs/docs/coverage/language/php.md @@ -4,23 +4,27 @@ Trivy supports [Composer][composer], which is a tool for dependency management i The following scanners are supported. -| Package manager | SBOM | Vulnerability | License | -| --------------- | :---: | :-----------: | :-----: | -| Composer | ✓ | ✓ | ✓ | +| Package manager | SBOM | Vulnerability | License | +|-----------------|:----:|:-------------:|:-------:| +| Composer | ✓ | ✓ | ✓ | The following table provides an outline of the features Trivy offers. -| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | -|-----------------|---------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| -| Composer | composer.lock | ✓ | Excluded | ✓ | ✓ | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|-----------------|----------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| +| Composer | composer.lock | ✓ | Excluded | ✓ | ✓ | +| Composer | installed.json | ✓ | Excluded | - | ✓ | -## Composer +## composer.lock In order to detect dependencies, Trivy searches for `composer.lock`. Trivy also supports dependency trees; however, to display an accurate tree, it needs to know whether each package is a direct dependency of the project. Since this information is not included in `composer.lock`, Trivy parses `composer.json`, which should be located next to `composer.lock`. If you want to see the dependency tree, please ensure that `composer.json` is present. +## installed.json +Trivy also supports dependency detection for `installed.json` files. By default, you can find this file at `path_to_app/vendor/composer/installed.json`. + [composer]: https://getcomposer.org/ [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies \ No newline at end of file diff --git a/integration/repo_test.go b/integration/repo_test.go index 49e342d1a91a..e07b48b950b7 100644 --- a/integration/repo_test.go +++ b/integration/repo_test.go @@ -250,6 +250,16 @@ func TestRepository(t *testing.T) { }, golden: "testdata/test-repo.json.golden", }, + { + name: "installed.json", + args: args{ + command: "rootfs", + scanner: types.VulnerabilityScanner, + listAllPkgs: true, + input: "testdata/fixtures/repo/composer-vendor", + }, + golden: "testdata/composer.vendor.json.golden", + }, { name: "dockerfile", args: args{ diff --git a/integration/testdata/composer.vendor.json.golden b/integration/testdata/composer.vendor.json.golden new file mode 100644 index 000000000000..ebb1f65a0824 --- /dev/null +++ b/integration/testdata/composer.vendor.json.golden @@ -0,0 +1,131 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/composer-vendor", + "ArtifactType": "filesystem", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "installed.json", + "Class": "lang-pkgs", + "Type": "composer-vendor", + "Packages": [ + { + "ID": "guzzlehttp/psr7@1.8.3", + "Name": "guzzlehttp/psr7", + "Identifier": { + "PURL": "pkg:composer/guzzlehttp/psr7@1.8.3", + "UID": "25fca97fe23aa7b1" + }, + "Version": "1.8.3", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "psr/http-message@1.1", + "ralouphie/getallheaders@3.0.3" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 3, + "EndLine": 115 + } + ] + }, + { + "ID": "psr/http-message@1.1", + "Name": "psr/http-message", + "Identifier": { + "PURL": "pkg:composer/psr/http-message@1.1", + "UID": "299d8ff4461e894" + }, + "Version": "1.1", + "Licenses": [ + "MIT" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 116, + "EndLine": 171 + } + ] + }, + { + "ID": "ralouphie/getallheaders@3.0.3", + "Name": "ralouphie/getallheaders", + "Identifier": { + "PURL": "pkg:composer/ralouphie/getallheaders@3.0.3", + "UID": "c383e94d979a209c" + }, + "Version": "3.0.3", + "Licenses": [ + "MIT" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 172, + "EndLine": 218 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-24775", + "PkgID": "guzzlehttp/psr7@1.8.3", + "PkgName": "guzzlehttp/psr7", + "PkgIdentifier": { + "PURL": "pkg:composer/guzzlehttp/psr7@1.8.3", + "UID": "25fca97fe23aa7b1" + }, + "InstalledVersion": "1.8.3", + "FixedVersion": "1.8.4", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-24775", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Composer", + "URL": "https://github.com/advisories?query=type%%3Areviewed+ecosystem%%3Acomposer" + }, + "Title": "Improper Input Validation in guzzlehttp/psr7", + "Description": "### Impact\nIn proper header parsing. An attacker could sneak in a new line character and pass untrusted values. \n\n### Patches\nThe issue is patched in 1.8.4 and 2.1.1.\n\n### Workarounds\nThere are no known workarounds.\n", + "Severity": "HIGH", + "CweIDs": [ + "CWE-20" + ], + "VendorSeverity": { + "ghsa": 3 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", + "V3Score": 7.5 + } + }, + "References": [ + "https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96", + "https://nvd.nist.gov/vuln/detail/CVE-2022-24775" + ], + "PublishedDate": "2022-03-25T19:26:33Z", + "LastModifiedDate": "2022-06-14T20:02:29Z" + } + ] + } + ] +} diff --git a/integration/testdata/fixtures/repo/composer-vendor/installed.json b/integration/testdata/fixtures/repo/composer-vendor/installed.json new file mode 100644 index 000000000000..532876cd7ff5 --- /dev/null +++ b/integration/testdata/fixtures/repo/composer-vendor/installed.json @@ -0,0 +1,222 @@ +{ + "packages": [ + { + "name": "guzzlehttp/psr7", + "version": "1.8.3", + "version_normalized": "1.8.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/1afdd860a2566ed3c2b0b4a3de6e23434a79ec85", + "reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "time": "2021-10-05T13:56:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/1.8.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/psr7" + }, + { + "name": "psr/http-message", + "version": "1.1", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "time": "2023-04-04T09:50:52+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, + "install-path": "../psr/http-message" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "version_normalized": "3.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "time": "2019-03-08T08:55:37+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "install-path": "../ralouphie/getallheaders" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/pkg/detector/library/driver.go b/pkg/detector/library/driver.go index af91c30c786f..6990d3c7e84d 100644 --- a/pkg/detector/library/driver.go +++ b/pkg/detector/library/driver.go @@ -33,7 +33,7 @@ func NewDriver(libType ftypes.LangType) (Driver, bool) { case ftypes.RustBinary, ftypes.Cargo: ecosystem = vulnerability.Cargo comparer = compare.GenericComparer{} - case ftypes.Composer: + case ftypes.Composer, ftypes.ComposerVendor: ecosystem = vulnerability.Composer comparer = compare.GenericComparer{} case ftypes.GoBinary, ftypes.GoModule: diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 82bf3bb12296..6e9d0332eb61 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -49,7 +49,8 @@ const ( TypeCargo Type = "cargo" // PHP - TypeComposer Type = "composer" + TypeComposer Type = "composer" + TypeComposerVendor Type = "composer-vendor" // Java TypeJar Type = "jar" @@ -218,6 +219,7 @@ var ( TypePubSpecLock, TypeMixLock, TypeCondaEnv, + TypeComposer, } // TypeIndividualPkgs has all analyzers for individual packages @@ -229,6 +231,7 @@ var ( TypeGoBinary, TypeJar, TypeRustBinary, + TypeComposerVendor, } // TypeConfigFiles has all config file analyzers diff --git a/pkg/fanal/analyzer/language/php/composer/composer.go b/pkg/fanal/analyzer/language/php/composer/composer.go index 1c0e14a1881a..15d2a2e8ec27 100644 --- a/pkg/fanal/analyzer/language/php/composer/composer.go +++ b/pkg/fanal/analyzer/language/php/composer/composer.go @@ -26,7 +26,7 @@ func init() { analyzer.RegisterPostAnalyzer(analyzer.TypeComposer, newComposerAnalyzer) } -const version = 1 +const composerAnalyzerVersion = 1 var requiredFiles = []string{ types.ComposerLock, @@ -96,7 +96,7 @@ func (a composerAnalyzer) Type() analyzer.Type { } func (a composerAnalyzer) Version() int { - return version + return composerAnalyzerVersion } func (a composerAnalyzer) parseComposerLock(path string, r io.Reader) (*types.Application, error) { diff --git a/pkg/fanal/analyzer/language/php/composer/composer_test.go b/pkg/fanal/analyzer/language/php/composer/composer_test.go index ea963d94dcf2..67ed0a0daa6e 100644 --- a/pkg/fanal/analyzer/language/php/composer/composer_test.go +++ b/pkg/fanal/analyzer/language/php/composer/composer_test.go @@ -20,7 +20,7 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { }{ { name: "happy path", - dir: "testdata/happy", + dir: "testdata/composer/happy", want: &analyzer.AnalysisResult{ Applications: []types.Application{ { @@ -63,7 +63,7 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { }, { name: "no composer.json", - dir: "testdata/no-composer-json", + dir: "testdata/composer/no-composer-json", want: &analyzer.AnalysisResult{ Applications: []types.Application{ { @@ -106,7 +106,7 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { }, { name: "wrong composer.json", - dir: "testdata/wrong-composer-json", + dir: "testdata/composer/wrong-composer-json", want: &analyzer.AnalysisResult{ Applications: []types.Application{ { @@ -149,7 +149,7 @@ func Test_composerAnalyzer_PostAnalyze(t *testing.T) { }, { name: "broken composer.lock", - dir: "testdata/sad", + dir: "testdata/composer/sad", want: &analyzer.AnalysisResult{}, }, } diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/composer-vendor/happy/installed.json b/pkg/fanal/analyzer/language/php/composer/testdata/composer-vendor/happy/installed.json new file mode 100644 index 000000000000..e44e60d9050a --- /dev/null +++ b/pkg/fanal/analyzer/language/php/composer/testdata/composer-vendor/happy/installed.json @@ -0,0 +1,131 @@ +{ + "packages": [ + { + "name": "pear/log", + "version": "1.13.3", + "version_normalized": "1.13.3.0", + "source": { + "type": "git", + "url": "https://github.com/pear/Log.git", + "reference": "21af0be11669194d72d88b5ee9d5f176dc75d9a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/Log/zipball/21af0be11669194d72d88b5ee9d5f176dc75d9a3", + "reference": "21af0be11669194d72d88b5ee9d5f176dc75d9a3", + "shasum": "" + }, + "require": { + "pear/pear_exception": "1.0.1 || 1.0.2", + "php": ">5.2" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "pear/db": "Install optionally via your project's composer.json" + }, + "time": "2021-05-04T23:51:30+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Log": "./" + }, + "exclude-from-classmap": [ + "/examples/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jon Parise", + "email": "jon@php.net", + "homepage": "http://www.indelible.org", + "role": "Developer" + } + ], + "description": "PEAR Logging Framework", + "homepage": "http://pear.github.io/Log/", + "keywords": [ + "log", + "logging" + ], + "support": { + "issues": "https://github.com/pear/Log/issues", + "source": "https://github.com/pear/Log" + }, + "install-path": "../pear/log" + }, + { + "name": "pear/pear_exception", + "version": "v1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/pear/PEAR_Exception.git", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "<9" + }, + "time": "2021-03-21T15:43:46+00:00", + "type": "class", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "PEAR/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "." + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Helgi Thormar", + "email": "dufuz@php.net" + }, + { + "name": "Greg Beaver", + "email": "cellog@php.net" + } + ], + "description": "The PEAR Exception base class.", + "homepage": "https://github.com/pear/PEAR_Exception", + "keywords": [ + "exception" + ], + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception", + "source": "https://github.com/pear/PEAR_Exception" + }, + "install-path": "../pear/pear_exception" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/sad/composer.lock b/pkg/fanal/analyzer/language/php/composer/testdata/composer-vendor/sad/installed.json similarity index 100% rename from pkg/fanal/analyzer/language/php/composer/testdata/sad/composer.lock rename to pkg/fanal/analyzer/language/php/composer/testdata/composer-vendor/sad/installed.json diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/happy/composer.json b/pkg/fanal/analyzer/language/php/composer/testdata/composer/happy/composer.json similarity index 100% rename from pkg/fanal/analyzer/language/php/composer/testdata/happy/composer.json rename to pkg/fanal/analyzer/language/php/composer/testdata/composer/happy/composer.json diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/happy/composer.lock b/pkg/fanal/analyzer/language/php/composer/testdata/composer/happy/composer.lock similarity index 100% rename from pkg/fanal/analyzer/language/php/composer/testdata/happy/composer.lock rename to pkg/fanal/analyzer/language/php/composer/testdata/composer/happy/composer.lock diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/no-composer-json/composer.lock b/pkg/fanal/analyzer/language/php/composer/testdata/composer/no-composer-json/composer.lock similarity index 100% rename from pkg/fanal/analyzer/language/php/composer/testdata/no-composer-json/composer.lock rename to pkg/fanal/analyzer/language/php/composer/testdata/composer/no-composer-json/composer.lock diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/wrong-composer-json/composer.json b/pkg/fanal/analyzer/language/php/composer/testdata/composer/sad/composer.lock similarity index 100% rename from pkg/fanal/analyzer/language/php/composer/testdata/wrong-composer-json/composer.json rename to pkg/fanal/analyzer/language/php/composer/testdata/composer/sad/composer.lock diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/composer/wrong-composer-json/composer.json b/pkg/fanal/analyzer/language/php/composer/testdata/composer/wrong-composer-json/composer.json new file mode 100644 index 000000000000..81750b96f9d8 --- /dev/null +++ b/pkg/fanal/analyzer/language/php/composer/testdata/composer/wrong-composer-json/composer.json @@ -0,0 +1 @@ +{ \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/php/composer/testdata/wrong-composer-json/composer.lock b/pkg/fanal/analyzer/language/php/composer/testdata/composer/wrong-composer-json/composer.lock similarity index 100% rename from pkg/fanal/analyzer/language/php/composer/testdata/wrong-composer-json/composer.lock rename to pkg/fanal/analyzer/language/php/composer/testdata/composer/wrong-composer-json/composer.lock diff --git a/pkg/fanal/analyzer/language/php/composer/vendor.go b/pkg/fanal/analyzer/language/php/composer/vendor.go new file mode 100644 index 000000000000..423de2b7a352 --- /dev/null +++ b/pkg/fanal/analyzer/language/php/composer/vendor.go @@ -0,0 +1,39 @@ +package composer + +import ( + "context" + "os" + "path/filepath" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/php/composer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func init() { + analyzer.RegisterAnalyzer(&composerVendorAnalyzer{}) +} + +const ( + composerInstalledAnalyzerVersion = 1 +) + +// composerVendorAnalyzer analyzes 'installed.json' +type composerVendorAnalyzer struct{} + +func (a composerVendorAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + return language.Analyze(types.ComposerVendor, input.FilePath, input.Content, composer.NewParser()) +} + +func (a composerVendorAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.Base(filePath) == types.ComposerInstalledJson +} + +func (a composerVendorAnalyzer) Type() analyzer.Type { + return analyzer.TypeComposerVendor +} + +func (a composerVendorAnalyzer) Version() int { + return composerInstalledAnalyzerVersion +} diff --git a/pkg/fanal/analyzer/language/php/composer/vendor_test.go b/pkg/fanal/analyzer/language/php/composer/vendor_test.go new file mode 100644 index 000000000000..887c5d404039 --- /dev/null +++ b/pkg/fanal/analyzer/language/php/composer/vendor_test.go @@ -0,0 +1,120 @@ +package composer + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_composerVendorAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "happy path", + inputFile: "testdata/composer-vendor/happy/installed.json", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.ComposerVendor, + FilePath: "testdata/composer-vendor/happy/installed.json", + Packages: []types.Package{ + { + ID: "pear/log@1.13.3", + Name: "pear/log", + Version: "1.13.3", + Indirect: false, + Relationship: types.RelationshipUnknown, + Licenses: []string{"MIT"}, + Locations: []types.Location{ + { + StartLine: 3, + EndLine: 65, + }, + }, + DependsOn: []string{"pear/pear_exception@v1.0.2"}, + }, + { + ID: "pear/pear_exception@v1.0.2", + Name: "pear/pear_exception", + Version: "v1.0.2", + Indirect: false, + Relationship: types.RelationshipUnknown, + Licenses: []string{"BSD-2-Clause"}, + Locations: []types.Location{ + { + StartLine: 66, + EndLine: 127, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "sad path", + inputFile: "testdata/composer-vendor/sad/installed.json", + wantErr: "decode error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer func() { + err = f.Close() + require.NoError(t, err) + }() + + a := composerVendorAnalyzer{} + got, err := a.Analyze(nil, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, + }) + + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} + +func Test_composerVendorAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "happy path", + filePath: "app/vendor/composer/installed.json", + want: true, + }, + { + name: "sad path", + filePath: "composer.json", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := composerVendorAnalyzer{} + got := a.Required(tt.filePath, nil) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go index 6ce36846868a..cd7fea2df1e2 100644 --- a/pkg/fanal/artifact/image/image_test.go +++ b/pkg/fanal/artifact/image/image_test.go @@ -352,17 +352,17 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingArtifact: true, - MissingBlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + MissingBlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0", + BlobID: "sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -429,7 +429,7 @@ func TestArtifact_Inspect(t *testing.T) { Name: "../../test/testdata/alpine-311.tar.gz", Type: artifact.TypeContainerImage, ID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", DiffIDs: []string{ @@ -488,25 +488,25 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", - "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", - "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", - "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", + "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", + "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", + "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", - "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", - "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", - "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", + "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", + "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", + "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", }, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + BlobID: "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -594,7 +594,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + BlobID: "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -690,7 +690,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + BlobID: "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -898,7 +898,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + BlobID: "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1761,10 +1761,10 @@ func TestArtifact_Inspect(t *testing.T) { Type: artifact.TypeContainerImage, ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", - "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", - "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", - "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", + "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", + "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", + "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", }, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", @@ -1858,25 +1858,25 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", - "sha256:12c266a627dc4014c3ee96936058ba98209056f4ffe0081bb5fca7ff91592cdb", - "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", - "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", + "sha256:f46989447d5a1357f6b2427b86ca2af827dd380dbd7fbf392d2abf9a5d457323", + "sha256:487a6fb0914825c8fb9f3a0662a608039bd5a8b6488d76b9de2eb1a684e908e1", + "sha256:a23b05a9c95939a0d30d6b4f6c25393473252bde47b2daa03258c27461367509", + "sha256:47226d3c41a3ffd99dacdbcd2b197a7394ee8948270710ee035181427f88dfab", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", - "sha256:12c266a627dc4014c3ee96936058ba98209056f4ffe0081bb5fca7ff91592cdb", - "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", - "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", + "sha256:f46989447d5a1357f6b2427b86ca2af827dd380dbd7fbf392d2abf9a5d457323", + "sha256:487a6fb0914825c8fb9f3a0662a608039bd5a8b6488d76b9de2eb1a684e908e1", + "sha256:a23b05a9c95939a0d30d6b4f6c25393473252bde47b2daa03258c27461367509", + "sha256:47226d3c41a3ffd99dacdbcd2b197a7394ee8948270710ee035181427f88dfab", }, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", + BlobID: "sha256:f46989447d5a1357f6b2427b86ca2af827dd380dbd7fbf392d2abf9a5d457323", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1887,7 +1887,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:12c266a627dc4014c3ee96936058ba98209056f4ffe0081bb5fca7ff91592cdb", + BlobID: "sha256:487a6fb0914825c8fb9f3a0662a608039bd5a8b6488d76b9de2eb1a684e908e1", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1898,7 +1898,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", + BlobID: "sha256:a23b05a9c95939a0d30d6b4f6c25393473252bde47b2daa03258c27461367509", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1910,7 +1910,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", + BlobID: "sha256:47226d3c41a3ffd99dacdbcd2b197a7394ee8948270710ee035181427f88dfab", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1926,10 +1926,10 @@ func TestArtifact_Inspect(t *testing.T) { Type: artifact.TypeContainerImage, ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:e1187118cdbe8893fc2fd4b345f813d195ee6aaeb4820d4576694199f8c10350", - "sha256:12c266a627dc4014c3ee96936058ba98209056f4ffe0081bb5fca7ff91592cdb", - "sha256:47adac0e28b12338e99dedbd7e8b0ef1f7aaa28e646f637ab2db8908b80704c8", - "sha256:dd1082b33b17401fdc31bcbf60eaaecb9ce29e23956c50db6f34b2cc6cfa13c8", + "sha256:f46989447d5a1357f6b2427b86ca2af827dd380dbd7fbf392d2abf9a5d457323", + "sha256:487a6fb0914825c8fb9f3a0662a608039bd5a8b6488d76b9de2eb1a684e908e1", + "sha256:a23b05a9c95939a0d30d6b4f6c25393473252bde47b2daa03258c27461367509", + "sha256:47226d3c41a3ffd99dacdbcd2b197a7394ee8948270710ee035181427f88dfab", }, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", @@ -2012,7 +2012,7 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ Err: xerrors.New("MissingBlobs failed"), @@ -2026,16 +2026,16 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ - MissingBlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + MissingBlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0", + BlobID: "sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -2095,18 +2095,18 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", - "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", - "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", - "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", + "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", + "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", + "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", - "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", - "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", - "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", + "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", + "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", + "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", }, }, }, @@ -2114,7 +2114,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:dd0a4f4754bf4590327be34f4266f63c92184352afadb72e4c9b162f76224000", + BlobID: "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", BlobInfoAnything: true, }, @@ -2125,7 +2125,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:f9e6a3065bb47f810916e90249076950a4b70785a27d3bcb90406d0ab342fa67", + BlobID: "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", BlobInfoAnything: true, }, @@ -2136,7 +2136,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:b6be0de11c6090f71dea119f43dd360335643420058e317baffb089f0dff4001", + BlobID: "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", BlobInfoAnything: true, }, @@ -2147,7 +2147,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:37c561c19b169f5f9832f4b0060bf74ebc8d1c9e01662ad4fa21c394da159440", + BlobID: "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", BlobInfoAnything: true, }, @@ -2164,17 +2164,17 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingArtifact: true, - MissingBlobIDs: []string{"sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0"}, + MissingBlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:1fd280c63e1416a2261e76454caa19a5b77c6bddedd48309c9687c4fe72b34c0", + BlobID: "sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index 9ad8c1a2c57b..7253404c0be1 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -43,38 +43,39 @@ const ( // Programming language dependencies const ( - Bundler LangType = "bundler" - GemSpec LangType = "gemspec" - Cargo LangType = "cargo" - Composer LangType = "composer" - Npm LangType = "npm" - NuGet LangType = "nuget" - DotNetCore LangType = "dotnet-core" - PackagesProps LangType = "packages-props" - Pip LangType = "pip" - Pipenv LangType = "pipenv" - Poetry LangType = "poetry" - CondaPkg LangType = "conda-pkg" - CondaEnv LangType = "conda-environment" - PythonPkg LangType = "python-pkg" - NodePkg LangType = "node-pkg" - Yarn LangType = "yarn" - Pnpm LangType = "pnpm" - Jar LangType = "jar" - Pom LangType = "pom" - Gradle LangType = "gradle" - Sbt LangType = "sbt" - GoBinary LangType = "gobinary" - GoModule LangType = "gomod" - JavaScript LangType = "javascript" - RustBinary LangType = "rustbinary" - Conan LangType = "conan" - Cocoapods LangType = "cocoapods" - Swift LangType = "swift" - Pub LangType = "pub" - Hex LangType = "hex" - Bitnami LangType = "bitnami" - Julia LangType = "julia" + Bundler LangType = "bundler" + GemSpec LangType = "gemspec" + Cargo LangType = "cargo" + Composer LangType = "composer" + ComposerVendor LangType = "composer-vendor" + Npm LangType = "npm" + NuGet LangType = "nuget" + DotNetCore LangType = "dotnet-core" + PackagesProps LangType = "packages-props" + Pip LangType = "pip" + Pipenv LangType = "pipenv" + Poetry LangType = "poetry" + CondaPkg LangType = "conda-pkg" + CondaEnv LangType = "conda-environment" + PythonPkg LangType = "python-pkg" + NodePkg LangType = "node-pkg" + Yarn LangType = "yarn" + Pnpm LangType = "pnpm" + Jar LangType = "jar" + Pom LangType = "pom" + Gradle LangType = "gradle" + Sbt LangType = "sbt" + GoBinary LangType = "gobinary" + GoModule LangType = "gomod" + JavaScript LangType = "javascript" + RustBinary LangType = "rustbinary" + Conan LangType = "conan" + Cocoapods LangType = "cocoapods" + Swift LangType = "swift" + Pub LangType = "pub" + Hex LangType = "hex" + Bitnami LangType = "bitnami" + Julia LangType = "julia" K8sUpstream LangType = "kubernetes" EKS LangType = "eks" // Amazon Elastic Kubernetes Service @@ -122,8 +123,9 @@ const ( YarnLock = "yarn.lock" PnpmLock = "pnpm-lock.yaml" - ComposerLock = "composer.lock" - ComposerJson = "composer.json" + ComposerLock = "composer.lock" + ComposerJson = "composer.json" + ComposerInstalledJson = "installed.json" PyProject = "pyproject.toml" PipRequirements = "requirements.txt" diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index 1ccf39c8530a..12b27e6290e6 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -448,6 +448,8 @@ func purlType(t ftypes.TargetType) string { return packageurl.TypeGem case ftypes.NuGet, ftypes.DotNetCore, ftypes.PackagesProps: return packageurl.TypeNuget + case ftypes.Composer, ftypes.ComposerVendor: + return packageurl.TypeComposer case ftypes.CondaPkg, ftypes.CondaEnv: return packageurl.TypeConda case ftypes.PythonPkg, ftypes.Pip, ftypes.Pipenv, ftypes.Poetry: From 14d71ba63c39e51dd4179ba2d6002b46e1816e90 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 28 Jun 2024 12:52:19 +0400 Subject: [PATCH 201/352] fix(sbom): use package UIDs for uniqueness (#7042) Signed-off-by: knqyf263 --- integration/convert_test.go | 69 ++ .../testdata/fixtures/convert/npm.json.golden | 381 +++++++++ .../testdata/npm-cyclonedx.json.golden | 725 ++++++++++++++++++ pkg/commands/convert/run.go | 30 + pkg/dependency/id.go | 20 + pkg/fanal/applier/docker.go | 22 +- pkg/sbom/core/bom.go | 1 - pkg/sbom/cyclonedx/marshal_test.go | 177 ++--- pkg/sbom/io/encode.go | 19 +- pkg/sbom/io/encode_test.go | 162 +++- pkg/sbom/spdx/marshal_test.go | 8 + 11 files changed, 1483 insertions(+), 131 deletions(-) create mode 100644 integration/convert_test.go create mode 100644 integration/testdata/fixtures/convert/npm.json.golden create mode 100644 integration/testdata/npm-cyclonedx.json.golden diff --git a/integration/convert_test.go b/integration/convert_test.go new file mode 100644 index 000000000000..803ba538dcba --- /dev/null +++ b/integration/convert_test.go @@ -0,0 +1,69 @@ +//go:build integration + +package integration + +import ( + "path/filepath" + "testing" + + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestConvert(t *testing.T) { + type args struct { + input string + format string + scanners string + } + tests := []struct { + name string + args args + golden string + override OverrideFunc + }{ + { + name: "npm", + args: args{ + input: "testdata/npm.json.golden", + format: "cyclonedx", + }, + golden: "testdata/npm-cyclonedx.json.golden", + }, + { + name: "npm without package UID", + args: args{ + input: "testdata/fixtures/convert/npm.json.golden", + format: "cyclonedx", + }, + golden: "testdata/npm-cyclonedx.json.golden", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + osArgs := []string{ + "convert", + "--cache-dir", + t.TempDir(), + "-q", + "--format", + tt.args.format, + } + + // Set up the output file + outputFile := filepath.Join(t.TempDir(), "output.json") + if *update { + outputFile = tt.golden + } + + osArgs = append(osArgs, "--output", outputFile) + osArgs = append(osArgs, tt.args.input) + + // Run "trivy convert" + runTest(t, osArgs, tt.golden, outputFile, types.Format(tt.args.format), runOptions{ + fakeUUID: "3ff14136-e09f-4df9-80ea-%012d", + }) + }) + } + +} diff --git a/integration/testdata/fixtures/convert/npm.json.golden b/integration/testdata/fixtures/convert/npm.json.golden new file mode 100644 index 000000000000..a576da82c72e --- /dev/null +++ b/integration/testdata/fixtures/convert/npm.json.golden @@ -0,0 +1,381 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/npm", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "package-lock.json", + "Class": "lang-pkgs", + "Type": "npm", + "Packages": [ + { + "ID": "asap@2.0.6", + "Name": "asap", + "Identifier": { + "PURL": "pkg:npm/asap@2.0.6" + }, + "Version": "2.0.6", + "Layer": {}, + "Locations": [ + { + "StartLine": 6, + "EndLine": 10 + } + ] + }, + { + "ID": "jquery@3.3.9", + "Name": "jquery", + "Identifier": { + "PURL": "pkg:npm/jquery@3.3.9" + }, + "Version": "3.3.9", + "Licenses": [ + "MIT" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 11, + "EndLine": 15 + } + ] + }, + { + "ID": "js-tokens@4.0.0", + "Name": "js-tokens", + "Identifier": { + "PURL": "pkg:npm/js-tokens@4.0.0" + }, + "Version": "4.0.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 16, + "EndLine": 20 + } + ] + }, + { + "ID": "loose-envify@1.4.0", + "Name": "loose-envify", + "Identifier": { + "PURL": "pkg:npm/loose-envify@1.4.0" + }, + "Version": "1.4.0", + "DependsOn": [ + "js-tokens@4.0.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 21, + "EndLine": 28 + } + ] + }, + { + "ID": "object-assign@4.1.1", + "Name": "object-assign", + "Identifier": { + "PURL": "pkg:npm/object-assign@4.1.1" + }, + "Version": "4.1.1", + "Layer": {}, + "Locations": [ + { + "StartLine": 29, + "EndLine": 33 + } + ] + }, + { + "ID": "promise@8.0.3", + "Name": "promise", + "Identifier": { + "PURL": "pkg:npm/promise@8.0.3" + }, + "Version": "8.0.3", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "asap@2.0.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 34, + "EndLine": 41 + } + ] + }, + { + "ID": "prop-types@15.7.2", + "Name": "prop-types", + "Identifier": { + "PURL": "pkg:npm/prop-types@15.7.2" + }, + "Version": "15.7.2", + "DependsOn": [ + "loose-envify@1.4.0", + "object-assign@4.1.1", + "react-is@16.8.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 42, + "EndLine": 51 + } + ] + }, + { + "ID": "react@16.8.6", + "Name": "react", + "Identifier": { + "PURL": "pkg:npm/react@16.8.6" + }, + "Version": "16.8.6", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "loose-envify@1.4.0", + "object-assign@4.1.1", + "prop-types@15.7.2", + "scheduler@0.13.6" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 52, + "EndLine": 62 + } + ] + }, + { + "ID": "react-is@16.8.6", + "Name": "react-is", + "Identifier": { + "PURL": "pkg:npm/react-is@16.8.6" + }, + "Version": "16.8.6", + "Licenses": [ + "MIT" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 63, + "EndLine": 67 + } + ] + }, + { + "ID": "redux@4.0.1", + "Name": "redux", + "Identifier": { + "PURL": "pkg:npm/redux@4.0.1" + }, + "Version": "4.0.1", + "Licenses": [ + "MIT" + ], + "DependsOn": [ + "loose-envify@1.4.0", + "symbol-observable@1.2.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 68, + "EndLine": 76 + } + ] + }, + { + "ID": "scheduler@0.13.6", + "Name": "scheduler", + "Identifier": { + "PURL": "pkg:npm/scheduler@0.13.6" + }, + "Version": "0.13.6", + "DependsOn": [ + "loose-envify@1.4.0", + "object-assign@4.1.1" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 77, + "EndLine": 85 + } + ] + }, + { + "ID": "symbol-observable@1.2.0", + "Name": "symbol-observable", + "Identifier": { + "PURL": "pkg:npm/symbol-observable@1.2.0" + }, + "Version": "1.2.0", + "Layer": {}, + "Locations": [ + { + "StartLine": 86, + "EndLine": 90 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-11358", + "PkgID": "jquery@3.3.9", + "PkgName": "jquery", + "PkgIdentifier": { + "PURL": "pkg:npm/jquery@3.3.9" + }, + "InstalledVersion": "3.3.9", + "FixedVersion": "3.4.0", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-11358", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Npm", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "Title": "jquery: Prototype pollution in object's prototype leading to denial of service, remote code execution, or property injection", + "Description": "jQuery before 3.4.0, as used in Drupal, Backdrop CMS, and other products, mishandles jQuery.extend(true, {}, ...) because of Object.prototype pollution. If an unsanitized source object contained an enumerable __proto__ property, it could extend the native Object.prototype.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-79" + ], + "VendorSeverity": { + "alma": 2, + "amazon": 2, + "arch-linux": 2, + "ghsa": 2, + "nodejs-security-wg": 2, + "nvd": 2, + "oracle-oval": 2, + "redhat": 2, + "ruby-advisory-db": 2, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + "V2Score": 4.3, + "V3Score": 6.1 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + "V3Score": 5.6 + } + }, + "References": [ + "http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00006.html", + "http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00025.html", + "http://packetstormsecurity.com/files/152787/dotCMS-5.1.1-Vulnerable-Dependencies.html", + "http://packetstormsecurity.com/files/153237/RetireJS-CORS-Issue-Script-Execution.html", + "http://packetstormsecurity.com/files/156743/OctoberCMS-Insecure-Dependencies.html", + "http://seclists.org/fulldisclosure/2019/May/10", + "http://seclists.org/fulldisclosure/2019/May/11", + "http://seclists.org/fulldisclosure/2019/May/13", + "http://www.openwall.com/lists/oss-security/2019/06/03/2", + "http://www.securityfocus.com/bid/108023", + "https://access.redhat.com/errata/RHBA-2019:1570", + "https://access.redhat.com/errata/RHSA-2019:1456", + "https://access.redhat.com/errata/RHSA-2019:2587", + "https://access.redhat.com/errata/RHSA-2019:3023", + "https://access.redhat.com/errata/RHSA-2019:3024", + "https://access.redhat.com/security/cve/CVE-2019-11358", + "https://backdropcms.org/security/backdrop-sa-core-2019-009", + "https://blog.jquery.com/2019/04/10/jquery-3-4-0-released/", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11358", + "https://github.com/DanielRuf/snyk-js-jquery-174006?files=1", + "https://github.com/advisories/GHSA-6c3j-c64m-qhgq", + "https://github.com/jquery/jquery/commit/753d591aea698e57d6db58c9f722cd0808619b1b", + "https://github.com/jquery/jquery/pull/4333", + "https://github.com/rails/jquery-rails/blob/master/CHANGELOG.md#434", + "https://hackerone.com/reports/454365", + "https://kb.pulsesecure.net/articles/Pulse_Security_Advisories/SA44601", + "https://linux.oracle.com/cve/CVE-2019-11358.html", + "https://linux.oracle.com/errata/ELSA-2020-4847.html", + "https://lists.apache.org/thread.html/08720ef215ee7ab3386c05a1a90a7d1c852bf0706f176a7816bf65fc@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/519eb0fd45642dcecd9ff74cb3e71c20a4753f7d82e2f07864b5108f@%3Cdev.drill.apache.org%3E", + "https://lists.apache.org/thread.html/5928aa293e39d248266472210c50f176cac1535220f2486e6a7fa844@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/6097cdbd6f0a337bedd9bb5cc441b2d525ff002a96531de367e4259f@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/88fb0362fd40e5b605ea8149f63241537b8b6fb5bfa315391fc5cbb7@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/b0656d359c7d40ec9f39c8cc61bca66802ef9a2a12ee199f5b0c1442@%3Cdev.drill.apache.org%3E", + "https://lists.apache.org/thread.html/b736d0784cf02f5a30fbb4c5902762a15ad6d47e17e2c5a17b7d6205@%3Ccommits.airflow.apache.org%3E", + "https://lists.apache.org/thread.html/ba79cf1658741e9f146e4c59b50aee56656ea95d841d358d006c18b6@%3Ccommits.roller.apache.org%3E", + "https://lists.apache.org/thread.html/bcce5a9c532b386c68dab2f6b3ce8b0cc9b950ec551766e76391caa3@%3Ccommits.nifi.apache.org%3E", + "https://lists.apache.org/thread.html/f9bc3e55f4e28d1dcd1a69aae6d53e609a758e34d2869b4d798e13cc@%3Cissues.drill.apache.org%3E", + "https://lists.apache.org/thread.html/r2041a75d3fc09dec55adfd95d598b38d22715303f65c997c054844c9@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r2baacab6e0acb5a2092eb46ae04fd6c3e8277b4fd79b1ffb7f3254fa@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r38f0d1aa3c923c22977fe7376508f030f22e22c1379fbb155bf29766@%3Cdev.syncope.apache.org%3E", + "https://lists.apache.org/thread.html/r41b5bfe009c845f67d4f68948cc9419ac2d62e287804aafd72892b08@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r7aac081cbddb6baa24b75e74abf0929bf309b176755a53e3ed810355@%3Cdev.flink.apache.org%3E", + "https://lists.apache.org/thread.html/r7d64895cc4dff84d0becfc572b20c0e4bf9bfa7b10c6f5f73e783734@%3Cdev.storm.apache.org%3E", + "https://lists.apache.org/thread.html/r7e8ebccb7c022e41295f6fdb7b971209b83702339f872ddd8cf8bf73@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/rac25da84ecdcd36f6de5ad0d255f4e967209bbbebddb285e231da37d@%3Cissues.flink.apache.org%3E", + "https://lists.apache.org/thread.html/rca37935d661f4689cb4119f1b3b224413b22be161b678e6e6ce0c69b@%3Ccommits.nifi.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2019/05/msg00006.html", + "https://lists.debian.org/debian-lts-announce/2019/05/msg00029.html", + "https://lists.debian.org/debian-lts-announce/2020/02/msg00024.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/4UOAZIFCSZ3ENEFOR5IXX6NFAD3HV7FA/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/5IABSKTYZ5JUGL735UKGXL5YPRYOPUYI/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/KYH3OAGR2RTCHRA5NOKX2TES7SNQMWGO/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/QV3PKZC3PQCO3273HAT76PAQZFBEO4KP/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RLXRX23725JL366CNZGJZ7AQQB7LHQ6F/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WZW27UCJ5CYFL4KFFFMYMIBNMIU2ALG5/", + "https://nvd.nist.gov/vuln/detail/CVE-2019-11358", + "https://seclists.org/bugtraq/2019/Apr/32", + "https://seclists.org/bugtraq/2019/Jun/12", + "https://seclists.org/bugtraq/2019/May/18", + "https://security.netapp.com/advisory/ntap-20190919-0001/", + "https://snyk.io/vuln/SNYK-JS-JQUERY-174006", + "https://www.debian.org/security/2019/dsa-4434", + "https://www.debian.org/security/2019/dsa-4460", + "https://www.drupal.org/sa-core-2019-006", + "https://www.oracle.com//security-alerts/cpujul2021.html", + "https://www.oracle.com/security-alerts/cpuApr2021.html", + "https://www.oracle.com/security-alerts/cpuapr2020.html", + "https://www.oracle.com/security-alerts/cpujan2020.html", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/security-alerts/cpuoct2021.html", + "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html", + "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html", + "https://www.privacy-wise.com/mitigating-cve-2019-11358-in-old-versions-of-jquery/", + "https://www.synology.com/security/advisory/Synology_SA_19_19", + "https://www.tenable.com/security/tns-2019-08", + "https://www.tenable.com/security/tns-2020-02" + ], + "PublishedDate": "2019-04-20T00:29:00Z", + "LastModifiedDate": "2021-10-20T11:15:00Z" + } + ] + } + ] +} diff --git a/integration/testdata/npm-cyclonedx.json.golden b/integration/testdata/npm-cyclonedx.json.golden new file mode 100644 index 000000000000..d7bcc56af462 --- /dev/null +++ b/integration/testdata/npm-cyclonedx.json.golden @@ -0,0 +1,725 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000015", + "version": 1, + "metadata": { + "timestamp": "2021-08-25T12:20:30+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000001", + "type": "application", + "name": "testdata/fixtures/repo/npm", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000002", + "type": "application", + "name": "package-lock.json", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "lang-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "npm" + } + ] + }, + { + "bom-ref": "pkg:npm/asap@2.0.6", + "type": "library", + "name": "asap", + "version": "2.0.6", + "purl": "pkg:npm/asap@2.0.6", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "asap@2.0.6" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "npm" + } + ] + }, + { + "bom-ref": "pkg:npm/jquery@3.3.9", + "type": "library", + "name": "jquery", + "version": "3.3.9", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:npm/jquery@3.3.9", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "jquery@3.3.9" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "npm" + } + ] + }, + { + "bom-ref": "pkg:npm/js-tokens@4.0.0", + "type": "library", + "name": "js-tokens", + "version": "4.0.0", + "purl": "pkg:npm/js-tokens@4.0.0", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "js-tokens@4.0.0" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "npm" + } + ] + }, + { + "bom-ref": "pkg:npm/loose-envify@1.4.0", + "type": "library", + "name": "loose-envify", + "version": "1.4.0", + "purl": "pkg:npm/loose-envify@1.4.0", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "loose-envify@1.4.0" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "npm" + } + ] + }, + { + "bom-ref": "pkg:npm/object-assign@4.1.1", + "type": "library", + "name": "object-assign", + "version": "4.1.1", + "purl": "pkg:npm/object-assign@4.1.1", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "object-assign@4.1.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "npm" + } + ] + }, + { + "bom-ref": "pkg:npm/promise@8.0.3", + "type": "library", + "name": "promise", + "version": "8.0.3", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:npm/promise@8.0.3", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "promise@8.0.3" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "npm" + } + ] + }, + { + "bom-ref": "pkg:npm/prop-types@15.7.2", + "type": "library", + "name": "prop-types", + "version": "15.7.2", + "purl": "pkg:npm/prop-types@15.7.2", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "prop-types@15.7.2" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "npm" + } + ] + }, + { + "bom-ref": "pkg:npm/react-is@16.8.6", + "type": "library", + "name": "react-is", + "version": "16.8.6", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:npm/react-is@16.8.6", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "react-is@16.8.6" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "npm" + } + ] + }, + { + "bom-ref": "pkg:npm/react@16.8.6", + "type": "library", + "name": "react", + "version": "16.8.6", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:npm/react@16.8.6", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "react@16.8.6" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "npm" + } + ] + }, + { + "bom-ref": "pkg:npm/redux@4.0.1", + "type": "library", + "name": "redux", + "version": "4.0.1", + "licenses": [ + { + "license": { + "name": "MIT" + } + } + ], + "purl": "pkg:npm/redux@4.0.1", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "redux@4.0.1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "npm" + } + ] + }, + { + "bom-ref": "pkg:npm/scheduler@0.13.6", + "type": "library", + "name": "scheduler", + "version": "0.13.6", + "purl": "pkg:npm/scheduler@0.13.6", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "scheduler@0.13.6" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "npm" + } + ] + }, + { + "bom-ref": "pkg:npm/symbol-observable@1.2.0", + "type": "library", + "name": "symbol-observable", + "version": "1.2.0", + "purl": "pkg:npm/symbol-observable@1.2.0", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "symbol-observable@1.2.0" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "npm" + } + ] + } + ], + "dependencies": [ + { + "ref": "3ff14136-e09f-4df9-80ea-000000000001", + "dependsOn": [ + "3ff14136-e09f-4df9-80ea-000000000002" + ] + }, + { + "ref": "3ff14136-e09f-4df9-80ea-000000000002", + "dependsOn": [ + "pkg:npm/asap@2.0.6", + "pkg:npm/jquery@3.3.9", + "pkg:npm/js-tokens@4.0.0", + "pkg:npm/loose-envify@1.4.0", + "pkg:npm/object-assign@4.1.1", + "pkg:npm/promise@8.0.3", + "pkg:npm/prop-types@15.7.2", + "pkg:npm/react-is@16.8.6", + "pkg:npm/react@16.8.6", + "pkg:npm/redux@4.0.1", + "pkg:npm/scheduler@0.13.6", + "pkg:npm/symbol-observable@1.2.0" + ] + }, + { + "ref": "pkg:npm/asap@2.0.6", + "dependsOn": [] + }, + { + "ref": "pkg:npm/jquery@3.3.9", + "dependsOn": [] + }, + { + "ref": "pkg:npm/js-tokens@4.0.0", + "dependsOn": [] + }, + { + "ref": "pkg:npm/loose-envify@1.4.0", + "dependsOn": [ + "pkg:npm/js-tokens@4.0.0" + ] + }, + { + "ref": "pkg:npm/object-assign@4.1.1", + "dependsOn": [] + }, + { + "ref": "pkg:npm/promise@8.0.3", + "dependsOn": [ + "pkg:npm/asap@2.0.6" + ] + }, + { + "ref": "pkg:npm/prop-types@15.7.2", + "dependsOn": [ + "pkg:npm/loose-envify@1.4.0", + "pkg:npm/object-assign@4.1.1", + "pkg:npm/react-is@16.8.6" + ] + }, + { + "ref": "pkg:npm/react-is@16.8.6", + "dependsOn": [] + }, + { + "ref": "pkg:npm/react@16.8.6", + "dependsOn": [ + "pkg:npm/loose-envify@1.4.0", + "pkg:npm/object-assign@4.1.1", + "pkg:npm/prop-types@15.7.2", + "pkg:npm/scheduler@0.13.6" + ] + }, + { + "ref": "pkg:npm/redux@4.0.1", + "dependsOn": [ + "pkg:npm/loose-envify@1.4.0", + "pkg:npm/symbol-observable@1.2.0" + ] + }, + { + "ref": "pkg:npm/scheduler@0.13.6", + "dependsOn": [ + "pkg:npm/loose-envify@1.4.0", + "pkg:npm/object-assign@4.1.1" + ] + }, + { + "ref": "pkg:npm/symbol-observable@1.2.0", + "dependsOn": [] + } + ], + "vulnerabilities": [ + { + "id": "CVE-2019-11358", + "source": { + "name": "ghsa", + "url": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "ratings": [ + { + "source": { + "name": "alma" + }, + "severity": "medium" + }, + { + "source": { + "name": "amazon" + }, + "severity": "medium" + }, + { + "source": { + "name": "arch-linux" + }, + "severity": "medium" + }, + { + "source": { + "name": "ghsa" + }, + "severity": "medium" + }, + { + "source": { + "name": "nodejs-security-wg" + }, + "severity": "medium" + }, + { + "source": { + "name": "nvd" + }, + "score": 4.3, + "severity": "medium", + "method": "CVSSv2", + "vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + { + "source": { + "name": "nvd" + }, + "score": 6.1, + "severity": "medium", + "method": "CVSSv31", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + { + "source": { + "name": "oracle-oval" + }, + "severity": "medium" + }, + { + "source": { + "name": "redhat" + }, + "score": 5.6, + "severity": "medium", + "method": "CVSSv3", + "vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L" + }, + { + "source": { + "name": "ruby-advisory-db" + }, + "severity": "medium" + }, + { + "source": { + "name": "ubuntu" + }, + "severity": "low" + } + ], + "cwes": [ + 79 + ], + "description": "jQuery before 3.4.0, as used in Drupal, Backdrop CMS, and other products, mishandles jQuery.extend(true, {}, ...) because of Object.prototype pollution. If an unsanitized source object contained an enumerable __proto__ property, it could extend the native Object.prototype.", + "recommendation": "Upgrade jquery to version 3.4.0", + "advisories": [ + { + "url": "https://avd.aquasec.com/nvd/cve-2019-11358" + }, + { + "url": "http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00006.html" + }, + { + "url": "http://lists.opensuse.org/opensuse-security-announce/2019-08/msg00025.html" + }, + { + "url": "http://packetstormsecurity.com/files/152787/dotCMS-5.1.1-Vulnerable-Dependencies.html" + }, + { + "url": "http://packetstormsecurity.com/files/153237/RetireJS-CORS-Issue-Script-Execution.html" + }, + { + "url": "http://packetstormsecurity.com/files/156743/OctoberCMS-Insecure-Dependencies.html" + }, + { + "url": "http://seclists.org/fulldisclosure/2019/May/10" + }, + { + "url": "http://seclists.org/fulldisclosure/2019/May/11" + }, + { + "url": "http://seclists.org/fulldisclosure/2019/May/13" + }, + { + "url": "http://www.openwall.com/lists/oss-security/2019/06/03/2" + }, + { + "url": "http://www.securityfocus.com/bid/108023" + }, + { + "url": "https://access.redhat.com/errata/RHBA-2019:1570" + }, + { + "url": "https://access.redhat.com/errata/RHSA-2019:1456" + }, + { + "url": "https://access.redhat.com/errata/RHSA-2019:2587" + }, + { + "url": "https://access.redhat.com/errata/RHSA-2019:3023" + }, + { + "url": "https://access.redhat.com/errata/RHSA-2019:3024" + }, + { + "url": "https://access.redhat.com/security/cve/CVE-2019-11358" + }, + { + "url": "https://backdropcms.org/security/backdrop-sa-core-2019-009" + }, + { + "url": "https://blog.jquery.com/2019/04/10/jquery-3-4-0-released/" + }, + { + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11358" + }, + { + "url": "https://github.com/DanielRuf/snyk-js-jquery-174006?files=1" + }, + { + "url": "https://github.com/advisories/GHSA-6c3j-c64m-qhgq" + }, + { + "url": "https://github.com/jquery/jquery/commit/753d591aea698e57d6db58c9f722cd0808619b1b" + }, + { + "url": "https://github.com/jquery/jquery/pull/4333" + }, + { + "url": "https://github.com/rails/jquery-rails/blob/master/CHANGELOG.md#434" + }, + { + "url": "https://hackerone.com/reports/454365" + }, + { + "url": "https://kb.pulsesecure.net/articles/Pulse_Security_Advisories/SA44601" + }, + { + "url": "https://linux.oracle.com/cve/CVE-2019-11358.html" + }, + { + "url": "https://linux.oracle.com/errata/ELSA-2020-4847.html" + }, + { + "url": "https://lists.apache.org/thread.html/08720ef215ee7ab3386c05a1a90a7d1c852bf0706f176a7816bf65fc@%3Ccommits.airflow.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/519eb0fd45642dcecd9ff74cb3e71c20a4753f7d82e2f07864b5108f@%3Cdev.drill.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/5928aa293e39d248266472210c50f176cac1535220f2486e6a7fa844@%3Ccommits.airflow.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/6097cdbd6f0a337bedd9bb5cc441b2d525ff002a96531de367e4259f@%3Ccommits.airflow.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/88fb0362fd40e5b605ea8149f63241537b8b6fb5bfa315391fc5cbb7@%3Ccommits.airflow.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/b0656d359c7d40ec9f39c8cc61bca66802ef9a2a12ee199f5b0c1442@%3Cdev.drill.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/b736d0784cf02f5a30fbb4c5902762a15ad6d47e17e2c5a17b7d6205@%3Ccommits.airflow.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/ba79cf1658741e9f146e4c59b50aee56656ea95d841d358d006c18b6@%3Ccommits.roller.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/bcce5a9c532b386c68dab2f6b3ce8b0cc9b950ec551766e76391caa3@%3Ccommits.nifi.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/f9bc3e55f4e28d1dcd1a69aae6d53e609a758e34d2869b4d798e13cc@%3Cissues.drill.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/r2041a75d3fc09dec55adfd95d598b38d22715303f65c997c054844c9@%3Cissues.flink.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/r2baacab6e0acb5a2092eb46ae04fd6c3e8277b4fd79b1ffb7f3254fa@%3Cissues.flink.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/r38f0d1aa3c923c22977fe7376508f030f22e22c1379fbb155bf29766@%3Cdev.syncope.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/r41b5bfe009c845f67d4f68948cc9419ac2d62e287804aafd72892b08@%3Cissues.flink.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/r7aac081cbddb6baa24b75e74abf0929bf309b176755a53e3ed810355@%3Cdev.flink.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/r7d64895cc4dff84d0becfc572b20c0e4bf9bfa7b10c6f5f73e783734@%3Cdev.storm.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/r7e8ebccb7c022e41295f6fdb7b971209b83702339f872ddd8cf8bf73@%3Cissues.flink.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/rac25da84ecdcd36f6de5ad0d255f4e967209bbbebddb285e231da37d@%3Cissues.flink.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/rca37935d661f4689cb4119f1b3b224413b22be161b678e6e6ce0c69b@%3Ccommits.nifi.apache.org%3E" + }, + { + "url": "https://lists.debian.org/debian-lts-announce/2019/05/msg00006.html" + }, + { + "url": "https://lists.debian.org/debian-lts-announce/2019/05/msg00029.html" + }, + { + "url": "https://lists.debian.org/debian-lts-announce/2020/02/msg00024.html" + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/4UOAZIFCSZ3ENEFOR5IXX6NFAD3HV7FA/" + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/5IABSKTYZ5JUGL735UKGXL5YPRYOPUYI/" + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/KYH3OAGR2RTCHRA5NOKX2TES7SNQMWGO/" + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/QV3PKZC3PQCO3273HAT76PAQZFBEO4KP/" + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RLXRX23725JL366CNZGJZ7AQQB7LHQ6F/" + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WZW27UCJ5CYFL4KFFFMYMIBNMIU2ALG5/" + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-11358" + }, + { + "url": "https://seclists.org/bugtraq/2019/Apr/32" + }, + { + "url": "https://seclists.org/bugtraq/2019/Jun/12" + }, + { + "url": "https://seclists.org/bugtraq/2019/May/18" + }, + { + "url": "https://security.netapp.com/advisory/ntap-20190919-0001/" + }, + { + "url": "https://snyk.io/vuln/SNYK-JS-JQUERY-174006" + }, + { + "url": "https://www.debian.org/security/2019/dsa-4434" + }, + { + "url": "https://www.debian.org/security/2019/dsa-4460" + }, + { + "url": "https://www.drupal.org/sa-core-2019-006" + }, + { + "url": "https://www.oracle.com//security-alerts/cpujul2021.html" + }, + { + "url": "https://www.oracle.com/security-alerts/cpuApr2021.html" + }, + { + "url": "https://www.oracle.com/security-alerts/cpuapr2020.html" + }, + { + "url": "https://www.oracle.com/security-alerts/cpujan2020.html" + }, + { + "url": "https://www.oracle.com/security-alerts/cpujan2021.html" + }, + { + "url": "https://www.oracle.com/security-alerts/cpujul2020.html" + }, + { + "url": "https://www.oracle.com/security-alerts/cpuoct2020.html" + }, + { + "url": "https://www.oracle.com/security-alerts/cpuoct2021.html" + }, + { + "url": "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html" + }, + { + "url": "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html" + }, + { + "url": "https://www.privacy-wise.com/mitigating-cve-2019-11358-in-old-versions-of-jquery/" + }, + { + "url": "https://www.synology.com/security/advisory/Synology_SA_19_19" + }, + { + "url": "https://www.tenable.com/security/tns-2019-08" + }, + { + "url": "https://www.tenable.com/security/tns-2020-02" + } + ], + "published": "2019-04-20T00:29:00+00:00", + "updated": "2021-10-20T11:15:00+00:00", + "affects": [ + { + "ref": "pkg:npm/jquery@3.3.9", + "versions": [ + { + "version": "3.3.9", + "status": "affected" + } + ] + } + ] + } + ] +} diff --git a/pkg/commands/convert/run.go b/pkg/commands/convert/run.go index 428d6b5b0b4b..584bc6f6bddc 100644 --- a/pkg/commands/convert/run.go +++ b/pkg/commands/convert/run.go @@ -8,6 +8,8 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/commands/operation" + "github.com/aquasecurity/trivy/pkg/dependency" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/report" @@ -35,6 +37,7 @@ func Run(ctx context.Context, opts flag.Options) (err error) { return xerrors.New("AWS and Kubernetes scanning reports are not yet supported") } + compat(&r) if err = result.Filter(ctx, r, opts.FilterOpts()); err != nil { return xerrors.Errorf("unable to filter results: %w", err) } @@ -46,3 +49,30 @@ func Run(ctx context.Context, opts flag.Options) (err error) { return operation.Exit(opts, r.Results.Failed(), r.Metadata) } + +// compat converts the JSON report to the latest format +func compat(r *types.Report) { + for i, res := range r.Results { + pkgs := make(map[string]ftypes.Package, len(res.Packages)) + for j, pkg := range res.Packages { + if pkg.Identifier.UID != "" { + continue + } + // Fill in the UID field since older JSON reports don't have it + pkg.Identifier.UID = dependency.UID(res.Target, pkg) + pkgs[pkg.ID+pkg.FilePath] = pkg + r.Results[i].Packages[j] = pkg + } + + for j, vuln := range res.Vulnerabilities { + if vuln.PkgIdentifier.UID != "" { + continue + } + if pkg, ok := pkgs[vuln.PkgID+vuln.PkgPath]; !ok { + continue + } else { + r.Results[i].Vulnerabilities[j].PkgIdentifier = pkg.Identifier + } + } + } +} diff --git a/pkg/dependency/id.go b/pkg/dependency/id.go index 8f01bf23123a..577ed5d0ac41 100644 --- a/pkg/dependency/id.go +++ b/pkg/dependency/id.go @@ -1,9 +1,13 @@ package dependency import ( + "fmt" "strings" + "github.com/mitchellh/hashstructure/v2" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" ) // ID returns a unique ID for the given library. @@ -30,3 +34,19 @@ func ID(ltype types.LangType, name, version string) string { } return name + sep + version } + +// UID calculates the hash of the package for the unique ID +func UID(filePath string, pkg types.Package) string { + v := map[string]any{ + "filePath": filePath, // To differentiate the hash of the same package but different file path + "pkg": pkg, + } + hash, err := hashstructure.Hash(v, hashstructure.FormatV2, &hashstructure.HashOptions{ + ZeroNil: true, + IgnoreZeroValue: true, + }) + if err != nil { + log.Warn("Failed to calculate the package hash", log.String("pkg", pkg.Name), log.Err(err)) + } + return fmt.Sprintf("%x", hash) +} diff --git a/pkg/fanal/applier/docker.go b/pkg/fanal/applier/docker.go index d4f88bc851a7..c1c21f236b22 100644 --- a/pkg/fanal/applier/docker.go +++ b/pkg/fanal/applier/docker.go @@ -6,10 +6,10 @@ import ( "time" "github.com/knqyf263/nested" - "github.com/mitchellh/hashstructure/v2" "github.com/package-url/packageurl-go" "github.com/samber/lo" + "github.com/aquasecurity/trivy/pkg/dependency" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/purl" @@ -223,7 +223,7 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { if mergedLayer.OS.Family != "" { mergedLayer.Packages[i].Identifier.PURL = newPURL(mergedLayer.OS.Family, types.Metadata{OS: &mergedLayer.OS}, pkg) } - mergedLayer.Packages[i].Identifier.UID = calcPkgUID("", pkg) + mergedLayer.Packages[i].Identifier.UID = dependency.UID("", pkg) // Only debian packages if licenses, ok := dpkgLicenses[pkg.Name]; ok { @@ -244,7 +244,7 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { if pkg.Identifier.PURL == nil { app.Packages[i].Identifier.PURL = newPURL(app.Type, types.Metadata{}, pkg) } - app.Packages[i].Identifier.UID = calcPkgUID(app.FilePath, pkg) + app.Packages[i].Identifier.UID = dependency.UID(app.FilePath, pkg) } } @@ -263,22 +263,6 @@ func newPURL(pkgType ftypes.TargetType, metadata types.Metadata, pkg ftypes.Pack return p.Unwrap() } -// calcPkgUID calculates the hash of the package for the unique ID -func calcPkgUID(filePath string, pkg ftypes.Package) string { - v := map[string]any{ - "filePath": filePath, // To differentiate the hash of the same package but different file path - "pkg": pkg, - } - hash, err := hashstructure.Hash(v, hashstructure.FormatV2, &hashstructure.HashOptions{ - ZeroNil: true, - IgnoreZeroValue: true, - }) - if err != nil { - log.Warn("Failed to calculate the package hash", log.String("pkg", pkg.Name), log.Err(err)) - } - return fmt.Sprintf("%x", hash) -} - // aggregate merges all packages installed by pip/gem/npm/jar/conda into each application func aggregate(detail *ftypes.ArtifactDetail) { var apps []ftypes.Application diff --git a/pkg/sbom/core/bom.go b/pkg/sbom/core/bom.go index 1fb3078d0c6b..51875bff8738 100644 --- a/pkg/sbom/core/bom.go +++ b/pkg/sbom/core/bom.go @@ -194,7 +194,6 @@ type Relationship struct { type Vulnerability struct { dtypes.Vulnerability ID string - PkgID string PkgName string InstalledVersion string FixedVersion string diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go index 9209c0c0100c..9dc28a2ab812 100644 --- a/pkg/sbom/cyclonedx/marshal_test.go +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -24,6 +24,46 @@ import ( "github.com/aquasecurity/trivy/pkg/uuid" ) +var ( + binutilsIdentifier = ftypes.PkgIdentifier{ + UID: "7CC457C23685235A", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "centos", + Name: "binutils", + Version: "2.30-93.el8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "aarch64", + }, + { + Key: "distro", + Value: "centos-8.3.2011", + }, + }, + }, + } + + actionpack700Identifier = ftypes.PkgIdentifier{ + UID: "DFF5FF40889105B2", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actionpack", + Version: "7.0.0", + }, + } + + actionpack701Identifier = ftypes.PkgIdentifier{ + UID: "6B0A6392BAA7D584", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeGem, + Name: "actionpack", + Version: "7.0.1", + }, + } +) + func TestMarshaler_MarshalReport(t *testing.T) { testSBOM := core.NewBOM(core.Options{GenerateBOMRef: true}) testSBOM.AddComponent(&core.Component{ @@ -74,30 +114,13 @@ func TestMarshaler_MarshalReport(t *testing.T) { Type: ftypes.CentOS, Packages: []ftypes.Package{ { - ID: "binutils@2.30-93.el8", - Name: "binutils", - Version: "2.30", - Release: "93.el8", - Epoch: 0, - Arch: "aarch64", - Identifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeRPM, - Namespace: "centos", - Name: "binutils", - Version: "2.30-93.el8", - Qualifiers: packageurl.Qualifiers{ - { - Key: "arch", - Value: "aarch64", - }, - { - Key: "distro", - Value: "centos-8.3.2011", - }, - }, - }, - }, + ID: "binutils@2.30-93.el8", + Name: "binutils", + Version: "2.30", + Release: "93.el8", + Epoch: 0, + Arch: "aarch64", + Identifier: binutilsIdentifier, SrcName: "binutils", SrcVersion: "2.30", SrcRelease: "93.el8", @@ -124,6 +147,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Name: "Red Hat OVAL v2", URL: "https://www.redhat.com/security/data/oval/v2/", }, + PkgIdentifier: binutilsIdentifier, Vulnerability: dtypes.Vulnerability{ Title: "binutils: Use-after-free in the error function", Description: "In GNU Binutils 2.31.1, there is a use-after-free in the error function in elfcomm.c when called from the process_archive function in readelf.c via a crafted ELF file.", @@ -158,23 +182,18 @@ func TestMarshaler_MarshalReport(t *testing.T) { Packages: []ftypes.Package{ { // This package conflicts - ID: "actionpack@7.0.0", - Name: "actionpack", - Version: "7.0.0", - Identifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeGem, - Name: "actionpack", - Version: "7.0.0", - }, - }, - Indirect: false, + ID: "actionpack@7.0.0", + Name: "actionpack", + Version: "7.0.0", + Identifier: actionpack700Identifier, + Indirect: false, }, { ID: "actioncontroller@7.0.0", Name: "actioncontroller", Version: "7.0.0", Identifier: ftypes.PkgIdentifier{ + UID: "41ED2619CA718170", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGem, Name: "actioncontroller", @@ -195,16 +214,10 @@ func TestMarshaler_MarshalReport(t *testing.T) { Packages: []ftypes.Package{ { // This package conflicts - ID: "actionpack@7.0.0", - Name: "actionpack", - Version: "7.0.0", - Identifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeGem, - Name: "actionpack", - Version: "7.0.0", - }, - }, + ID: "actionpack@7.0.0", + Name: "actionpack", + Version: "7.0.0", + Identifier: actionpack700Identifier, }, }, }, @@ -218,6 +231,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Name: "Newtonsoft.Json", Version: "9.0.1", Identifier: ftypes.PkgIdentifier{ + UID: "94AB97F672F97AFB", PURL: &packageurl.PackageURL{ Type: packageurl.TypeNuget, Name: "Newtonsoft.Json", @@ -236,6 +250,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Name: "golang.org/x/crypto", Version: "v0.0.0-20210421170649-83a5a9bb288b", Identifier: ftypes.PkgIdentifier{ + UID: "B7183ED2CF7EB470", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "golang.org/x", @@ -698,6 +713,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Epoch: 1, Arch: "aarch64", Identifier: ftypes.PkgIdentifier{ + UID: "2FF7A09FA4E6AA2E", PURL: &packageurl.PackageURL{ Type: packageurl.TypeRPM, Namespace: "centos", @@ -738,6 +754,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Epoch: 0, Arch: "aarch64", Identifier: ftypes.PkgIdentifier{ + UID: "2DCAB94016E57F8E", PURL: &packageurl.PackageURL{ Type: packageurl.TypeRPM, Namespace: "centos", @@ -771,32 +788,20 @@ func TestMarshaler_MarshalReport(t *testing.T) { Type: ftypes.GemSpec, Packages: []ftypes.Package{ { - ID: "actionpack@7.0.0", - Name: "actionpack", - Version: "7.0.0", - Identifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeGem, - Name: "actionpack", - Version: "7.0.0", - }, - }, + ID: "actionpack@7.0.0", + Name: "actionpack", + Version: "7.0.0", + Identifier: actionpack700Identifier, Layer: ftypes.Layer{ DiffID: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488", }, FilePath: "tools/project-john/specifications/actionpack.gemspec", }, { - ID: "actionpack@7.0.1", - Name: "actionpack", - Version: "7.0.1", - Identifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeGem, - Name: "actionpack", - Version: "7.0.1", - }, - }, + ID: "actionpack@7.0.1", + Name: "actionpack", + Version: "7.0.1", + Identifier: actionpack701Identifier, Layer: ftypes.Layer{ DiffID: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488", }, @@ -805,17 +810,11 @@ func TestMarshaler_MarshalReport(t *testing.T) { }, Vulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2022-23633", - PkgID: "actionpack@7.0.0", - PkgName: "actionpack", - PkgPath: "tools/project-john/specifications/actionpack.gemspec", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeGem, - Name: "actionpack", - Version: "7.0.0", - }, - }, + VulnerabilityID: "CVE-2022-23633", + PkgID: "actionpack@7.0.0", + PkgName: "actionpack", + PkgPath: "tools/project-john/specifications/actionpack.gemspec", + PkgIdentifier: actionpack700Identifier, InstalledVersion: "7.0.0", FixedVersion: "~> 5.2.6, >= 5.2.6.2, ~> 6.0.4, >= 6.0.4.6, ~> 6.1.4, >= 6.1.4.6, >= 7.0.2.2", SeveritySource: vulnerability.RubySec, @@ -855,17 +854,11 @@ func TestMarshaler_MarshalReport(t *testing.T) { }, }, { - VulnerabilityID: "CVE-2022-23633", - PkgID: "actionpack@7.0.1", - PkgName: "actionpack", - PkgPath: "tools/project-doe/specifications/actionpack.gemspec", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeGem, - Name: "actionpack", - Version: "7.0.1", - }, - }, + VulnerabilityID: "CVE-2022-23633", + PkgID: "actionpack@7.0.1", + PkgName: "actionpack", + PkgPath: "tools/project-doe/specifications/actionpack.gemspec", + PkgIdentifier: actionpack701Identifier, InstalledVersion: "7.0.1", FixedVersion: "~> 5.2.6, >= 5.2.6.2, ~> 6.0.4, >= 6.0.4.6, ~> 6.1.4, >= 6.1.4.6, >= 7.0.2.2", SeveritySource: vulnerability.RubySec, @@ -1241,6 +1234,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Name: "actioncable", Version: "6.1.4.1", Identifier: ftypes.PkgIdentifier{ + UID: "2E6CF0E3CD6949BD", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGem, Name: "actioncable", @@ -1259,6 +1253,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Name: "org.springframework:spring-web", Version: "5.3.22", Identifier: ftypes.PkgIdentifier{ + UID: "38DDCC9B589D3124", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "org.springframework", @@ -1280,6 +1275,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Name: "@babel/helper-string-parser", Version: "7.23.4", Identifier: ftypes.PkgIdentifier{ + UID: "F4C833D7F3FD9ECF", PURL: &packageurl.PackageURL{ Type: packageurl.TypeNPM, Namespace: "@babel", @@ -1458,6 +1454,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Version: "2.13.4.1", Identifier: ftypes.PkgIdentifier{ BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1?file_path=jackson-databind-2.13.4.1.jar", + UID: "9A5066570222D04C", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "com.fasterxml.jackson.core", @@ -1475,6 +1472,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { PkgPath: "jackson-databind-2.13.4.1.jar", PkgIdentifier: ftypes.PkgIdentifier{ BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1?file_path=jackson-databind-2.13.4.1.jar", + UID: "9A5066570222D04C", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "com.fasterxml.jackson.core", @@ -1641,6 +1639,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Name: "org.apache.nifi:nifi-dbcp-base", Version: "1.20.0", Identifier: ftypes.PkgIdentifier{ + UID: "6F266C79E57ADC38", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "org.apache.nifi", @@ -1654,6 +1653,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Name: "org.apache.nifi:nifi-hikari-dbcp-service", Version: "1.20.0", Identifier: ftypes.PkgIdentifier{ + UID: "3EA16F0A4CAB50F9", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "org.apache.nifi", @@ -1670,6 +1670,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { PkgName: "org.apache.nifi:nifi-dbcp-base", PkgPath: "nifi-dbcp-base-1.20.0.jar", PkgIdentifier: ftypes.PkgIdentifier{ + UID: "6F266C79E57ADC38", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "org.apache.nifi", @@ -1720,6 +1721,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { PkgName: "org.apache.nifi:nifi-hikari-dbcp-service", PkgPath: "nifi-hikari-dbcp-service-1.20.0.jar", PkgIdentifier: ftypes.PkgIdentifier{ + UID: "3EA16F0A4CAB50F9", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "org.apache.nifi", @@ -1939,6 +1941,7 @@ func TestMarshaler_MarshalReport(t *testing.T) { Name: "ruby-typeprof", Version: "0.20.1", Identifier: ftypes.PkgIdentifier{ + UID: "C861FD5FC7AC663F", PURL: &packageurl.PackageURL{ Type: packageurl.TypeNPM, Name: "ruby-typeprof", diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index 9672f1648dc6..64a24dbdcb07 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -174,25 +174,33 @@ func (e *Encoder) encodePackages(parent *core.Component, result types.Result) { vulns := make(map[string][]core.Vulnerability) for _, vuln := range result.Vulnerabilities { v := e.vulnerability(vuln) - vulns[v.PkgID] = append(vulns[v.PkgID], v) + vulns[vuln.PkgIdentifier.UID] = append(vulns[vuln.PkgIdentifier.UID], v) } // Convert packages into components and add them to the BOM parentRelationship := core.RelationshipContains + + // UID => Package Component components := make(map[string]*core.Component, len(result.Packages)) + // PkgID => Package Component + dependencies := make(map[string]*core.Component, len(result.Packages)) for i, pkg := range result.Packages { pkgID := lo.Ternary(pkg.ID == "", fmt.Sprintf("%s@%s", pkg.Name, pkg.Version), pkg.ID) result.Packages[i].ID = pkgID // Convert packages to components c := e.component(result, pkg) - components[pkgID+pkg.FilePath] = c + components[pkg.Identifier.UID] = c + + // For dependencies: the key "pkgID" might be duplicated in aggregated packages, + // but it doesn't matter as they don't have "DependsOn". + dependencies[pkgID] = c // Add a component e.bom.AddComponent(c) // Add vulnerabilities - if vv := vulns[pkgID]; vv != nil { + if vv := vulns[pkg.Identifier.UID]; vv != nil { e.bom.AddVulnerabilities(c, vv) } @@ -211,7 +219,7 @@ func (e *Encoder) encodePackages(parent *core.Component, result types.Result) { if pkg.Relationship == ftypes.RelationshipRoot { continue } - c := components[pkg.ID+pkg.FilePath] + c := components[pkg.Identifier.UID] // Add a relationship between the parent and the package if needed if e.belongToParent(pkg, parents) { @@ -220,7 +228,7 @@ func (e *Encoder) encodePackages(parent *core.Component, result types.Result) { // Add relationships between the package and its dependencies for _, dep := range pkg.DependsOn { - dependsOn, ok := components[dep] + dependsOn, ok := dependencies[dep] if !ok { continue } @@ -369,7 +377,6 @@ func (*Encoder) vulnerability(vuln types.DetectedVulnerability) core.Vulnerabili return core.Vulnerability{ Vulnerability: vuln.Vulnerability, ID: vuln.VulnerabilityID, - PkgID: lo.Ternary(vuln.PkgID == "", fmt.Sprintf("%s@%s", vuln.PkgName, vuln.InstalledVersion), vuln.PkgID), PkgName: vuln.PkgName, InstalledVersion: vuln.InstalledVersion, FixedVersion: vuln.FixedVersion, diff --git a/pkg/sbom/io/encode_test.go b/pkg/sbom/io/encode_test.go index d165b64c3e80..06109c963d4f 100644 --- a/pkg/sbom/io/encode_test.go +++ b/pkg/sbom/io/encode_test.go @@ -55,6 +55,7 @@ func TestEncoder_Encode(t *testing.T) { Name: "libc6", Version: "2.37-15.1", Identifier: ftypes.PkgIdentifier{ + UID: "33654D2C483FC3AD", PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Name: "libc6", @@ -67,6 +68,7 @@ func TestEncoder_Encode(t *testing.T) { Name: "curl", Version: "7.50.3-1", Identifier: ftypes.PkgIdentifier{ + UID: "51BA9E006222819D", PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Name: "curl", @@ -88,6 +90,9 @@ func TestEncoder_Encode(t *testing.T) { Vulnerability: dtypes.Vulnerability{ Severity: "HIGH", }, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "51BA9E006222819D", + }, }, }, }, @@ -97,20 +102,66 @@ func TestEncoder_Encode(t *testing.T) { Class: types.ClassLangPkg, Packages: []ftypes.Package{ { - ID: "org.apache.xmlgraphics/batik-anim:1.9.1", - Name: "org.apache.xmlgraphics/batik-anim", - Version: "1.9.1", - FilePath: "/app/batik-anim-1.9.1.jar", + ID: "com.fasterxml.jackson.core:jackson-databind:2.13.4", + Name: "com.fasterxml.jackson.core:jackson-databind", + Version: "2.13.4", + FilePath: "/foo/jackson-databind-2.13.4.jar", Identifier: ftypes.PkgIdentifier{ + UID: "A6BD5A2FE5C00E10", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, - Namespace: "org.apache.xmlgraphics", - Name: "batik-anim", - Version: "1.9.1", + Namespace: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.13.4", + }, + }, + }, + { + ID: "com.fasterxml.jackson.core:jackson-databind:2.13.4", + Name: "com.fasterxml.jackson.core:jackson-databind", + Version: "2.13.4", + FilePath: "/bar/jackson-databind-2.13.4.jar", + Identifier: ftypes.PkgIdentifier{ + UID: "64244651208EC759", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.13.4", }, }, }, }, + Vulnerabilities: []types.DetectedVulnerability{ + { + PkgName: "com.fasterxml.jackson.core:jackson-databind", + PkgID: "com.fasterxml.jackson.core:jackson-databind:2.13.4", + VulnerabilityID: "CVE-2022-42003", + InstalledVersion: "2.13.4", + FixedVersion: "2.12.7.1, 2.13.4.2", + PkgPath: "/foo/jackson-databind-2.13.4.jar", + Vulnerability: dtypes.Vulnerability{ + Severity: "HIGH", + }, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "A6BD5A2FE5C00E10", + }, + }, + { + PkgName: "com.fasterxml.jackson.core:jackson-databind", + PkgID: "com.fasterxml.jackson.core:jackson-databind:2.13.4", + VulnerabilityID: "CVE-2022-42003", + InstalledVersion: "2.13.4", + FixedVersion: "2.12.7.1, 2.13.4.2", + PkgPath: "/bar/jackson-databind-2.13.4.jar", + Vulnerability: dtypes.Vulnerability{ + Severity: "HIGH", + }, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "64244651208EC759", + }, + }, + }, }, }, }, @@ -185,6 +236,7 @@ func TestEncoder_Encode(t *testing.T) { }, }, PkgIdentifier: ftypes.PkgIdentifier{ + UID: "33654D2C483FC3AD", PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Name: "libc6", @@ -208,6 +260,7 @@ func TestEncoder_Encode(t *testing.T) { }, }, PkgIdentifier: ftypes.PkgIdentifier{ + UID: "51BA9E006222819D", PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Name: "curl", @@ -218,22 +271,22 @@ func TestEncoder_Encode(t *testing.T) { }, uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"): { Type: core.TypeLibrary, - Group: "org.apache.xmlgraphics", - Name: "batik-anim", - Version: "1.9.1", + Group: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.13.4", Files: []core.File{ { - Path: "/app/batik-anim-1.9.1.jar", + Path: "/foo/jackson-databind-2.13.4.jar", }, }, Properties: []core.Property{ { Name: core.PropertyFilePath, - Value: "/app/batik-anim-1.9.1.jar", + Value: "/foo/jackson-databind-2.13.4.jar", }, { Name: core.PropertyPkgID, - Value: "org.apache.xmlgraphics/batik-anim:1.9.1", + Value: "com.fasterxml.jackson.core:jackson-databind:2.13.4", }, { Name: core.PropertyPkgType, @@ -241,13 +294,49 @@ func TestEncoder_Encode(t *testing.T) { }, }, PkgIdentifier: ftypes.PkgIdentifier{ + UID: "A6BD5A2FE5C00E10", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, - Namespace: "org.apache.xmlgraphics", - Name: "batik-anim", - Version: "1.9.1", + Namespace: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.13.4", }, - BOMRef: "pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000005", + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000006"): { + Type: core.TypeLibrary, + Group: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.13.4", + Files: []core.File{ + { + Path: "/bar/jackson-databind-2.13.4.jar", + }, + }, + Properties: []core.Property{ + { + Name: core.PropertyFilePath, + Value: "/bar/jackson-databind-2.13.4.jar", + }, + { + Name: core.PropertyPkgID, + Value: "com.fasterxml.jackson.core:jackson-databind:2.13.4", + }, + { + Name: core.PropertyPkgType, + Value: "jar", + }, + }, + PkgIdentifier: ftypes.PkgIdentifier{ + UID: "64244651208EC759", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "com.fasterxml.jackson.core", + Name: "jackson-databind", + Version: "2.13.4", + }, + BOMRef: "3ff14136-e09f-4df9-80ea-000000000006", }, }, }, @@ -261,6 +350,10 @@ func TestEncoder_Encode(t *testing.T) { Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"), Type: core.RelationshipContains, }, + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000006"), + Type: core.RelationshipContains, + }, }, uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): { { @@ -280,12 +373,12 @@ func TestEncoder_Encode(t *testing.T) { }, }, uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"): nil, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000006"): nil, }, wantVulns: map[uuid.UUID][]core.Vulnerability{ uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000004"): { { ID: "CVE-2021-22876", - PkgID: "curl@7.50.3-1", PkgName: "curl", InstalledVersion: "7.50.3-1", FixedVersion: "7.50.3-1+deb9u1", @@ -294,6 +387,28 @@ func TestEncoder_Encode(t *testing.T) { }, }, }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"): { + { + ID: "CVE-2022-42003", + PkgName: "com.fasterxml.jackson.core:jackson-databind", + InstalledVersion: "2.13.4", + FixedVersion: "2.12.7.1, 2.13.4.2", + Vulnerability: dtypes.Vulnerability{ + Severity: "HIGH", + }, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000006"): { + { + ID: "CVE-2022-42003", + PkgName: "com.fasterxml.jackson.core:jackson-databind", + InstalledVersion: "2.13.4", + FixedVersion: "2.12.7.1, 2.13.4.2", + Vulnerability: dtypes.Vulnerability{ + Severity: "HIGH", + }, + }, + }, }, }, { @@ -312,6 +427,7 @@ func TestEncoder_Encode(t *testing.T) { ID: "github.com/org/root", Name: "github.com/org/root", Identifier: ftypes.PkgIdentifier{ + UID: "03D528806D964D22", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "github.com/org", @@ -328,6 +444,7 @@ func TestEncoder_Encode(t *testing.T) { Name: "github.com/org/direct", Version: "v1.0.0", Identifier: ftypes.PkgIdentifier{ + UID: "A74CADAD4D9805FF", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "github.com/org", @@ -345,6 +462,7 @@ func TestEncoder_Encode(t *testing.T) { Name: "github.com/org/indirect", Version: "2.0.0", Identifier: ftypes.PkgIdentifier{ + UID: "955AB4E7E24AC085", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "github.com/org", @@ -359,6 +477,7 @@ func TestEncoder_Encode(t *testing.T) { Name: "stdlib", Version: "1.22.1", Identifier: ftypes.PkgIdentifier{ + UID: "49728B9674E318A6", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Name: "stdlib", @@ -418,6 +537,7 @@ func TestEncoder_Encode(t *testing.T) { }, }, PkgIdentifier: ftypes.PkgIdentifier{ + UID: "03D528806D964D22", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "github.com/org", @@ -442,6 +562,7 @@ func TestEncoder_Encode(t *testing.T) { }, }, PkgIdentifier: ftypes.PkgIdentifier{ + UID: "A74CADAD4D9805FF", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "github.com/org", @@ -467,6 +588,7 @@ func TestEncoder_Encode(t *testing.T) { }, }, PkgIdentifier: ftypes.PkgIdentifier{ + UID: "955AB4E7E24AC085", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "github.com/org", @@ -492,6 +614,7 @@ func TestEncoder_Encode(t *testing.T) { }, }, PkgIdentifier: ftypes.PkgIdentifier{ + UID: "49728B9674E318A6", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Name: "stdlib", @@ -552,6 +675,7 @@ func TestEncoder_Encode(t *testing.T) { Name: "org.apache.logging.log4j:log4j-core", Version: "2.23.1", Identifier: ftypes.PkgIdentifier{ + UID: "6C0AE96901617503", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "org.apache.logging.log4j", @@ -598,6 +722,7 @@ func TestEncoder_Encode(t *testing.T) { Name: "org.apache.logging.log4j:log4j-core", Version: "2.23.1", Identifier: ftypes.PkgIdentifier{ + UID: "6C0AE96901617503", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "org.apache.logging.log4j", @@ -698,6 +823,7 @@ var ( Group: "org.apache.logging.log4j", Version: "2.23.1", PkgIdentifier: ftypes.PkgIdentifier{ + UID: "6C0AE96901617503", BOMRef: "pkg:maven/org.apache.logging.log4j/log4j-core@2.23.1", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, diff --git a/pkg/sbom/spdx/marshal_test.go b/pkg/sbom/spdx/marshal_test.go index 02585ceaa418..122e529c14d5 100644 --- a/pkg/sbom/spdx/marshal_test.go +++ b/pkg/sbom/spdx/marshal_test.go @@ -64,6 +64,7 @@ func TestMarshaler_Marshal(t *testing.T) { Epoch: 0, Arch: "aarch64", Identifier: ftypes.PkgIdentifier{ + UID: "F4C10A4371C93487", PURL: &packageurl.PackageURL{ Type: packageurl.TypeRPM, Namespace: "centos", @@ -101,6 +102,7 @@ func TestMarshaler_Marshal(t *testing.T) { Name: "actionpack", Version: "7.0.1", Identifier: ftypes.PkgIdentifier{ + UID: "B1A9DE534F2737AF", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGem, Name: "actionpack", @@ -112,6 +114,7 @@ func TestMarshaler_Marshal(t *testing.T) { Name: "actioncontroller", Version: "7.0.1", Identifier: ftypes.PkgIdentifier{ + UID: "1628B51BD543965D", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGem, Name: "actioncontroller", @@ -130,6 +133,7 @@ func TestMarshaler_Marshal(t *testing.T) { Name: "actionpack", Version: "7.0.1", Identifier: ftypes.PkgIdentifier{ + UID: "92D6B6D3FF6F8FF5", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGem, Name: "actionpack", @@ -387,6 +391,7 @@ func TestMarshaler_Marshal(t *testing.T) { Epoch: 1, Arch: "aarch64", Identifier: ftypes.PkgIdentifier{ + UID: "740219943F17B1DF", PURL: &packageurl.PackageURL{ Type: packageurl.TypeRPM, Namespace: "centos", @@ -427,6 +432,7 @@ func TestMarshaler_Marshal(t *testing.T) { Name: "actionpack", Version: "7.0.1", Identifier: ftypes.PkgIdentifier{ + UID: "E8DB2C6E35F8B990", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGem, Name: "actionpack", @@ -443,6 +449,7 @@ func TestMarshaler_Marshal(t *testing.T) { Name: "actionpack", Version: "7.0.1", Identifier: ftypes.PkgIdentifier{ + UID: "B3E70B2159CFAC50", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGem, Name: "actionpack", @@ -1063,6 +1070,7 @@ func TestMarshaler_Marshal(t *testing.T) { Name: "golang.org/x/crypto", Version: "v0.0.1", Identifier: ftypes.PkgIdentifier{ + UID: "161541A259EF014B", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, Namespace: "golang.org/x", From 55ccd06df43f6ff28685f46d215ccb70f55916d2 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 28 Jun 2024 13:42:02 +0400 Subject: [PATCH 202/352] feat: add memory cache backend (#7048) Signed-off-by: knqyf263 --- docs/docs/configuration/cache.md | 50 ++- .../configuration/cli/trivy_config.md | 2 +- .../configuration/cli/trivy_filesystem.md | 2 +- .../configuration/cli/trivy_image.md | 2 +- .../configuration/cli/trivy_kubernetes.md | 2 +- .../configuration/cli/trivy_repository.md | 2 +- .../configuration/cli/trivy_rootfs.md | 2 +- .../configuration/cli/trivy_sbom.md | 2 +- .../configuration/cli/trivy_server.md | 2 +- .../references/configuration/cli/trivy_vm.md | 2 +- pkg/cache/client.go | 8 + pkg/cache/memory.go | 98 +++++ pkg/cache/memory_test.go | 396 ++++++++++++++++++ pkg/commands/app.go | 45 +- pkg/fanal/types/package.go | 2 +- pkg/flag/cache_flags.go | 2 +- 16 files changed, 577 insertions(+), 42 deletions(-) create mode 100644 pkg/cache/memory.go create mode 100644 pkg/cache/memory_test.go diff --git a/docs/docs/configuration/cache.md b/docs/docs/configuration/cache.md index 719b0deced17..8817a2adb3ea 100644 --- a/docs/docs/configuration/cache.md +++ b/docs/docs/configuration/cache.md @@ -11,7 +11,7 @@ The cache option is common to all scanners. ## Clear Caches `trivy clean` subcommand removes caches. -``` +```bash $ trivy clean --scan-cache ``` @@ -31,31 +31,59 @@ See `trivy clean --help` for details. ## Cache Directory Specify where the cache is stored with `--cache-dir`. -``` +```bash $ trivy --cache-dir /tmp/trivy/ image python:3.4-alpine3.9 ``` -## Cache Backend +## Scan Cache Backend !!! warning "EXPERIMENTAL" This feature might change without preserving backwards compatibility. -Trivy supports local filesystem and Redis as the cache backend. This option is useful especially for client/server mode. - -Two options: +Trivy utilizes a scan cache to store analysis results, such as package lists. +It supports three types of backends for this cache: -- `fs` - - the cache path can be specified by `--cache-dir` -- `redis://` +- Local File System (`fs`) + - The cache path can be specified by `--cache-dir` +- Memory (`memory`) +- Redis (`redis://`) - `redis://[HOST]:[PORT]` - TTL can be configured via `--cache-ttl` +### Local File System +The local file system backend is the default choice for container and VM image scans. +When scanning container images, it stores analysis results on a per-layer basis, using layer IDs as keys. +This approach enables faster scans of the same container image or different images that share layers. + +!!! note + Internally, this backend uses [BoltDB][boltdb], which has an important limitation: only one process can access the cache at a time. + Subsequent processes attempting to access the cache will be locked. + For more details on this limitation, refer to the [troubleshooting guide][parallel-run]. + +### Memory +The memory backend stores analysis results in memory, which means the cache is discarded when the process ends. +This makes it useful in scenarios where caching is not required or desired. +It serves as the default for repository, filesystem and SBOM scans and can also be employed for container image scans when caching is unnecessary. + +To use the memory backend for a container image scan, you can use the following command: + +```bash +$ trivy image debian:11 --cache-backend memory ``` + +### Redis + +The Redis backend is particularly useful when you need to share the cache across multiple Trivy instances. +You can set up Trivy to use a Redis backend with a command like this: + +```bash $ trivy server --cache-backend redis://localhost:6379 ``` +This approach allows for centralized caching, which can be beneficial in distributed or high-concurrency environments. + If you want to use TLS with Redis, you can enable it by specifying the `--redis-tls` flag. -```shell +```bash $ trivy server --cache-backend redis://localhost:6379 --redis-tls ``` @@ -72,6 +100,8 @@ $ trivy server --cache-backend redis://localhost:6379 \ [trivy-db]: ./db.md#vulnerability-database [trivy-java-db]: ./db.md#java-index-database [misconf-checks]: ../scanner/misconfiguration/check/builtin.md +[boltdb]: https://github.com/etcd-io/bbolt +[parallel-run]: https://aquasecurity.github.io/trivy/v0.52/docs/references/troubleshooting/#running-in-parallel-takes-same-time-as-series-run [^1]: Downloaded when scanning for vulnerabilities [^2]: Downloaded when scanning `jar/war/par/ear` files diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index f10788347129..0176c09ea58f 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -9,7 +9,7 @@ trivy config [flags] DIR ### Options ``` - --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "memory") --cache-ttl duration cache TTL when using redis as cache backend --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index d024b2738d92..ae88ed8ca83b 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -19,7 +19,7 @@ trivy filesystem [flags] PATH ### Options ``` - --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "memory") --cache-ttl duration cache TTL when using redis as cache backend --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 1ef6e1e8cf3c..8c3fe309f929 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -34,7 +34,7 @@ trivy image [flags] IMAGE_NAME ### Options ``` - --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 91e9e5fa8e76..201eee466765 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -30,7 +30,7 @@ trivy kubernetes [flags] [CONTEXT] ``` --burst int specify the maximum burst for throttle (default 10) - --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 963727554494..cf85082bee7f 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -19,7 +19,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) ``` --branch string pass the branch name to be scanned - --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "memory") --cache-ttl duration cache TTL when using redis as cache backend --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index ed287f689928..6ab7705ff633 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -22,7 +22,7 @@ trivy rootfs [flags] ROOTDIR ### Options ``` - --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "memory") --cache-ttl duration cache TTL when using redis as cache backend --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index b5681576c830..2adbd12a253a 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -20,7 +20,7 @@ trivy sbom [flags] SBOM_PATH ### Options ``` - --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "memory") --cache-ttl duration cache TTL when using redis as cache backend --compliance string compliance report to generate --custom-headers strings custom headers in client mode diff --git a/docs/docs/references/configuration/cli/trivy_server.md b/docs/docs/references/configuration/cli/trivy_server.md index d8d711092e5e..4291496e34f1 100644 --- a/docs/docs/references/configuration/cli/trivy_server.md +++ b/docs/docs/references/configuration/cli/trivy_server.md @@ -20,7 +20,7 @@ trivy server [flags] ### Options ``` - --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --download-db-only download/update vulnerability database but don't run a scan diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 67be823ed6b6..5ad96c87b0df 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -21,7 +21,7 @@ trivy vm [flags] VM_IMAGE ``` --aws-region string AWS region to scan - --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") --compliance string compliance report to generate diff --git a/pkg/cache/client.go b/pkg/cache/client.go index 46bced1771aa..667900366e3e 100644 --- a/pkg/cache/client.go +++ b/pkg/cache/client.go @@ -5,12 +5,15 @@ import ( "time" "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" ) const ( TypeUnknown Type = "unknown" TypeFS Type = "fs" TypeRedis Type = "redis" + TypeMemory Type = "memory" ) type Type string @@ -33,6 +36,8 @@ func NewType(backend string) Type { return TypeRedis case backend == "fs", backend == "": return TypeFS + case backend == "memory": + return TypeMemory default: return TypeUnknown } @@ -44,6 +49,7 @@ func New(opts Options) (Cache, func(), error) { var cache Cache t := NewType(opts.Backend) + log.Debug("Initializing scan cache...", log.String("type", string(t))) switch t { case TypeRedis: redisCache, err := NewRedisCache(opts.Backend, opts.RedisCACert, opts.RedisCert, opts.RedisKey, opts.RedisTLS, opts.TTL) @@ -58,6 +64,8 @@ func New(opts Options) (Cache, func(), error) { return nil, cleanup, xerrors.Errorf("unable to initialize fs cache: %w", err) } cache = fsCache + case TypeMemory: + cache = NewMemoryCache() default: return nil, cleanup, xerrors.Errorf("unknown cache type: %s", t) } diff --git a/pkg/cache/memory.go b/pkg/cache/memory.go new file mode 100644 index 000000000000..485c6ff4624f --- /dev/null +++ b/pkg/cache/memory.go @@ -0,0 +1,98 @@ +package cache + +import ( + "sync" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +var _ Cache = &MemoryCache{} + +type MemoryCache struct { + artifacts sync.Map // Map to store artifact information + blobs sync.Map // Map to store blob information +} + +func NewMemoryCache() *MemoryCache { + return &MemoryCache{} +} + +// PutArtifact stores the artifact information in the memory cache +func (c *MemoryCache) PutArtifact(artifactID string, artifactInfo types.ArtifactInfo) error { + c.artifacts.Store(artifactID, artifactInfo) + return nil +} + +// PutBlob stores the blob information in the memory cache +func (c *MemoryCache) PutBlob(blobID string, blobInfo types.BlobInfo) error { + c.blobs.Store(blobID, blobInfo) + return nil +} + +// DeleteBlobs removes the specified blobs from the memory cache +func (c *MemoryCache) DeleteBlobs(blobIDs []string) error { + for _, blobID := range blobIDs { + c.blobs.Delete(blobID) + } + return nil +} + +// GetArtifact retrieves the artifact information from the memory cache +func (c *MemoryCache) GetArtifact(artifactID string) (types.ArtifactInfo, error) { + info, ok := c.artifacts.Load(artifactID) + if !ok { + return types.ArtifactInfo{}, xerrors.Errorf("artifact (%s) not found in memory cache", artifactID) + } + artifactInfo, ok := info.(types.ArtifactInfo) + if !ok { + return types.ArtifactInfo{}, xerrors.Errorf("invalid type for artifact (%s) in memory cache", artifactID) + } + return artifactInfo, nil +} + +// GetBlob retrieves the blob information from the memory cache +func (c *MemoryCache) GetBlob(blobID string) (types.BlobInfo, error) { + info, ok := c.blobs.Load(blobID) + if !ok { + return types.BlobInfo{}, xerrors.Errorf("blob (%s) not found in memory cache", blobID) + } + blobInfo, ok := info.(types.BlobInfo) + if !ok { + return types.BlobInfo{}, xerrors.Errorf("invalid type for blob (%s) in memory cache", blobID) + } + return blobInfo, nil +} + +// MissingBlobs determines the missing artifact and blob information in the memory cache +func (c *MemoryCache) MissingBlobs(artifactID string, blobIDs []string) (bool, []string, error) { + var missingArtifact bool + var missingBlobIDs []string + + if _, err := c.GetArtifact(artifactID); err != nil { + missingArtifact = true + } + + for _, blobID := range blobIDs { + if _, err := c.GetBlob(blobID); err != nil { + missingBlobIDs = append(missingBlobIDs, blobID) + } + } + + return missingArtifact, missingBlobIDs, nil +} + +// Close clears the artifact and blob information from the memory cache +func (c *MemoryCache) Close() error { + c.artifacts = sync.Map{} + c.blobs = sync.Map{} + return nil +} + +// Clear clears the artifact and blob information from the memory cache +func (c *MemoryCache) Clear() error { + c.artifacts = sync.Map{} + c.blobs = sync.Map{} + return nil +} diff --git a/pkg/cache/memory_test.go b/pkg/cache/memory_test.go new file mode 100644 index 000000000000..3d88b565c7f9 --- /dev/null +++ b/pkg/cache/memory_test.go @@ -0,0 +1,396 @@ +package cache_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/cache" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func TestMemoryCache_PutArtifact(t *testing.T) { + tests := []struct { + name string + artifactID string + artifactInfo types.ArtifactInfo + }{ + { + name: "happy path", + artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf", + artifactInfo: types.ArtifactInfo{ + SchemaVersion: 2, + Architecture: "amd64", + Created: time.Date(2020, 11, 14, 0, 20, 4, 0, time.UTC), + DockerVersion: "19.03.12", + OS: "linux", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := cache.NewMemoryCache() + + err := c.PutArtifact(tt.artifactID, tt.artifactInfo) + require.NoError(t, err) + + got, err := c.GetArtifact(tt.artifactID) + require.NoError(t, err) + assert.Equal(t, tt.artifactInfo, got) + }) + } +} + +func TestMemoryCache_PutBlob(t *testing.T) { + tests := []struct { + name string + blobID string + blobInfo types.BlobInfo + }{ + { + name: "happy path", + blobID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + blobInfo: types.BlobInfo{ + SchemaVersion: 2, + Digest: "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + DiffID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + OS: types.OS{ + Family: "alpine", + Name: "3.10.2", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "lib/apk/db/installed", + Packages: []types.Package{ + { + Name: "musl", + Version: "1.1.22-r3", + SrcName: "musl", + SrcVersion: "1.1.22-r3", + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := cache.NewMemoryCache() + + err := c.PutBlob(tt.blobID, tt.blobInfo) + require.NoError(t, err) + + got, err := c.GetBlob(tt.blobID) + require.NoError(t, err) + assert.Equal(t, tt.blobInfo, got) + }) + } +} + +func TestMemoryCache_GetArtifact(t *testing.T) { + tests := []struct { + name string + artifactID string + artifactInfo types.ArtifactInfo + wantErr bool + }{ + { + name: "happy path", + artifactID: "sha256:8652b9f0cb4c0599575e5a003f5906876e10c1ceb2ab9fe1786712dac14a50cf", + artifactInfo: types.ArtifactInfo{ + SchemaVersion: 2, + Architecture: "amd64", + Created: time.Date(2020, 11, 14, 0, 20, 4, 0, time.UTC), + DockerVersion: "19.03.12", + OS: "linux", + }, + wantErr: false, + }, + { + name: "not found", + artifactID: "sha256:nonexistent", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := cache.NewMemoryCache() + + if !tt.wantErr { + err := c.PutArtifact(tt.artifactID, tt.artifactInfo) + require.NoError(t, err) + } + + got, err := c.GetArtifact(tt.artifactID) + if tt.wantErr { + require.ErrorContains(t, err, "not found in memory cache") + return + } + require.NoError(t, err) + assert.Equal(t, tt.artifactInfo, got) + }) + } +} + +func TestMemoryCache_GetBlob(t *testing.T) { + tests := []struct { + name string + blobID string + blobInfo types.BlobInfo + wantErr bool + }{ + { + name: "happy path", + blobID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + blobInfo: types.BlobInfo{ + SchemaVersion: 2, + Digest: "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", + DiffID: "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", + OS: types.OS{ + Family: "alpine", + Name: "3.10.2", + }, + }, + wantErr: false, + }, + { + name: "not found", + blobID: "sha256:nonexistent", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := cache.NewMemoryCache() + + if !tt.wantErr { + err := c.PutBlob(tt.blobID, tt.blobInfo) + require.NoError(t, err) + } + + got, err := c.GetBlob(tt.blobID) + if tt.wantErr { + require.ErrorContains(t, err, "not found in memory cache") + return + } + require.NoError(t, err) + assert.Equal(t, tt.blobInfo, got) + }) + } +} + +func TestMemoryCache_MissingBlobs(t *testing.T) { + tests := []struct { + name string + artifactID string + blobIDs []string + putArtifact bool + putBlobs []string + wantMissingArtifact bool + wantMissingBlobIDs []string + }{ + { + name: "missing both artifact and blob", + artifactID: "sha256:artifact1", + blobIDs: []string{ + "sha256:blob1", + "sha256:blob2", + }, + putArtifact: false, + putBlobs: []string{}, + wantMissingArtifact: true, + wantMissingBlobIDs: []string{ + "sha256:blob1", + "sha256:blob2", + }, + }, + { + name: "missing artifact only", + artifactID: "sha256:artifact1", + blobIDs: []string{ + "sha256:blob1", + "sha256:blob2", + }, + putArtifact: false, + putBlobs: []string{ + "sha256:blob1", + "sha256:blob2", + }, + wantMissingArtifact: true, + wantMissingBlobIDs: nil, + }, + { + name: "missing one blob", + artifactID: "sha256:artifact1", + blobIDs: []string{ + "sha256:blob1", + "sha256:blob2", + }, + putArtifact: true, + putBlobs: []string{"sha256:blob1"}, + wantMissingArtifact: false, + wantMissingBlobIDs: []string{"sha256:blob2"}, + }, + { + name: "no missing blobs", + artifactID: "sha256:artifact1", + blobIDs: []string{ + "sha256:blob1", + "sha256:blob2", + }, + putArtifact: true, + putBlobs: []string{ + "sha256:blob1", + "sha256:blob2", + }, + wantMissingArtifact: false, + wantMissingBlobIDs: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := cache.NewMemoryCache() + + if tt.putArtifact { + err := c.PutArtifact(tt.artifactID, types.ArtifactInfo{}) + require.NoError(t, err) + } + + for _, blobID := range tt.putBlobs { + err := c.PutBlob(blobID, types.BlobInfo{}) + require.NoError(t, err) + } + + gotMissingArtifact, gotMissingBlobIDs, err := c.MissingBlobs(tt.artifactID, tt.blobIDs) + require.NoError(t, err) + assert.Equal(t, tt.wantMissingArtifact, gotMissingArtifact) + assert.Equal(t, tt.wantMissingBlobIDs, gotMissingBlobIDs) + }) + } +} + +func TestMemoryCache_DeleteBlobs(t *testing.T) { + tests := []struct { + name string + blobIDs []string + }{ + { + name: "delete existing blobs", + blobIDs: []string{ + "sha256:blob1", + "sha256:blob2", + }, + }, + { + name: "delete non-existing blobs", + blobIDs: []string{ + "sha256:nonexistent1", + "sha256:nonexistent2", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := cache.NewMemoryCache() + + // Put some blobs in the cache + for _, blobID := range tt.blobIDs { + err := c.PutBlob(blobID, types.BlobInfo{}) + require.NoError(t, err) + } + + err := c.DeleteBlobs(tt.blobIDs) + require.NoError(t, err) + + // Check that the blobs are no longer in the cache + for _, blobID := range tt.blobIDs { + _, err := c.GetBlob(blobID) + require.Error(t, err) + assert.Contains(t, err.Error(), "not found in memory cache") + } + }) + } +} + +func TestMemoryCache_Clear(t *testing.T) { + tests := []struct { + name string + artifactID string + blobID string + }{ + { + name: "clear cache", + artifactID: "sha256:artifact1", + blobID: "sha256:blob1", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := cache.NewMemoryCache() + + err := c.PutArtifact(tt.artifactID, types.ArtifactInfo{}) + require.NoError(t, err) + + err = c.PutBlob(tt.blobID, types.BlobInfo{}) + require.NoError(t, err) + + err = c.Clear() + require.NoError(t, err) + + _, err = c.GetArtifact(tt.artifactID) + require.Error(t, err) + assert.Contains(t, err.Error(), "not found in memory cache") + + _, err = c.GetBlob(tt.blobID) + require.Error(t, err) + assert.Contains(t, err.Error(), "not found in memory cache") + }) + } +} + +func TestMemoryCache_Close(t *testing.T) { + tests := []struct { + name string + artifactID string + blobID string + }{ + { + name: "close cache", + artifactID: "sha256:artifact1", + blobID: "sha256:blob1", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := cache.NewMemoryCache() + + err := c.PutArtifact(tt.artifactID, types.ArtifactInfo{}) + require.NoError(t, err) + + err = c.PutBlob(tt.blobID, types.BlobInfo{}) + require.NoError(t, err) + + err = c.Close() + require.NoError(t, err) + + _, err = c.GetArtifact(tt.artifactID) + require.Error(t, err) + assert.Contains(t, err.Error(), "not found in memory cache") + + _, err = c.GetBlob(tt.blobID) + require.Error(t, err) + assert.Contains(t, err.Error(), "not found in memory cache") + }) + } +} diff --git a/pkg/commands/app.go b/pkg/commands/app.go index c130deb64876..3f88b9e9b37e 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -13,6 +13,7 @@ import ( "github.com/spf13/viper" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/commands/artifact" "github.com/aquasecurity/trivy/pkg/commands/clean" "github.com/aquasecurity/trivy/pkg/commands/convert" @@ -330,12 +331,6 @@ func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { - reportFlagGroup := flag.NewReportFlagGroup() - reportFormat := flag.ReportFormatFlag.Clone() - reportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports - reportFlagGroup.ReportFormat = reportFormat - reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' - fsFlags := &flag.Flags{ GlobalFlagGroup: globalFlags, CacheFlagGroup: flag.NewCacheFlagGroup(), @@ -346,12 +341,16 @@ func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode RegistryFlagGroup: flag.NewRegistryFlagGroup(), RegoFlagGroup: flag.NewRegoFlagGroup(), - ReportFlagGroup: reportFlagGroup, + ReportFlagGroup: flag.NewReportFlagGroup(), ScanFlagGroup: flag.NewScanFlagGroup(), SecretFlagGroup: flag.NewSecretFlagGroup(), VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), } + fsFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default + fsFlags.ReportFlagGroup.ReportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports + fsFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + cmd := &cobra.Command{ Use: "filesystem [flags] PATH", Aliases: []string{"fs"}, @@ -405,10 +404,11 @@ func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { SecretFlagGroup: flag.NewSecretFlagGroup(), VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), } - rootfsFlags.ReportFlagGroup.ReportFormat = nil // TODO: support --report summary - rootfsFlags.ReportFlagGroup.Compliance = nil // disable '--compliance' - rootfsFlags.ReportFlagGroup.ReportFormat = nil // disable '--report' - rootfsFlags.ScanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + rootfsFlags.ReportFlagGroup.ReportFormat = nil // TODO: support --report summary + rootfsFlags.ReportFlagGroup.Compliance = nil // disable '--compliance' + rootfsFlags.ReportFlagGroup.ReportFormat = nil // disable '--report' + rootfsFlags.ScanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + rootfsFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default cmd := &cobra.Command{ Use: "rootfs [flags] ROOTDIR", @@ -469,6 +469,8 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { repoFlags.ReportFlagGroup.Compliance = nil // disable '--compliance' repoFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + repoFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default + cmd := &cobra.Command{ Use: "repository [flags] (REPO_PATH | REPO_URL)", Aliases: []string{"repo"}, @@ -651,15 +653,6 @@ func NewServerCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { - reportFlagGroup := flag.NewReportFlagGroup() - reportFlagGroup.DependencyTree = nil // disable '--dependency-tree' - reportFlagGroup.ListAllPkgs = nil // disable '--list-all-pkgs' - reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' - reportFlagGroup.ShowSuppressed = nil // disable '--show-suppressed' - reportFormat := flag.ReportFormatFlag.Clone() - reportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports - reportFlagGroup.ReportFormat = reportFormat - scanFlags := &flag.ScanFlagGroup{ // Enable only '--skip-dirs' and '--skip-files' and disable other flags SkipDirs: flag.SkipDirsFlag.Clone(), @@ -678,10 +671,17 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { // disable unneeded flags K8sVersion: flag.K8sVersionFlag.Clone(), }, - ReportFlagGroup: reportFlagGroup, + ReportFlagGroup: flag.NewReportFlagGroup(), ScanFlagGroup: scanFlags, } + configFlags.ReportFlagGroup.DependencyTree = nil // disable '--dependency-tree' + configFlags.ReportFlagGroup.ListAllPkgs = nil // disable '--list-all-pkgs' + configFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + configFlags.ReportFlagGroup.ShowSuppressed = nil // disable '--show-suppressed' + configFlags.ReportFlagGroup.ReportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports + configFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) + cmd := &cobra.Command{ Use: "config [flags] DIR", Aliases: []string{"conf"}, @@ -1142,6 +1142,8 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { LicenseFlagGroup: licenseFlagGroup, } + sbomFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default + cmd := &cobra.Command{ Use: "sbom [flags] SBOM_PATH", Short: "Scan SBOM for vulnerabilities and licenses", @@ -1220,6 +1222,7 @@ func NewCleanCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return cmd } + func NewVersionCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { var versionFormat string cmd := &cobra.Command{ diff --git a/pkg/fanal/types/package.go b/pkg/fanal/types/package.go index 0a281326b35d..a0734651355d 100644 --- a/pkg/fanal/types/package.go +++ b/pkg/fanal/types/package.go @@ -170,7 +170,7 @@ type Package struct { SrcEpoch int `json:",omitempty"` Licenses []string `json:",omitempty"` Maintainer string `json:",omitempty"` - ExternalReferences []ExternalRef `json:"-"` + ExternalReferences []ExternalRef `json:"-" hash:"ignore"` Modularitylabel string `json:",omitempty"` // only for Red Hat based distributions BuildInfo *BuildInfo `json:",omitempty"` // only for Red Hat diff --git a/pkg/flag/cache_flags.go b/pkg/flag/cache_flags.go index 786a0c9c7ffe..074953c2ea44 100644 --- a/pkg/flag/cache_flags.go +++ b/pkg/flag/cache_flags.go @@ -25,7 +25,7 @@ var ( Name: "cache-backend", ConfigName: "cache.backend", Default: "fs", - Usage: "cache backend (e.g. redis://localhost:6379)", + Usage: "[EXPERIMENTAL] cache backend (e.g. redis://localhost:6379)", } CacheTTLFlag = Flag[time.Duration]{ Name: "cache-ttl", From 3d4ae8b5be94cd9b00badeece8d86c2258b2cd90 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:45:06 +0600 Subject: [PATCH 203/352] fix(sbom): fix panic when scanning SBOM file without root component into SBOM format (#7051) --- pkg/sbom/io/encode.go | 3 ++- pkg/sbom/io/encode_test.go | 55 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index 64a24dbdcb07..096abd026b86 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -85,7 +85,8 @@ func (e *Encoder) rootComponent(r types.Report) (*core.Component, error) { root.Type = core.TypeRepository case artifact.TypeCycloneDX, artifact.TypeSPDX: // When we scan SBOM file - if r.BOM != nil { + // If SBOM file doesn't contain root component - use filesystem + if r.BOM != nil && r.BOM.Root() != nil { return r.BOM.Root(), nil } // When we scan a `json` file (meaning a file in `json` format) which was created from the SBOM file. diff --git a/pkg/sbom/io/encode_test.go b/pkg/sbom/io/encode_test.go index 06109c963d4f..80783827cee7 100644 --- a/pkg/sbom/io/encode_test.go +++ b/pkg/sbom/io/encode_test.go @@ -705,6 +705,53 @@ func TestEncoder_Encode(t *testing.T) { }, wantVulns: make(map[uuid.UUID][]core.Vulnerability), }, + { + name: "SBOM file without root component", + report: types.Report{ + SchemaVersion: 2, + ArtifactName: "report.cdx.json", + ArtifactType: artifact.TypeCycloneDX, + Results: []types.Result{ + { + Target: "Java", + Type: ftypes.Jar, + Class: types.ClassLangPkg, + Packages: []ftypes.Package{ + { + ID: "org.apache.logging.log4j:log4j-core:2.23.1", + Name: "org.apache.logging.log4j:log4j-core", + Version: "2.23.1", + Identifier: ftypes.PkgIdentifier{ + UID: "6C0AE96901617503", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.logging.log4j", + Name: "log4j-core", + Version: "2.23.1", + }, + }, + FilePath: "log4j-core-2.23.1.jar", + }, + }, + }, + }, + BOM: newTestBOM2(t), + }, + wantComponents: map[uuid.UUID]*core.Component{ + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): fsComponent, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): libComponent, + }, + wantRels: map[uuid.UUID][]core.Relationship{ + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"), + Type: core.RelationshipContains, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): nil, + }, + wantVulns: make(map[uuid.UUID][]core.Vulnerability), + }, { name: "json file created from SBOM file (BOM is empty)", report: types.Report{ @@ -860,3 +907,11 @@ func newTestBOM(t *testing.T) *core.BOM { bom.AddComponent(appComponent) return bom } + +// BOM without root component +func newTestBOM2(t *testing.T) *core.BOM { + uuid.SetFakeUUID(t, "2ff14136-e09f-4df9-80ea-%012d") + bom := core.NewBOM(core.Options{}) + bom.AddComponent(libComponent) + return bom +} From 654217a65485ca0a07771ea61071977894eb4920 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:21:38 +0600 Subject: [PATCH 204/352] feat(conda): add licenses support for `environment.yml` files (#6953) Co-authored-by: Teppei Fukuda --- docs/docs/coverage/os/conda.md | 35 +- .../parser/conda/environment/parse.go | 15 +- .../parser/conda/environment/parse_test.go | 333 +++++++++--------- .../language/conda/environment/environment.go | 82 ++++- .../conda/environment/environment_test.go | 63 ++++ .../conda-meta/_libgcc_mutex-0.1-main.json | 5 + .../conda-meta/_openmp_mutex-5.1-1_gnu.json | 5 + .../conda-meta/blas-1.0-openblas.json | 5 + .../conda-meta/bzip2-1.0.8-h5eee18b_6.json | 5 + .../conda/environment/testdata/empty.yaml | 5 + .../testdata/environment-with-licenses.yaml | 9 + 11 files changed, 378 insertions(+), 184 deletions(-) create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_libgcc_mutex-0.1-main.json create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_openmp_mutex-5.1-1_gnu.json create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/blas-1.0-openblas.json create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/bzip2-1.0.8-h5eee18b_6.json create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/empty.yaml create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/environment-with-licenses.yaml diff --git a/docs/docs/coverage/os/conda.md b/docs/docs/coverage/os/conda.md index 79a49194fd66..10c1e93c1b8c 100644 --- a/docs/docs/coverage/os/conda.md +++ b/docs/docs/coverage/os/conda.md @@ -6,31 +6,38 @@ Trivy supports the following scanners for Conda packages. |:-------------:|:---------:| | SBOM | ✓ | | Vulnerability | - | -| License | ✓[^1] | +| License | ✓ | -## SBOM -Trivy detects packages that have been installed with `Conda`. +## `.json` +### SBOM +Trivy parses `/envs//conda-meta/.json` files to find the dependencies installed in your env. -### `.json` -Trivy parses `/envs//conda-meta/.json` files to find the version and license for the dependencies installed in your env. +### License +The `.json` files contain package license information. +Trivy includes licenses for the packages it finds without having to parse additional files. -### `environment.yml`[^2] -Trivy supports parsing [environment.yml][environment.yml][^2] files to find dependency list. +## `environment.yml`[^1] +### SBOM +Trivy supports parsing [environment.yml][environment.yml][^1] files to find dependency list. -!!! note - License detection is currently not supported. - -`environment.yml`[^2] files supports [version range][env-version-range]. We can't be sure about versions for these dependencies. -Therefore, you need to use `conda env export` command to get dependency list in `Conda` default format before scanning `environment.yml`[^2] file. +`environment.yml`[^1] files supports [version range][env-version-range]. We can't be sure about versions for these dependencies. +Therefore, you need to use `conda env export` command to get dependency list in `Conda` default format before scanning `environment.yml`[^1] file. !!! note For dependencies in a non-Conda format, Trivy doesn't include a version of them. +### License +Trivy parses `conda-meta/.json` files at the [prefix] path. + +To correctly define licenses, make sure your `environment.yml`[^1] contains `prefix` field and `prefix` directory contains `package.json` files. + +!!! note + To get correct `environment.yml`[^1] file and fill `prefix` directory - use `conda env export` command. -[^1]: License detection is only supported for `.json` files -[^2]: Trivy supports both `yaml` and `yml` extensions. +[^1]: Trivy supports both `yaml` and `yml` extensions. [environment.yml]: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#sharing-an-environment [env-version-range]: https://docs.conda.io/projects/conda-build/en/latest/resources/package-spec.html#examples-of-package-specs +[prefix]: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#specifying-a-location-for-an-environment diff --git a/pkg/dependency/parser/conda/environment/parse.go b/pkg/dependency/parser/conda/environment/parse.go index be04d828b40a..3aaccbc1d34f 100644 --- a/pkg/dependency/parser/conda/environment/parse.go +++ b/pkg/dependency/parser/conda/environment/parse.go @@ -16,6 +16,7 @@ import ( type environment struct { Entries []Entry `yaml:"dependencies"` + Prefix string `yaml:"prefix"` } type Entry struct { @@ -27,6 +28,11 @@ type Dependency struct { Line int } +type Packages struct { + Packages ftypes.Packages + Prefix string +} + type Parser struct { logger *log.Logger once sync.Once @@ -39,10 +45,10 @@ func NewParser() *Parser { } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) (Packages, error) { var env environment if err := yaml.NewDecoder(r).Decode(&env); err != nil { - return nil, nil, xerrors.Errorf("unable to decode conda environment.yml file: %w", err) + return Packages{}, xerrors.Errorf("unable to decode conda environment.yml file: %w", err) } var pkgs ftypes.Packages @@ -58,7 +64,10 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc } sort.Sort(pkgs) - return pkgs, nil, nil + return Packages{ + Packages: pkgs, + Prefix: env.Prefix, + }, nil } func (p *Parser) toPackage(dep Dependency) ftypes.Package { diff --git a/pkg/dependency/parser/conda/environment/parse_test.go b/pkg/dependency/parser/conda/environment/parse_test.go index 6283e8e135d6..9323f65c6ee4 100644 --- a/pkg/dependency/parser/conda/environment/parse_test.go +++ b/pkg/dependency/parser/conda/environment/parse_test.go @@ -15,175 +15,178 @@ func TestParse(t *testing.T) { tests := []struct { name string input string - want []ftypes.Package + want environment.Packages wantErr string }{ { name: "happy path", input: "testdata/happy.yaml", - want: []ftypes.Package{ - { - Name: "_openmp_mutex", - Locations: ftypes.Locations{ - { - StartLine: 6, - EndLine: 6, - }, - }, - }, - { - Name: "asgiref", - Version: "3.8.1", - Locations: ftypes.Locations{ - { - StartLine: 21, - EndLine: 21, - }, - }, - }, - { - Name: "blas", - Version: "1.0", - Locations: ftypes.Locations{ - { - StartLine: 5, - EndLine: 5, - }, - }, - }, - { - Name: "bzip2", - Version: "1.0.8", - Locations: ftypes.Locations{ - { - StartLine: 19, - EndLine: 19, - }, - }, - }, - { - Name: "ca-certificates", - Version: "2024.2", - Locations: ftypes.Locations{ - { - StartLine: 7, - EndLine: 7, - }, - }, - }, - { - Name: "django", - Version: "5.0.6", - Locations: ftypes.Locations{ - { - StartLine: 22, - EndLine: 22, - }, - }, - }, - { - Name: "ld_impl_linux-aarch64", - Locations: ftypes.Locations{ - { - StartLine: 8, - EndLine: 8, - }, - }, - }, - { - Name: "libblas", - Locations: ftypes.Locations{ - { - StartLine: 9, - EndLine: 9, - }, - }, - }, - { - Name: "libcblas", - Locations: ftypes.Locations{ - { - StartLine: 10, - EndLine: 10, - }, - }, - }, - { - Name: "libexpat", - Version: "2.6.2", - Locations: ftypes.Locations{ - { - StartLine: 11, - EndLine: 11, - }, - }, - }, - { - Name: "libffi", - Version: "3.4.2", - Locations: ftypes.Locations{ - { - StartLine: 12, - EndLine: 12, - }, - }, - }, - { - Name: "libgcc-ng", - Locations: ftypes.Locations{ - { - StartLine: 13, - EndLine: 13, - }, - }, - }, - { - Name: "libgfortran-ng", - Locations: ftypes.Locations{ - { - StartLine: 14, - EndLine: 14, - }, - }, - }, - { - Name: "libgfortran5", - Locations: ftypes.Locations{ - { - StartLine: 15, - EndLine: 15, - }, - }, - }, - { - Name: "libgomp", - Version: "13.2.0", - Locations: ftypes.Locations{ - { - StartLine: 16, - EndLine: 16, - }, - }, - }, - { - Name: "liblapack", - Locations: ftypes.Locations{ - { - StartLine: 17, - EndLine: 17, - }, - }, - }, - { - Name: "libnsl", - Version: "2.0.1", - Locations: ftypes.Locations{ - { - StartLine: 18, - EndLine: 18, - }, - }, - }, + want: environment.Packages{ + Packages: []ftypes.Package{ + { + Name: "_openmp_mutex", + Locations: ftypes.Locations{ + { + StartLine: 6, + EndLine: 6, + }, + }, + }, + { + Name: "asgiref", + Version: "3.8.1", + Locations: ftypes.Locations{ + { + StartLine: 21, + EndLine: 21, + }, + }, + }, + { + Name: "blas", + Version: "1.0", + Locations: ftypes.Locations{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + { + Name: "bzip2", + Version: "1.0.8", + Locations: ftypes.Locations{ + { + StartLine: 19, + EndLine: 19, + }, + }, + }, + { + Name: "ca-certificates", + Version: "2024.2", + Locations: ftypes.Locations{ + { + StartLine: 7, + EndLine: 7, + }, + }, + }, + { + Name: "django", + Version: "5.0.6", + Locations: ftypes.Locations{ + { + StartLine: 22, + EndLine: 22, + }, + }, + }, + { + Name: "ld_impl_linux-aarch64", + Locations: ftypes.Locations{ + { + StartLine: 8, + EndLine: 8, + }, + }, + }, + { + Name: "libblas", + Locations: ftypes.Locations{ + { + StartLine: 9, + EndLine: 9, + }, + }, + }, + { + Name: "libcblas", + Locations: ftypes.Locations{ + { + StartLine: 10, + EndLine: 10, + }, + }, + }, + { + Name: "libexpat", + Version: "2.6.2", + Locations: ftypes.Locations{ + { + StartLine: 11, + EndLine: 11, + }, + }, + }, + { + Name: "libffi", + Version: "3.4.2", + Locations: ftypes.Locations{ + { + StartLine: 12, + EndLine: 12, + }, + }, + }, + { + Name: "libgcc-ng", + Locations: ftypes.Locations{ + { + StartLine: 13, + EndLine: 13, + }, + }, + }, + { + Name: "libgfortran-ng", + Locations: ftypes.Locations{ + { + StartLine: 14, + EndLine: 14, + }, + }, + }, + { + Name: "libgfortran5", + Locations: ftypes.Locations{ + { + StartLine: 15, + EndLine: 15, + }, + }, + }, + { + Name: "libgomp", + Version: "13.2.0", + Locations: ftypes.Locations{ + { + StartLine: 16, + EndLine: 16, + }, + }, + }, + { + Name: "liblapack", + Locations: ftypes.Locations{ + { + StartLine: 17, + EndLine: 17, + }, + }, + }, + { + Name: "libnsl", + Version: "2.0.1", + Locations: ftypes.Locations{ + { + StartLine: 18, + EndLine: 18, + }, + }, + }, + }, + Prefix: "/opt/conda/envs/test-env", }, }, { @@ -213,7 +216,7 @@ func TestParse(t *testing.T) { require.NoError(t, err) defer f.Close() - got, _, err := environment.NewParser().Parse(f) + got, err := environment.NewParser().Parse(f) if tt.wantErr != "" { assert.ErrorContains(t, err, tt.wantErr) diff --git a/pkg/fanal/analyzer/language/conda/environment/environment.go b/pkg/fanal/analyzer/language/conda/environment/environment.go index ee4dfbd7de88..305ac4821855 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment.go @@ -2,32 +2,110 @@ package environment import ( "context" + "fmt" "os" "path/filepath" + "sync" + "github.com/bmatcuk/doublestar/v4" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/conda/environment" + "github.com/aquasecurity/trivy/pkg/dependency/parser/conda/meta" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/version/doc" + xio "github.com/aquasecurity/trivy/pkg/x/io" ) func init() { analyzer.RegisterAnalyzer(&environmentAnalyzer{}) } -const version = 1 +const version = 2 + +type parser struct{} + +func (*parser) Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, error) { + p := environment.NewParser() + pkgs, err := p.Parse(r) + if err != nil { + return nil, nil, err + } + + once := sync.Once{} + for i, pkg := range pkgs.Packages { + // Skip packages without a version, because in this case we will not be able to get the correct file name. + if pkg.Version != "" { + licenses, err := findLicenseFromEnvDir(pkg, pkgs.Prefix) + if err != nil { + // Show log once per file + once.Do(func() { + // e.g. https://aquasecurity.github.io/trivy/latest/docs/coverage/os/conda/#license_1 + log.WithPrefix("conda").Debug(fmt.Sprintf("License not found. See %s for details.", doc.URL("docs/coverage/os/conda/", "license_1")), + log.String("pkg", pkg.Name), log.Err(err)) + }) + } + pkg.Licenses = licenses + } + pkgs.Packages[i] = pkg + } + + return pkgs.Packages, nil, nil +} type environmentAnalyzer struct{} func (a environmentAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { - res, err := language.Analyze(types.CondaEnv, input.FilePath, input.Content, environment.NewParser()) + res, err := language.Analyze(types.CondaEnv, input.FilePath, input.Content, &parser{}) if err != nil { return nil, xerrors.Errorf("unable to parse environment.yaml: %w", err) } + + if res == nil { + return nil, nil + } return res, nil } + +func findLicenseFromEnvDir(pkg types.Package, prefix string) ([]string, error) { + if prefix == "" { + return nil, xerrors.Errorf("`prefix` field doesn't exist") + } + condaMetaDir := filepath.Join(prefix, "conda-meta") + entries, err := os.ReadDir(condaMetaDir) + if err != nil { + return nil, xerrors.Errorf("unable to read conda-meta dir: %w", err) + } + for _, entry := range entries { + if entry.IsDir() { + continue + } + + pattern := fmt.Sprintf("%s-%s-*.json", pkg.Name, pkg.Version) + matched, err := doublestar.Match(pattern, entry.Name()) + if err != nil { + return nil, xerrors.Errorf("incorrect packageJSON file pattern: %w", err) + } + if matched { + file, err := os.Open(filepath.Join(condaMetaDir, entry.Name())) + if err != nil { + return nil, xerrors.Errorf("unable to open packageJSON file: %w", err) + } + packageJson, _, err := meta.NewParser().Parse(file) + if err != nil { + return nil, xerrors.Errorf("unable to parse packageJSON file: %w", err) + } + // packageJson always contain only 1 element + // cf. https://github.com/aquasecurity/trivy/blob/c3192f061d7e84eaf38df8df7c879dc00b4ca137/pkg/dependency/parser/conda/meta/parse.go#L39-L45 + return packageJson[0].Licenses, nil + } + } + return nil, xerrors.Errorf("meta file didn't find") +} + func (a environmentAnalyzer) Required(filePath string, _ os.FileInfo) bool { return filepath.Base(filePath) == types.CondaEnvYml || filepath.Base(filePath) == types.CondaEnvYaml } diff --git a/pkg/fanal/analyzer/language/conda/environment/environment_test.go b/pkg/fanal/analyzer/language/conda/environment/environment_test.go index 044585f14683..02a188a87f2a 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment_test.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment_test.go @@ -72,6 +72,69 @@ func Test_environmentAnalyzer_Analyze(t *testing.T) { }, }, }, + { + name: "happy path with licenses", + inputFile: "testdata/environment-with-licenses.yaml", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.CondaEnv, + FilePath: "testdata/environment-with-licenses.yaml", + Packages: types.Packages{ + { + Name: "_libgcc_mutex", + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + { + Name: "_openmp_mutex", + Version: "5.1", + Locations: []types.Location{ + { + StartLine: 6, + EndLine: 6, + }, + }, + Licenses: []string{ + "BSD-3-Clause", + }, + }, + { + Name: "blas", + Version: "1.0", + Locations: []types.Location{ + { + StartLine: 7, + EndLine: 7, + }, + }, + }, + { + Name: "bzip2", + Version: "1.0.8", + Locations: []types.Location{ + { + StartLine: 8, + EndLine: 8, + }, + }, + Licenses: []string{ + "bzip2-1.0.8", + }, + }, + }, + }, + }, + }, + }, + { + name: "empty", + inputFile: "testdata/empty.yaml", + }, { name: "invalid", inputFile: "testdata/invalid.yaml", diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_libgcc_mutex-0.1-main.json b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_libgcc_mutex-0.1-main.json new file mode 100644 index 000000000000..bfc2daa841d5 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_libgcc_mutex-0.1-main.json @@ -0,0 +1,5 @@ +{ + "license": "MIT", + "name": "_libgcc_mutex", + "version": "0.1" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_openmp_mutex-5.1-1_gnu.json b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_openmp_mutex-5.1-1_gnu.json new file mode 100644 index 000000000000..a61f193ee669 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_openmp_mutex-5.1-1_gnu.json @@ -0,0 +1,5 @@ +{ + "license": "BSD-3-Clause", + "name": "_openmp_mutex", + "version": "5.1" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/blas-1.0-openblas.json b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/blas-1.0-openblas.json new file mode 100644 index 000000000000..9a14025cd5b2 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/blas-1.0-openblas.json @@ -0,0 +1,5 @@ +{ + "license": "", + "name": "blas", + "version": "1.0" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/bzip2-1.0.8-h5eee18b_6.json b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/bzip2-1.0.8-h5eee18b_6.json new file mode 100644 index 000000000000..413243843b73 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/bzip2-1.0.8-h5eee18b_6.json @@ -0,0 +1,5 @@ +{ + "license": "bzip2-1.0.8", + "name": "bzip2", + "version": "1.0.8" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/empty.yaml b/pkg/fanal/analyzer/language/conda/environment/testdata/empty.yaml new file mode 100644 index 000000000000..f9622b8a1030 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/empty.yaml @@ -0,0 +1,5 @@ +name: test-env +channels: + - defaults +dependencies: +prefix: /opt/conda/envs/test-env diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/environment-with-licenses.yaml b/pkg/fanal/analyzer/language/conda/environment/testdata/environment-with-licenses.yaml new file mode 100644 index 000000000000..a6db3a8a9e7c --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/environment-with-licenses.yaml @@ -0,0 +1,9 @@ +name: test-env +channels: + - defaults +dependencies: + - _libgcc_mutex + - _openmp_mutex=5.1 + - blas=1.0=openblas + - bzip2=1.0.8=h998d150_5 +prefix: testdata From c55b0e6cac49c5d30abe6c0d4ccbb56932a0a45d Mon Sep 17 00:00:00 2001 From: Aqua Security automated builds <54269356+aqua-bot@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:28:03 +0300 Subject: [PATCH 205/352] release: v0.53.0 [main] (#6855) --- .release-please-manifest.json | 2 +- CHANGELOG.md | 56 +++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index be3d2812d7fe..e5e510d9d3ad 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{".":"0.52.0"} +{".":"0.53.0"} diff --git a/CHANGELOG.md b/CHANGELOG.md index 721b9a11baa7..fccfdbb410c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,61 @@ # Changelog +## [0.53.0](https://github.com/aquasecurity/trivy/compare/v0.52.0...v0.53.0) (2024-07-01) + + +### ⚠ BREAKING CHANGES + +* **k8s:** node-collector dynamic commands support ([#6861](https://github.com/aquasecurity/trivy/issues/6861)) +* add clean subcommand ([#6993](https://github.com/aquasecurity/trivy/issues/6993)) +* **aws:** Remove aws subcommand ([#6995](https://github.com/aquasecurity/trivy/issues/6995)) + +### Features + +* add clean subcommand ([#6993](https://github.com/aquasecurity/trivy/issues/6993)) ([8d0ae1f](https://github.com/aquasecurity/trivy/commit/8d0ae1f5de72d92a043dcd6b7c164d30e51b6047)) +* Add local ImageID to SARIF metadata ([#6522](https://github.com/aquasecurity/trivy/issues/6522)) ([f144e91](https://github.com/aquasecurity/trivy/commit/f144e912d34234f00b5a13b7a11a0019fa978b27)) +* add memory cache backend ([#7048](https://github.com/aquasecurity/trivy/issues/7048)) ([55ccd06](https://github.com/aquasecurity/trivy/commit/55ccd06df43f6ff28685f46d215ccb70f55916d2)) +* **aws:** Remove aws subcommand ([#6995](https://github.com/aquasecurity/trivy/issues/6995)) ([979e118](https://github.com/aquasecurity/trivy/commit/979e118a9e0ca8943bef9143f492d7eb1fd4d863)) +* **conda:** add licenses support for `environment.yml` files ([#6953](https://github.com/aquasecurity/trivy/issues/6953)) ([654217a](https://github.com/aquasecurity/trivy/commit/654217a65485ca0a07771ea61071977894eb4920)) +* **dart:** use first version of constraint for dependencies using SDK version ([#6239](https://github.com/aquasecurity/trivy/issues/6239)) ([042d6b0](https://github.com/aquasecurity/trivy/commit/042d6b08c283105c258a3dda98983b345a5305c3)) +* **image:** Set User-Agent header for Trivy container registry requests ([#6868](https://github.com/aquasecurity/trivy/issues/6868)) ([9b31697](https://github.com/aquasecurity/trivy/commit/9b31697274c8743d6e5a8f7a1a05daf60cd15910)) +* **java:** add support for `maven-metadata.xml` files for remote snapshot repositories. ([#6950](https://github.com/aquasecurity/trivy/issues/6950)) ([1f8fca1](https://github.com/aquasecurity/trivy/commit/1f8fca1fc77b989bb4e3ba820b297464dbdd825f)) +* **java:** add support for sbt projects using sbt-dependency-lock ([#6882](https://github.com/aquasecurity/trivy/issues/6882)) ([f18d035](https://github.com/aquasecurity/trivy/commit/f18d035ae13b281c96aa4ed69ca32e507d336e66)) +* **k8s:** node-collector dynamic commands support ([#6861](https://github.com/aquasecurity/trivy/issues/6861)) ([8d618e4](https://github.com/aquasecurity/trivy/commit/8d618e48a2f1b60c2e4c49cdd9deb8eb45c972b0)) +* **misconf:** add metadata to Cloud schema ([#6831](https://github.com/aquasecurity/trivy/issues/6831)) ([02d5404](https://github.com/aquasecurity/trivy/commit/02d540478d495416b50d7e8b187ff9f5bba41f45)) +* **misconf:** add support for AWS::EC2::SecurityGroupIngress/Egress ([#6755](https://github.com/aquasecurity/trivy/issues/6755)) ([55fa610](https://github.com/aquasecurity/trivy/commit/55fa6109cd0463fd3221aae41ca7b1d8c44ad430)) +* **misconf:** API Gateway V1 support for CloudFormation ([#6874](https://github.com/aquasecurity/trivy/issues/6874)) ([8491469](https://github.com/aquasecurity/trivy/commit/8491469f0b35bd9df706a433669f5b62239d4ef3)) +* **misconf:** support of selectors for all providers for Rego ([#6905](https://github.com/aquasecurity/trivy/issues/6905)) ([bc3741a](https://github.com/aquasecurity/trivy/commit/bc3741ae2c68cdd00fc0aef7e51985568b2eb78a)) +* **php:** add installed.json file support ([#4865](https://github.com/aquasecurity/trivy/issues/4865)) ([edc556b](https://github.com/aquasecurity/trivy/commit/edc556b85e3554c31e19b1ece189effb9ba2be12)) +* **plugin:** add support for nested archives ([#6845](https://github.com/aquasecurity/trivy/issues/6845)) ([622c67b](https://github.com/aquasecurity/trivy/commit/622c67b7647f94d0a0ca3acf711d8f847cdd8d98)) +* **sbom:** migrate to `CycloneDX v1.6` ([#6903](https://github.com/aquasecurity/trivy/issues/6903)) ([09e50ce](https://github.com/aquasecurity/trivy/commit/09e50ce6a82073ba62f1732d5aa0cd2701578693)) + + +### Bug Fixes + +* **c:** don't skip conan files from `file-patterns` and scan `.conan2` cache dir ([#6949](https://github.com/aquasecurity/trivy/issues/6949)) ([38b35dd](https://github.com/aquasecurity/trivy/commit/38b35dd3c804027e7a6e6a9d3c87b7ac333896c5)) +* **cli:** show info message only when --scanners is available ([#7032](https://github.com/aquasecurity/trivy/issues/7032)) ([e9fc3e3](https://github.com/aquasecurity/trivy/commit/e9fc3e3397564512038ddeca2adce0efcb3f93c5)) +* **cyclonedx:** trim non-URL info for `advisory.url` ([#6952](https://github.com/aquasecurity/trivy/issues/6952)) ([417212e](https://github.com/aquasecurity/trivy/commit/417212e0930aa52a27ebdc1b9370d2943ce0f8fa)) +* **debian:** take installed files from the origin layer ([#6849](https://github.com/aquasecurity/trivy/issues/6849)) ([089b953](https://github.com/aquasecurity/trivy/commit/089b953462260f01c40bdf588b2568ae0ef658bc)) +* **image:** parse `image.inspect.Created` field only for non-empty values ([#6948](https://github.com/aquasecurity/trivy/issues/6948)) ([0af5730](https://github.com/aquasecurity/trivy/commit/0af5730cbe56686417389c2fad643c1bdbb33999)) +* **license:** return license separation using separators `,`, `or`, etc. ([#6916](https://github.com/aquasecurity/trivy/issues/6916)) ([52f7aa5](https://github.com/aquasecurity/trivy/commit/52f7aa54b520a90a19736703f8ea63cc20fab104)) +* **misconf:** fix caching of modules in subdirectories ([#6814](https://github.com/aquasecurity/trivy/issues/6814)) ([0bcfedb](https://github.com/aquasecurity/trivy/commit/0bcfedbcaa9bbe30ee5ecade5b98e9ce3cc54c9b)) +* **misconf:** fix parsing of engine links and frameworks ([#6937](https://github.com/aquasecurity/trivy/issues/6937)) ([ec68c9a](https://github.com/aquasecurity/trivy/commit/ec68c9ab4580d057720179173d58734402c92af4)) +* **misconf:** handle source prefix to ignore ([#6945](https://github.com/aquasecurity/trivy/issues/6945)) ([c3192f0](https://github.com/aquasecurity/trivy/commit/c3192f061d7e84eaf38df8df7c879dc00b4ca137)) +* **misconf:** parsing numbers without fraction as int ([#6834](https://github.com/aquasecurity/trivy/issues/6834)) ([8141a13](https://github.com/aquasecurity/trivy/commit/8141a137ba50b553a9da877d95c7ccb491d041c6)) +* **nodejs:** fix infinite loop when package link from `package-lock.json` file is broken ([#6858](https://github.com/aquasecurity/trivy/issues/6858)) ([cf5aa33](https://github.com/aquasecurity/trivy/commit/cf5aa336e660e4c98481ebf8d15dd4e54c38581e)) +* **nodejs:** fix infinity loops for `pnpm` with cyclic imports ([#6857](https://github.com/aquasecurity/trivy/issues/6857)) ([7d083bc](https://github.com/aquasecurity/trivy/commit/7d083bc890eccc3bf32765c6d7e922cab2e2ef94)) +* **plugin:** respect `--insecure` ([#7022](https://github.com/aquasecurity/trivy/issues/7022)) ([3d02a31](https://github.com/aquasecurity/trivy/commit/3d02a31b44924f9e2495aae087f7ca9de3314db4)) +* **purl:** add missed os types ([#6955](https://github.com/aquasecurity/trivy/issues/6955)) ([2d85a00](https://github.com/aquasecurity/trivy/commit/2d85a003b22298d1101f84559f7c6b470f2b3909)) +* **python:** compare pkg names from `poetry.lock` and `pyproject.toml` in lowercase ([#6852](https://github.com/aquasecurity/trivy/issues/6852)) ([faa9d92](https://github.com/aquasecurity/trivy/commit/faa9d92cfeb8d924deda2dac583b6c97099c08d9)) +* **sbom:** don't overwrite `srcEpoch` when decoding SBOM files ([#6866](https://github.com/aquasecurity/trivy/issues/6866)) ([04af59c](https://github.com/aquasecurity/trivy/commit/04af59c2906bcfc7f7970b4e8f45a90f04313170)) +* **sbom:** fix panic when scanning SBOM file without root component into SBOM format ([#7051](https://github.com/aquasecurity/trivy/issues/7051)) ([3d4ae8b](https://github.com/aquasecurity/trivy/commit/3d4ae8b5be94cd9b00badeece8d86c2258b2cd90)) +* **sbom:** take pkg name from `purl` for maven pkgs ([#7008](https://github.com/aquasecurity/trivy/issues/7008)) ([a76e328](https://github.com/aquasecurity/trivy/commit/a76e3286c413de3dec55394fb41dd627dfee37ae)) +* **sbom:** use `purl` for `bitnami` pkg names ([#6982](https://github.com/aquasecurity/trivy/issues/6982)) ([7eabb92](https://github.com/aquasecurity/trivy/commit/7eabb92ec2e617300433445718be07ac74956454)) +* **sbom:** use package UIDs for uniqueness ([#7042](https://github.com/aquasecurity/trivy/issues/7042)) ([14d71ba](https://github.com/aquasecurity/trivy/commit/14d71ba63c39e51dd4179ba2d6002b46e1816e90)) +* **secret:** `Asymmetric Private Key` shouldn't start with space ([#6867](https://github.com/aquasecurity/trivy/issues/6867)) ([bb26445](https://github.com/aquasecurity/trivy/commit/bb26445e3df198df77930329f532ac5ab7a67af2)) +* **suse:** Add SLES 15.6 and Leap 15.6 ([#6964](https://github.com/aquasecurity/trivy/issues/6964)) ([5ee4e9d](https://github.com/aquasecurity/trivy/commit/5ee4e9d30ea814f60fd5705361cabf2e83a47a78)) +* use embedded when command path not found ([#7037](https://github.com/aquasecurity/trivy/issues/7037)) ([137c916](https://github.com/aquasecurity/trivy/commit/137c9164238ffd989a0c5ed24f23a55bbf341f6e)) + ## [0.52.0](https://github.com/aquasecurity/trivy/compare/v0.51.1...v0.52.0) (2024-06-03) From c46472655e18c751cc68155e443990e1ec8c154d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 08:32:16 +0400 Subject: [PATCH 206/352] chore(deps): bump the github-actions group with 2 updates (#7067) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/reusable-release.yaml | 2 +- .github/workflows/roadmap.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/reusable-release.yaml b/.github/workflows/reusable-release.yaml index 49b3ced06daa..e308aba0a2db 100644 --- a/.github/workflows/reusable-release.yaml +++ b/.github/workflows/reusable-release.yaml @@ -108,7 +108,7 @@ jobs: # because GoReleaser Free doesn't support pushing images with the `--snapshot` flag. - name: Build and push if: ${{ inputs.goreleaser_config == 'goreleaser-canary.yml' }} - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: platforms: linux/amd64, linux/arm64 file: ./Dockerfile.canary # path to Dockerfile diff --git a/.github/workflows/roadmap.yaml b/.github/workflows/roadmap.yaml index 39c80f367281..96f6e7ae4fb8 100644 --- a/.github/workflows/roadmap.yaml +++ b/.github/workflows/roadmap.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: # 'kind/feature' AND 'priority/backlog' labels -> 'Backlog' column - - uses: actions/add-to-project@v1.0.1 # add new issue to project + - uses: actions/add-to-project@v1.0.2 # add new issue to project with: project-url: https://github.com/orgs/aquasecurity/projects/25 github-token: ${{ secrets.ORG_PROJECT_TOKEN }} @@ -28,7 +28,7 @@ jobs: field-values: Backlog # 'kind/feature' AND 'priority/important-longterm' labels -> 'Important (long-term)' column - - uses: actions/add-to-project@v1.0.1 # add new issue to project + - uses: actions/add-to-project@v1.0.2 # add new issue to project with: project-url: https://github.com/orgs/aquasecurity/projects/25 github-token: ${{ secrets.ORG_PROJECT_TOKEN }} @@ -45,7 +45,7 @@ jobs: field-values: Important (long-term) # 'kind/feature' AND 'priority/important-soon' labels -> 'Important (soon)' column - - uses: actions/add-to-project@v1.0.1 # add new issue to project + - uses: actions/add-to-project@v1.0.2 # add new issue to project with: project-url: https://github.com/orgs/aquasecurity/projects/25 github-token: ${{ secrets.ORG_PROJECT_TOKEN }} @@ -62,7 +62,7 @@ jobs: field-values: Important (soon) # 'kind/feature' AND 'priority/critical-urgent' labels -> 'Urgent' column - - uses: actions/add-to-project@v1.0.1 # add new issue to project + - uses: actions/add-to-project@v1.0.2 # add new issue to project with: project-url: https://github.com/orgs/aquasecurity/projects/25 github-token: ${{ secrets.ORG_PROJECT_TOKEN }} From 6a307bb3895ab0feb1139e0ee65cc79b03b849b9 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Tue, 2 Jul 2024 08:32:31 +0400 Subject: [PATCH 207/352] docs: navigate to the release highlights and summary (#7072) Signed-off-by: knqyf263 --- docs/community/maintainer/release-flow.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/community/maintainer/release-flow.md b/docs/community/maintainer/release-flow.md index 6a47469f12b8..6c5bd17fc69d 100644 --- a/docs/community/maintainer/release-flow.md +++ b/docs/community/maintainer/release-flow.md @@ -16,9 +16,10 @@ For detailed behavior, please refer to [the GitHub Actions configuration][workfl The release flow consists of the following main steps: 1. Creating the release PR (automatically or manually) -1. Drafting the release notes +1. Drafting the release notes in GitHub Discussions 1. Merging the release PR -1. Updating the release notes +1. Updating the release notes in GitHub Discussions +1. Navigating to the release notes in GitHub Releases page ### Automatic Release PR Creation When a releasable commit (a commit with `feat` or `fix` prefix) is merged, a release PR is automatically created. @@ -57,6 +58,23 @@ When the PR is merged, a tag is automatically created, and [GoReleaser][goreleas If the release completes without errors, a page for the release notes is created in GitHub Discussions (e.g., https://github.com/aquasecurity/trivy/discussions/6622). Copy the draft release notes, adjust the formatting, and finalize the release notes. +### Navigating to the Release Notes +To navigate to the release highlights and summary in GitHub Discussions, place a link in the GitHub Releases page as below: + +``` +## ⚡Release highlights and summary⚡ + +👉 https://github.com/aquasecurity/trivy/discussions/6838 + +## Changelog +https://github.com/aquasecurity/trivy/blob/main/CHANGELOG.md#0520-2024-06-03 +``` + +Replace URLs with appropriate ones. + +Example: https://github.com/aquasecurity/trivy/releases/tag/v0.52.0 + + The release is now complete. [conventional-commits]: https://www.conventionalcommits.org/en/v1.0.0/ From fc6b3a760b646bb4ff0d4065fc5cd0058b70ca39 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Tue, 2 Jul 2024 08:32:46 +0400 Subject: [PATCH 208/352] refactor: pass DB dir to trivy-db (#7057) Signed-off-by: knqyf263 --- go.mod | 4 ++-- go.sum | 7 +++--- integration/integration_test.go | 13 +++------- internal/dbtest/db.go | 13 +++++----- pkg/commands/artifact/run.go | 4 ++-- pkg/commands/clean/run.go | 2 +- pkg/commands/operation/operation.go | 19 ++++----------- pkg/commands/server/run.go | 4 ++-- pkg/db/db.go | 37 ++++++++++++++++++++++------- pkg/db/db_test.go | 31 ++++++++++++------------ pkg/rpc/server/listen.go | 29 +++++++++++----------- pkg/rpc/server/listen_test.go | 15 ++++++------ pkg/version/version.go | 3 ++- 13 files changed, 91 insertions(+), 90 deletions(-) diff --git a/go.mod b/go.mod index e1607b6b8b39..8e01f47254d2 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac github.com/aquasecurity/tml v0.6.1 github.com/aquasecurity/trivy-checks v0.13.0 - github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d + github.com/aquasecurity/trivy-db v0.0.0-20240701103400-8e907467e9ab github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240627095026-cf9d48837f6d github.com/aws/aws-sdk-go-v2 v1.27.2 @@ -192,7 +192,7 @@ require ( github.com/containerd/ttrpc v1.2.4 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect diff --git a/go.sum b/go.sum index d5318916faeb..18c1ec4c2a66 100644 --- a/go.sum +++ b/go.sum @@ -771,8 +771,8 @@ github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gw github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= github.com/aquasecurity/trivy-checks v0.13.0 h1:na6PTdY4U0uK/fjz3HNRYBxvYSJ8vgTb57a5T8Y5t9w= github.com/aquasecurity/trivy-checks v0.13.0/go.mod h1:Xec/SMVGV66I7RgUqOX9MEr+YxBqHXDVLTYmpspPi3E= -github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d h1:fjI9mkoTUAkbGqpzt9nJsO24RAdfG+ZSiLFj0G2jO8c= -github.com/aquasecurity/trivy-db v0.0.0-20231005141211-4fc651f7ac8d/go.mod h1:cj9/QmD9N3OZnKQMp+/DvdV+ym3HyIkd4e+F0ZM3ZGs= +github.com/aquasecurity/trivy-db v0.0.0-20240701103400-8e907467e9ab h1:EmpLGFgRJOstPWDpL4KW+Xap4zRYxyctXDTj5luMQdE= +github.com/aquasecurity/trivy-db v0.0.0-20240701103400-8e907467e9ab/go.mod h1:f+wSW9D5txv8S+tw4D4WNOibaUJYwvNnQuQlGQ8gO6c= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240627095026-cf9d48837f6d h1:z5Ug+gqNjgHzCo7rmv6wKTmyJ8E3bAVEU2AASo3740s= @@ -1019,8 +1019,9 @@ github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf 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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= diff --git a/integration/integration_test.go b/integration/integration_test.go index e9d534da3e06..c7c923af0c33 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -26,12 +26,11 @@ import ( "github.com/stretchr/testify/require" "github.com/xeipuuv/gojsonschema" - "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/metadata" - "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/commands" + "github.com/aquasecurity/trivy/pkg/db" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/uuid" @@ -56,15 +55,9 @@ func initDB(t *testing.T) string { } cacheDir := dbtest.InitDB(t, fixtures) - defer db.Close() - - dbDir := filepath.Dir(db.Path(cacheDir)) - - metadataFile := filepath.Join(dbDir, "metadata.json") - f, err := os.Create(metadataFile) - require.NoError(t, err) + defer dbtest.Close() - err = json.NewEncoder(f).Encode(metadata.Metadata{ + err = metadata.NewClient(db.Dir(cacheDir)).Update(metadata.Metadata{ Version: db.SchemaVersion, NextUpdate: time.Now().Add(24 * time.Hour), UpdatedAt: time.Now(), diff --git a/internal/dbtest/db.go b/internal/dbtest/db.go index 9ef89fadba99..7976a54e8b5f 100644 --- a/internal/dbtest/db.go +++ b/internal/dbtest/db.go @@ -9,17 +9,18 @@ import ( "github.com/stretchr/testify/require" fixtures "github.com/aquasecurity/bolt-fixtures" - "github.com/aquasecurity/trivy-db/pkg/db" + trivydb "github.com/aquasecurity/trivy-db/pkg/db" jdb "github.com/aquasecurity/trivy-java-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/db" ) // InitDB initializes testing database. func InitDB(t *testing.T, fixtureFiles []string) string { // Create a temp dir - dir := t.TempDir() + cacheDir := t.TempDir() - dbPath := db.Path(dir) - dbDir := filepath.Dir(dbPath) + dbDir := db.Dir(cacheDir) + dbPath := trivydb.Path(dbDir) err := os.MkdirAll(dbDir, 0700) require.NoError(t, err) @@ -30,9 +31,9 @@ func InitDB(t *testing.T, fixtureFiles []string) string { require.NoError(t, loader.Close()) // Initialize DB - require.NoError(t, db.Init(dir)) + require.NoError(t, db.Init(dbDir)) - return dir + return cacheDir } func Close() error { diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index db73cf58b391..82cbb439ecce 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -11,9 +11,9 @@ import ( "github.com/spf13/viper" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/commands/operation" + "github.com/aquasecurity/trivy/pkg/db" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -295,7 +295,7 @@ func (r *runner) initDB(ctx context.Context, opts flag.Options) error { return SkipScan } - if err := db.Init(opts.CacheDir); err != nil { + if err := db.Init(db.Dir(opts.CacheDir)); err != nil { return xerrors.Errorf("error in vulnerability DB initialize: %w", err) } r.dbOpen = true diff --git a/pkg/commands/clean/run.go b/pkg/commands/clean/run.go index fb20799a571b..9d00d431b962 100644 --- a/pkg/commands/clean/run.go +++ b/pkg/commands/clean/run.go @@ -76,7 +76,7 @@ func cleanScanCache(ctx context.Context, opts flag.Options) error { func cleanVulnerabilityDB(ctx context.Context, opts flag.Options) error { log.InfoContext(ctx, "Removing vulnerability database...") - if err := db.NewClient(opts.CacheDir, true).Clear(ctx); err != nil { + if err := db.NewClient(db.Dir(opts.CacheDir), true).Clear(ctx); err != nil { return xerrors.Errorf("clear vulnerability database: %w", err) } diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index 63946710f1b2..92e45e5e696e 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -7,7 +7,6 @@ import ( "github.com/google/go-containerregistry/pkg/name" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy-db/pkg/metadata" "github.com/aquasecurity/trivy/pkg/db" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/flag" @@ -24,7 +23,8 @@ func DownloadDB(ctx context.Context, appVersion, cacheDir string, dbRepository n mu.Lock() defer mu.Unlock() - client := db.NewClient(cacheDir, quiet, db.WithDBRepository(dbRepository)) + dbDir := db.Dir(cacheDir) + client := db.NewClient(dbDir, quiet, db.WithDBRepository(dbRepository)) needsUpdate, err := client.NeedsUpdate(ctx, appVersion, skipUpdate) if err != nil { return xerrors.Errorf("database error: %w", err) @@ -33,29 +33,18 @@ func DownloadDB(ctx context.Context, appVersion, cacheDir string, dbRepository n if needsUpdate { log.Info("Need to update DB") log.Info("Downloading DB...", log.String("repository", dbRepository.String())) - if err = client.Download(ctx, cacheDir, opt); err != nil { + if err = client.Download(ctx, dbDir, opt); err != nil { return xerrors.Errorf("failed to download vulnerability DB: %w", err) } } // for debug - if err = showDBInfo(cacheDir); err != nil { + if err = client.ShowInfo(); err != nil { return xerrors.Errorf("failed to show database info: %w", err) } return nil } -func showDBInfo(cacheDir string) error { - m := metadata.NewClient(cacheDir) - meta, err := m.Get() - if err != nil { - return xerrors.Errorf("something wrong with DB: %w", err) - } - log.Debug("DB info", log.Int("schema", meta.Version), log.Time("updated_at", meta.UpdatedAt), - log.Time("next_update", meta.NextUpdate), log.Time("downloaded_at", meta.DownloadedAt)) - return nil -} - // InitBuiltinPolicies downloads the built-in policies and loads them func InitBuiltinPolicies(ctx context.Context, cacheDir string, quiet, skipUpdate bool, checkBundleRepository string, registryOpts ftypes.RegistryOptions) ([]string, error) { mu.Lock() diff --git a/pkg/commands/server/run.go b/pkg/commands/server/run.go index c5f7b0da2f0b..19b24f990396 100644 --- a/pkg/commands/server/run.go +++ b/pkg/commands/server/run.go @@ -5,9 +5,9 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/commands/operation" + "github.com/aquasecurity/trivy/pkg/db" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/module" @@ -35,7 +35,7 @@ func Run(ctx context.Context, opts flag.Options) (err error) { return nil } - if err = db.Init(opts.CacheDir); err != nil { + if err = db.Init(db.Dir(opts.CacheDir)); err != nil { return xerrors.Errorf("error in vulnerability DB initialize: %w", err) } diff --git a/pkg/db/db.go b/pkg/db/db.go index e87277f93375..e4af60d092c6 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "time" "github.com/google/go-containerregistry/pkg/name" @@ -28,6 +29,10 @@ const ( var ( DefaultRepository = fmt.Sprintf("%s:%d", "ghcr.io/aquasecurity/trivy-db", db.SchemaVersion) defaultRepository, _ = name.NewTag(DefaultRepository) + + Init = db.Init + Close = db.Close + Path = db.Path ) type options struct { @@ -56,13 +61,17 @@ func WithDBRepository(dbRepository name.Reference) Option { type Client struct { *options - cacheDir string + dbDir string metadata metadata.Client quiet bool } +func Dir(cacheDir string) string { + return filepath.Join(cacheDir, "db") +} + // NewClient is the factory method for DB client -func NewClient(cacheDir string, quiet bool, opts ...Option) *Client { +func NewClient(dbDir string, quiet bool, opts ...Option) *Client { o := &options{ dbRepository: defaultRepository, } @@ -73,8 +82,8 @@ func NewClient(cacheDir string, quiet bool, opts ...Option) *Client { return &Client{ options: o, - cacheDir: cacheDir, - metadata: metadata.NewClient(cacheDir), + dbDir: dbDir, + metadata: metadata.NewClient(dbDir), quiet: quiet, } } @@ -149,7 +158,7 @@ func (c *Client) Download(ctx context.Context, dst string, opt types.RegistryOpt return xerrors.Errorf("OCI artifact error: %w", err) } - if err = art.Download(ctx, db.Dir(dst), oci.DownloadOption{MediaType: dbMediaType}); err != nil { + if err = art.Download(ctx, dst, oci.DownloadOption{MediaType: dbMediaType}); err != nil { return xerrors.Errorf("database download error: %w", err) } @@ -159,19 +168,19 @@ func (c *Client) Download(ctx context.Context, dst string, opt types.RegistryOpt return nil } -func (c *Client) Clear(ctx context.Context) error { - if err := os.RemoveAll(db.Dir(c.cacheDir)); err != nil { +func (c *Client) Clear(_ context.Context) error { + if err := os.RemoveAll(c.dbDir); err != nil { return xerrors.Errorf("failed to remove vulnerability database: %w", err) } return nil } -func (c *Client) updateDownloadedAt(ctx context.Context, dst string) error { +func (c *Client) updateDownloadedAt(ctx context.Context, dbDir string) error { log.Debug("Updating database metadata...") // We have to initialize a metadata client here // since the destination may be different from the cache directory. - client := metadata.NewClient(dst) + client := metadata.NewClient(dbDir) meta, err := client.Get() if err != nil { return xerrors.Errorf("unable to get metadata: %w", err) @@ -207,3 +216,13 @@ func (c *Client) initOCIArtifact(opt types.RegistryOptions) (*oci.Artifact, erro } return art, nil } + +func (c *Client) ShowInfo() error { + meta, err := c.metadata.Get() + if err != nil { + return xerrors.Errorf("something wrong with DB: %w", err) + } + log.Debug("DB info", log.Int("schema", meta.Version), log.Time("updated_at", meta.UpdatedAt), + log.Time("next_update", meta.NextUpdate), log.Time("downloaded_at", meta.DownloadedAt)) + return nil +} diff --git a/pkg/db/db_test.go b/pkg/db/db_test.go index d7eca907fe32..ff5e36e2b194 100644 --- a/pkg/db/db_test.go +++ b/pkg/db/db_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - tdb "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/metadata" "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/clock" @@ -31,7 +30,7 @@ func TestClient_NeedsUpdate(t *testing.T) { { name: "happy path", metadata: metadata.Metadata{ - Version: tdb.SchemaVersion, + Version: db.SchemaVersion, NextUpdate: timeNextUpdateDay1, }, want: true, @@ -52,7 +51,7 @@ func TestClient_NeedsUpdate(t *testing.T) { { name: "happy path with --skip-update", metadata: metadata.Metadata{ - Version: tdb.SchemaVersion, + Version: db.SchemaVersion, NextUpdate: timeNextUpdateDay1, }, skip: true, @@ -61,7 +60,7 @@ func TestClient_NeedsUpdate(t *testing.T) { { name: "skip downloading DB", metadata: metadata.Metadata{ - Version: tdb.SchemaVersion, + Version: db.SchemaVersion, NextUpdate: timeNextUpdateDay2, }, want: false, @@ -69,11 +68,11 @@ func TestClient_NeedsUpdate(t *testing.T) { { name: "newer schema version", metadata: metadata.Metadata{ - Version: tdb.SchemaVersion + 1, + Version: db.SchemaVersion + 1, NextUpdate: timeNextUpdateDay2, }, wantErr: fmt.Sprintf("the version of DB schema doesn't match. Local DB: %d, Expected: %d", - tdb.SchemaVersion+1, tdb.SchemaVersion), + db.SchemaVersion+1, db.SchemaVersion), }, { name: "--skip-update on the first run", @@ -89,12 +88,12 @@ func TestClient_NeedsUpdate(t *testing.T) { }, skip: true, wantErr: fmt.Sprintf("--skip-update cannot be specified with the old DB schema. Local DB: %d, Expected: %d", - 0, tdb.SchemaVersion), + 0, db.SchemaVersion), }, { name: "happy with old DownloadedAt", metadata: metadata.Metadata{ - Version: tdb.SchemaVersion, + Version: db.SchemaVersion, NextUpdate: timeNextUpdateDay1, DownloadedAt: time.Date(2019, 9, 30, 22, 30, 0, 0, time.UTC), }, @@ -103,7 +102,7 @@ func TestClient_NeedsUpdate(t *testing.T) { { name: "skip downloading DB with recent DownloadedAt", metadata: metadata.Metadata{ - Version: tdb.SchemaVersion, + Version: db.SchemaVersion, NextUpdate: timeNextUpdateDay1, DownloadedAt: time.Date(2019, 9, 30, 23, 30, 0, 0, time.UTC), }, @@ -113,9 +112,9 @@ func TestClient_NeedsUpdate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cacheDir := t.TempDir() + dbDir := db.Dir(t.TempDir()) if tt.metadata != (metadata.Metadata{}) { - meta := metadata.NewClient(cacheDir) + meta := metadata.NewClient(dbDir) err := meta.Update(tt.metadata) require.NoError(t, err) } @@ -123,7 +122,7 @@ func TestClient_NeedsUpdate(t *testing.T) { // Set a fake time ctx := clock.With(context.Background(), time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)) - client := db.NewClient(cacheDir, true) + client := db.NewClient(dbDir, true) needsUpdate, err := client.NeedsUpdate(ctx, "test", tt.skip) switch { @@ -172,9 +171,9 @@ func TestClient_Download(t *testing.T) { // Fake DB art := dbtest.NewFakeDB(t, tt.input, dbtest.FakeDBOptions{}) - cacheDir := t.TempDir() - client := db.NewClient(cacheDir, true, db.WithOCIArtifact(art)) - err := client.Download(ctx, cacheDir, ftypes.RegistryOptions{}) + dbDir := db.Dir(t.TempDir()) + client := db.NewClient(dbDir, true, db.WithOCIArtifact(art)) + err := client.Download(ctx, dbDir, ftypes.RegistryOptions{}) if tt.wantErr != "" { require.Error(t, err) assert.ErrorContains(t, err, tt.wantErr) @@ -182,7 +181,7 @@ func TestClient_Download(t *testing.T) { } require.NoError(t, err) - meta := metadata.NewClient(cacheDir) + meta := metadata.NewClient(dbDir) got, err := meta.Get() require.NoError(t, err) diff --git a/pkg/rpc/server/listen.go b/pkg/rpc/server/listen.go index 7e2ebc6b8227..0c4484930773 100644 --- a/pkg/rpc/server/listen.go +++ b/pkg/rpc/server/listen.go @@ -13,10 +13,9 @@ import ( "github.com/twitchtv/twirp" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/metadata" "github.com/aquasecurity/trivy/pkg/cache" - dbc "github.com/aquasecurity/trivy/pkg/db" + "github.com/aquasecurity/trivy/pkg/db" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/utils/fsutils" @@ -31,7 +30,7 @@ const updateInterval = 1 * time.Hour type Server struct { appVersion string addr string - cacheDir string + dbDir string token string tokenHeader string dbRepository name.Reference @@ -45,7 +44,7 @@ func NewServer(appVersion, addr, cacheDir, token, tokenHeader string, dbReposito return Server{ appVersion: appVersion, addr: addr, - cacheDir: cacheDir, + dbDir: db.Dir(cacheDir), token: token, tokenHeader: tokenHeader, dbRepository: dbRepository, @@ -59,16 +58,16 @@ func (s Server) ListenAndServe(ctx context.Context, serverCache cache.Cache, ski dbUpdateWg := &sync.WaitGroup{} go func() { - worker := newDBWorker(dbc.NewClient(s.cacheDir, true, dbc.WithDBRepository(s.dbRepository))) + worker := newDBWorker(db.NewClient(s.dbDir, true, db.WithDBRepository(s.dbRepository))) for { time.Sleep(updateInterval) - if err := worker.update(ctx, s.appVersion, s.cacheDir, skipDBUpdate, dbUpdateWg, requestWg, s.RegistryOptions); err != nil { + if err := worker.update(ctx, s.appVersion, s.dbDir, skipDBUpdate, dbUpdateWg, requestWg, s.RegistryOptions); err != nil { log.Errorf("%+v\n", err) } } }() - mux := newServeMux(ctx, serverCache, dbUpdateWg, requestWg, s.token, s.tokenHeader, s.cacheDir) + mux := newServeMux(ctx, serverCache, dbUpdateWg, requestWg, s.token, s.tokenHeader, s.dbDir) log.Infof("Listening %s...", s.addr) return http.ListenAndServe(s.addr, mux) @@ -128,14 +127,14 @@ func withToken(base http.Handler, token, tokenHeader string) http.Handler { } type dbWorker struct { - dbClient *dbc.Client + dbClient *db.Client } -func newDBWorker(dbClient *dbc.Client) dbWorker { +func newDBWorker(dbClient *db.Client) dbWorker { return dbWorker{dbClient: dbClient} } -func (w dbWorker) update(ctx context.Context, appVersion, cacheDir string, +func (w dbWorker) update(ctx context.Context, appVersion, dbDir string, skipDBUpdate bool, dbUpdateWg, requestWg *sync.WaitGroup, opt types.RegistryOptions) error { log.Debug("Check for DB update...") needsUpdate, err := w.dbClient.NeedsUpdate(ctx, appVersion, skipDBUpdate) @@ -146,13 +145,13 @@ func (w dbWorker) update(ctx context.Context, appVersion, cacheDir string, } log.Info("Updating DB...") - if err = w.hotUpdate(ctx, cacheDir, dbUpdateWg, requestWg, opt); err != nil { + if err = w.hotUpdate(ctx, dbDir, dbUpdateWg, requestWg, opt); err != nil { return xerrors.Errorf("failed DB hot update: %w", err) } return nil } -func (w dbWorker) hotUpdate(ctx context.Context, cacheDir string, dbUpdateWg, requestWg *sync.WaitGroup, opt types.RegistryOptions) error { +func (w dbWorker) hotUpdate(ctx context.Context, dbDir string, dbUpdateWg, requestWg *sync.WaitGroup, opt types.RegistryOptions) error { tmpDir, err := os.MkdirTemp("", "db") if err != nil { return xerrors.Errorf("failed to create a temp dir: %w", err) @@ -175,17 +174,17 @@ func (w dbWorker) hotUpdate(ctx context.Context, cacheDir string, dbUpdateWg, re } // Copy trivy.db - if _, err = fsutils.CopyFile(db.Path(tmpDir), db.Path(cacheDir)); err != nil { + if _, err = fsutils.CopyFile(db.Path(tmpDir), db.Path(dbDir)); err != nil { return xerrors.Errorf("failed to copy the database file: %w", err) } // Copy metadata.json - if _, err = fsutils.CopyFile(metadata.Path(tmpDir), metadata.Path(cacheDir)); err != nil { + if _, err = fsutils.CopyFile(metadata.Path(tmpDir), metadata.Path(dbDir)); err != nil { return xerrors.Errorf("failed to copy the metadata file: %w", err) } log.Info("Reopening DB...") - if err = db.Init(cacheDir); err != nil { + if err = db.Init(dbDir); err != nil { return xerrors.Errorf("failed to open DB: %w", err) } diff --git a/pkg/rpc/server/listen_test.go b/pkg/rpc/server/listen_test.go index 82c8b2669bc4..8457cef042d7 100644 --- a/pkg/rpc/server/listen_test.go +++ b/pkg/rpc/server/listen_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - trivydb "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/metadata" "github.com/aquasecurity/trivy/internal/dbtest" "github.com/aquasecurity/trivy/pkg/cache" @@ -75,17 +74,17 @@ func Test_dbWorker_update(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cacheDir := t.TempDir() + dbDir := db.Dir(t.TempDir()) // Initialize the cache - meta := metadata.NewClient(cacheDir) + meta := metadata.NewClient(dbDir) err := meta.Update(cachedMetadata) require.NoError(t, err) - err = trivydb.Init(cacheDir) + err = db.Init(dbDir) require.NoError(t, err) - defer func() { _ = trivydb.Close() }() + defer func() { _ = db.Close() }() // Set a fake time ctx := clock.With(context.Background(), tt.now) @@ -95,11 +94,11 @@ func Test_dbWorker_update(t *testing.T) { art := dbtest.NewFakeDB(t, dbPath, dbtest.FakeDBOptions{ MediaType: tt.layerMediaType, }) - client := db.NewClient(cacheDir, true, db.WithOCIArtifact(art)) + client := db.NewClient(dbDir, true, db.WithOCIArtifact(art)) w := newDBWorker(client) var dbUpdateWg, requestWg sync.WaitGroup - err = w.update(ctx, "1.2.3", cacheDir, + err = w.update(ctx, "1.2.3", dbDir, tt.skipUpdate, &dbUpdateWg, &requestWg, ftypes.RegistryOptions{}) if tt.wantErr != "" { require.Error(t, err, tt.name) @@ -108,7 +107,7 @@ func Test_dbWorker_update(t *testing.T) { } require.NoError(t, err, tt.name) - mc := metadata.NewClient(cacheDir) + mc := metadata.NewClient(dbDir) got, err := mc.Get() require.NoError(t, err, tt.name) assert.Equal(t, tt.want, got, tt.name) diff --git a/pkg/version/version.go b/pkg/version/version.go index 4490364db9aa..60ee62bd52f1 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -6,6 +6,7 @@ import ( "github.com/aquasecurity/trivy-db/pkg/metadata" javadb "github.com/aquasecurity/trivy-java-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/db" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/policy" "github.com/aquasecurity/trivy/pkg/version/app" @@ -45,7 +46,7 @@ func NewVersionInfo(cacheDir string) VersionInfo { var dbMeta *metadata.Metadata var javadbMeta *metadata.Metadata - mc := metadata.NewClient(cacheDir) + mc := metadata.NewClient(db.Dir(cacheDir)) meta, err := mc.Get() if err != nil { log.Debug("Failed to get DB metadata", log.Err(err)) From acbec053c985388a26d899e73b4b7f5a6d1fa210 Mon Sep 17 00:00:00 2001 From: Paul Cacheux Date: Tue, 2 Jul 2024 08:11:09 +0200 Subject: [PATCH 209/352] perf(debian): use `bytes.Index` in `emptyLineSplit` to cut allocation (#7065) --- pkg/fanal/analyzer/pkg/dpkg/scanner.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/fanal/analyzer/pkg/dpkg/scanner.go b/pkg/fanal/analyzer/pkg/dpkg/scanner.go index 2e38f06b0cf7..d29fe951f652 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/scanner.go +++ b/pkg/fanal/analyzer/pkg/dpkg/scanner.go @@ -5,7 +5,6 @@ import ( "bytes" "io" "net/textproto" - "strings" ) type dpkgScanner struct { @@ -42,7 +41,7 @@ func emptyLineSplit(data []byte, atEOF bool) (advance int, token []byte, err err return 0, nil, nil } - if i := strings.Index(string(data), "\n\n"); i >= 0 { + if i := bytes.Index(data, []byte("\n\n")); i >= 0 { // We have a full empty line terminated block. return i + 2, data[0:i], nil } From 91f22372f93c8921b7900125eb579bfa333fe09e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:56:17 +0400 Subject: [PATCH 210/352] chore(deps): bump the common group across 1 directory with 23 updates (#7066) Signed-off-by: dependabot[bot] Signed-off-by: knqyf263 Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: knqyf263 --- go.mod | 68 +++++++++++++-------------- go.sum | 144 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 106 insertions(+), 106 deletions(-) diff --git a/go.mod b/go.mod index 8e01f47254d2..bae728a67f96 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ toolchain go1.22.4 require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 github.com/BurntSushi/toml v1.4.0 github.com/CycloneDX/cyclonedx-go v0.9.0 github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible @@ -15,7 +15,7 @@ require ( github.com/NYTimes/gziphandler v1.1.1 github.com/alecthomas/chroma v0.10.0 github.com/alicebob/miniredis/v2 v2.33.0 - github.com/antchfx/htmlquery v1.3.1 + github.com/antchfx/htmlquery v1.3.2 github.com/apparentlymart/go-cidr v1.1.0 github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce @@ -29,21 +29,21 @@ require ( github.com/aquasecurity/trivy-db v0.0.0-20240701103400-8e907467e9ab github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240627095026-cf9d48837f6d - github.com/aws/aws-sdk-go-v2 v1.27.2 - github.com/aws/aws-sdk-go-v2/config v1.27.18 - github.com/aws/aws-sdk-go-v2/credentials v1.17.18 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.1 - github.com/aws/aws-sdk-go-v2/service/ecr v1.28.5 - github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect - github.com/aws/smithy-go v1.20.2 + github.com/aws/aws-sdk-go-v2 v1.30.1 + github.com/aws/aws-sdk-go-v2/config v1.27.23 + github.com/aws/aws-sdk-go-v2/credentials v1.17.23 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.167.1 + github.com/aws/aws-sdk-go-v2/service/ecr v1.30.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.57.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect + github.com/aws/smithy-go v1.20.3 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cenkalti/backoff/v4 v4.3.0 github.com/cheggaaa/pb/v3 v3.1.5 - github.com/containerd/containerd v1.7.17 + github.com/containerd/containerd v1.7.18 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 - github.com/docker/docker v26.1.3+incompatible + github.com/docker/docker v26.1.4+incompatible github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.17.0 github.com/go-git/go-git/v5 v5.12.0 @@ -62,7 +62,7 @@ require ( github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/hc-install v0.7.0 - github.com/hashicorp/hcl/v2 v2.20.1 + github.com/hashicorp/hcl/v2 v2.21.0 github.com/hashicorp/terraform-exec v0.21.0 github.com/in-toto/in-toto-golang v0.9.0 github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f @@ -87,7 +87,7 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 github.com/moby/buildkit v0.13.2 - github.com/open-policy-agent/opa v0.65.0 + github.com/open-policy-agent/opa v0.66.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/openvex/go-vex v0.2.5 @@ -95,20 +95,20 @@ require ( github.com/owenrumney/squealer v1.2.2 github.com/package-url/packageurl-go v0.1.3 github.com/quasilyte/go-ruleguard/dsl v0.3.22 - github.com/samber/lo v1.39.0 + github.com/samber/lo v1.44.0 github.com/secure-systems-lab/go-securesystemslib v0.8.0 github.com/sigstore/rekor v1.3.6 github.com/sirupsen/logrus v1.9.3 github.com/sosedoff/gitkit v0.4.0 - github.com/spdx/tools-golang v0.5.4 // v0.5.3 with necessary changes. Can be upgraded to version 0.5.4 after release. + github.com/spdx/tools-golang v0.5.5 // v0.5.3 with necessary changes. Can be upgraded to version 0.5.4 after release. github.com/spf13/cast v1.6.0 - github.com/spf13/cobra v1.8.0 + github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.31.0 github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0 - github.com/tetratelabs/wazero v1.7.2 + github.com/tetratelabs/wazero v1.7.3 github.com/twitchtv/twirp v8.1.3+incompatible github.com/xeipuuv/gojsonschema v1.2.0 github.com/xlab/treeprint v1.2.0 @@ -117,18 +117,18 @@ require ( go.etcd.io/bbolt v1.3.10 golang.org/x/crypto v0.24.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect - golang.org/x/mod v0.17.0 + golang.org/x/mod v0.18.0 golang.org/x/net v0.26.0 golang.org/x/sync v0.7.0 golang.org/x/term v0.21.0 golang.org/x/text v0.16.0 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 - google.golang.org/protobuf v1.34.1 + google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.15.1 + helm.sh/helm/v3 v3.15.2 k8s.io/api v0.30.2 k8s.io/utils v0.0.0-20231127182322-b307cd553661 - modernc.org/sqlite v1.30.0 + modernc.org/sqlite v1.30.1 sigs.k8s.io/yaml v1.4.0 ) @@ -140,7 +140,7 @@ require ( dario.cat/mergo v1.0.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect @@ -164,19 +164,19 @@ require ( github.com/agnivade/levenshtein v1.1.1 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect - github.com/antchfx/xpath v1.3.0 // indirect + github.com/antchfx/xpath v1.3.1 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.54.6 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/briandowns/spinner v1.23.0 // indirect @@ -198,7 +198,7 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect - github.com/docker/cli v25.0.3+incompatible // indirect + github.com/docker/cli v26.1.4+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect @@ -377,7 +377,7 @@ require ( k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/kubectl v0.30.1 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect - modernc.org/libc v1.50.9 // indirect + modernc.org/libc v1.52.1 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect modernc.org/strutil v1.2.0 // indirect diff --git a/go.sum b/go.sum index 18c1ec4c2a66..606c3be8e5fc 100644 --- a/go.sum +++ b/go.sum @@ -616,12 +616,12 @@ github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= @@ -738,10 +738,10 @@ github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/antchfx/htmlquery v1.3.1 h1:wm0LxjLMsZhRHfQKKZscDf2COyH4vDYA3wyH+qZ+Ylc= -github.com/antchfx/htmlquery v1.3.1/go.mod h1:PTj+f1V2zksPlwNt7uVvZPsxpKNa7mlVliCRxLX6Nx8= -github.com/antchfx/xpath v1.3.0 h1:nTMlzGAK3IJ0bPpME2urTuFL76o4A96iYvoKFHRXJgc= -github.com/antchfx/xpath v1.3.0/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/antchfx/htmlquery v1.3.2 h1:85YdttVkR1rAY+Oiv/nKI4FCimID+NXhDn82kz3mEvs= +github.com/antchfx/htmlquery v1.3.2/go.mod h1:1mbkcEgEarAokJiWhTfr4hR06w/q2ZZjnYLrDt6CTUk= +github.com/antchfx/xpath v1.3.1 h1:PNbFuUqHwWl0xRjvUPjJ95Agbmdj2uzzIwmQKgu4oCk= +github.com/antchfx/xpath v1.3.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= @@ -789,40 +789,40 @@ github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g= github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8= -github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= -github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk= -github.com/aws/aws-sdk-go-v2/config v1.27.18/go.mod h1:0xz6cgdX55+kmppvPm2IaKzIXOheGJhAufacPJaXZ7c= -github.com/aws/aws-sdk-go-v2/credentials v1.17.18 h1:D/ALDWqK4JdY3OFgA2thcPO1c9aYTT5STS/CvnkqY1c= -github.com/aws/aws-sdk-go-v2/credentials v1.17.18/go.mod h1:JuitCWq+F5QGUrmMPsk945rop6bB57jdscu+Glozdnc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 h1:dDgptDO9dxeFkXy+tEgVkzSClHZje/6JkPW5aZyEvrQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5/go.mod h1:gjvE2KBUgUQhcv89jqxrIxH9GaKs1JbZzWejj/DaHGA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 h1:cy8ahBJuhtM8GTTSyOkfy6WVPV1IE+SS5/wfXUYuulw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9/go.mod h1:CZBXGLaJnEZI6EVNcPd7a6B5IC5cA/GkRWtu9fp3S6Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 h1:A4SYk07ef04+vxZToz9LWvAXl9LW0NClpPpMsi31cz0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9/go.mod h1:5jJcHuwDagxN+ErjQ3PU3ocf6Ylc/p9x+BLO/+X4iXw= +github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o= +github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/config v1.27.23 h1:Cr/gJEa9NAS7CDAjbnB7tHYb3aLZI2gVggfmSAasDac= +github.com/aws/aws-sdk-go-v2/config v1.27.23/go.mod h1:WMMYHqLCFu5LH05mFOF5tsq1PGEMfKbu083VKqLCd0o= +github.com/aws/aws-sdk-go-v2/credentials v1.17.23 h1:G1CfmLVoO2TdQ8z9dW+JBc/r8+MqyPQhXCafNZcXVZo= +github.com/aws/aws-sdk-go-v2/credentials v1.17.23/go.mod h1:V/DvSURn6kKgcuKEk4qwSwb/fZ2d++FFARtWSbXnLqY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.1 h1:0RiDkJO1veM6/FQ+GJcGiIhZgPwXlscX29B0zFE4Ulo= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.1/go.mod h1:gYk1NtyvkH1SxPcndDtfro3lwbiE5t0tW4eRki5YnOQ= -github.com/aws/aws-sdk-go-v2/service/ecr v1.28.5 h1:dvvTFXpWSv9+8lTNPl1EPNZL6BCUV6MgVckEMvXaOgk= -github.com/aws/aws-sdk-go-v2/service/ecr v1.28.5/go.mod h1:Ogt6AOZ/sPBlJZpVFJgOK+jGGREuo8DMjNg+O/7gpjI= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 h1:o4T+fKxA3gTMcluBNZZXE9DNaMkJuUL1O3mffCUjoJo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11/go.mod h1:84oZdJ+VjuJKs9v1UTC9NaodRZRseOXCTgku+vQJWR8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1 h1:UAxBuh0/8sFJk1qOkvOKewP5sWeWaTPDknbQz0ZkDm0= -github.com/aws/aws-sdk-go-v2/service/s3 v1.55.1/go.mod h1:hWjsYGjVuqCgfoveVcVFPXIWgz0aByzwaxKlN1StKcM= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 h1:gEYM2GSpr4YNWc6hCd5nod4+d4kd9vWIAWrmGuLdlMw= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.11/go.mod h1:gVvwPdPNYehHSP9Rs7q27U1EU+3Or2ZpXvzAYJNh63w= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 h1:iXjh3uaH3vsVcnyZX7MqCoCfcyxIrVE9iOQruRaWPrQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5/go.mod h1:5ZXesEuy/QcO0WUnt+4sDkxhdXRHTu2yG0uCSH8B6os= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 h1:M/1u4HBpwLuMtjlxuI2y6HoVLzF5e2mfxHCg7ZVMYmk= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.12/go.mod h1:kcfd+eTdEi/40FIbLq4Hif3XMXnl5b/+t/KTfLt9xIk= -github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= -github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.167.1 h1:194kHl9h0FnIZ9PTWeBiAYVX8lKYJ9OT3rZXFM79X2M= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.167.1/go.mod h1:CtLD6CPq9z9dyMxV+H6/M5d9+/ea3dO80um029GXqV0= +github.com/aws/aws-sdk-go-v2/service/ecr v1.30.1 h1:zV3FlyuyPzfyFOXKu6mJW9JBGzdtOgpdlj3va+naOD8= +github.com/aws/aws-sdk-go-v2/service/ecr v1.30.1/go.mod h1:l0zC7cSb2vAH1fr8+BRlolWT9cwlKpbRC8PjW6tyyIU= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.57.1 h1:aHPtNY87GZ214N4rShgIo+5JQz7ICrJ50i17JbueUTw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.57.1/go.mod h1:hdV0NTYd0RwV4FvNKhKUNbPLZoq9CTr/lke+3I7aCAI= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 h1:p1GahKIjyMDZtiKoIn0/jAj/TkMzfzndDv5+zi2Mhgc= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.1 h1:lCEv9f8f+zJ8kcFeAjRZsekLd/x5SAm96Cva+VbUdo8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.1/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -934,8 +934,8 @@ github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.7.17 h1:KjNnn0+tAVQHAoaWRjmdak9WlvnFR/8rU1CHHy8Rm2A= -github.com/containerd/containerd v1.7.17/go.mod h1:vK+hhT4TIv2uejlcDlbVIc8+h/BqtKLIyNrtCZol8lI= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -1019,7 +1019,6 @@ github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf 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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -1061,8 +1060,8 @@ github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284= -github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v26.1.4+incompatible h1:I8PHdc0MtxEADqYJZvhBrW9bo8gawKwwenxRM7/rLu8= +github.com/docker/cli v26.1.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= @@ -1070,8 +1069,9 @@ github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo= github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= +github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= @@ -1484,8 +1484,8 @@ github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC16 github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= 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/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= -github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= +github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= +github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= @@ -1759,8 +1759,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= -github.com/open-policy-agent/opa v0.65.0 h1:wnEU0pEk80YjFi3yoDbFTMluyNssgPI4VJNJetD9a4U= -github.com/open-policy-agent/opa v0.65.0/go.mod h1:CNoLL44LuCH1Yot/zoeZXRKFylQtCJV+oGFiP2TeeEc= +github.com/open-policy-agent/opa v0.66.0 h1:DbrvfJQja0FBRcPOB3Z/BOckocN+M4ApNWyNhSRJt0w= +github.com/open-policy-agent/opa v0.66.0/go.mod h1:EIgNnJcol7AvQR/IcWLwL13k64gHVbNAVG46b2G+/EY= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -1901,8 +1901,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= -github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/samber/lo v1.44.0 h1:5il56KxRE+GHsm1IR+sZ/6J42NODigFiqCWpSc2dybA= +github.com/samber/lo v1.44.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -1948,8 +1948,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= -github.com/spdx/tools-golang v0.5.4 h1:fRW4iz16P1ZCUtWStFqS6YiMgnK7WgfTFU/lrsYlvqY= -github.com/spdx/tools-golang v0.5.4/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= +github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= +github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= 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.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= @@ -1965,8 +1965,8 @@ github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKv github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 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= @@ -2015,8 +2015,8 @@ github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jX github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI= github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0 h1:pPz0J5Gbu7eAirpWP7QDT/v3s0zpNb/sNA8Ww/rjkoQ= github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0/go.mod h1:vqOXktUtHpTte9ilzE5enoUO8wt4FYDpZ3ARIAp28PM= -github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= -github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= +github.com/tetratelabs/wazero v1.7.3 h1:PBH5KVahrt3S2AHgEjKu4u+LlDbbk+nsGE3KLucy6Rw= +github.com/tetratelabs/wazero v1.7.3/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= @@ -2089,8 +2089,8 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= github.com/zclconf/go-cty-yaml v1.0.3 h1:og/eOQ7lvA/WWhHGFETVWNduJM7Rjsv2RRpx1sdFMLc= github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= @@ -2240,8 +2240,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2922,8 +2922,8 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2974,8 +2974,8 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.15.1 h1:22ztacHz4gMqhXNqCQ9NAg6BFWoRUryNLvnkz6OVyw0= -helm.sh/helm/v3 v3.15.1/go.mod h1:fvfoRcB8UKRUV5jrIfOTaN/pG1TPhuqSb56fjYdTKXg= +helm.sh/helm/v3 v3.15.2 h1:/3XINUFinJOBjQplGnjw92eLGpgXXp1L8chWPkCkDuw= +helm.sh/helm/v3 v3.15.2/go.mod h1:FzSIP8jDQaa6WAVg9F+OkKz7J0ZmAga4MABtTbsb9WQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -3044,8 +3044,8 @@ modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWs modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs= -modernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA= +modernc.org/ccgo/v4 v4.17.10 h1:6wrtRozgrhCxieCeJh85QsxkX/2FFrT9hdaWPlbn4Zo= +modernc.org/ccgo/v4 v4.17.10/go.mod h1:0NBHgsqTTpm9cA5z2ccErvGZmtntSM9qD2kFAs6pjXM= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= @@ -3061,8 +3061,8 @@ modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= -modernc.org/libc v1.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk= -modernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE= +modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M= +modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= @@ -3079,8 +3079,8 @@ modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/sqlite v1.30.0 h1:8YhPUs/HTnlEgErn/jSYQTwHN/ex8CjHHjg+K9iG7LM= -modernc.org/sqlite v1.30.0/go.mod h1:cgkTARJ9ugeXSNaLBPK3CqbOe7Ec7ZhWPoMFGldEYEw= +modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk= +modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= From db68d106ce9aa7368ae453592ed54c153b29a579 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Tue, 2 Jul 2024 13:36:54 +0400 Subject: [PATCH 211/352] chore: bump golangci-lint from v1.58 to v1.59 (#7077) Signed-off-by: knqyf263 --- .github/workflows/test.yaml | 2 +- internal/testutil/util.go | 4 ++-- magefiles/magefile.go | 2 +- pkg/downloader/downloader_test.go | 2 +- pkg/fanal/artifact/image/remote_sbom_test.go | 2 +- pkg/iac/scanners/terraform/scanner_test.go | 12 ++++++++---- pkg/plugin/index_test.go | 2 +- pkg/plugin/manager_unix_test.go | 6 +++--- pkg/rekortest/server.go | 10 +++++----- 9 files changed, 23 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2b21d714153f..441d37e735ab 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -37,7 +37,7 @@ jobs: id: lint uses: golangci/golangci-lint-action@v6.0.1 with: - version: v1.58 + version: v1.59 args: --verbose --out-format=line-number if: matrix.operating-system == 'ubuntu-latest-m' diff --git a/internal/testutil/util.go b/internal/testutil/util.go index 4b2252db0891..50286f9880a2 100644 --- a/internal/testutil/util.go +++ b/internal/testutil/util.go @@ -23,8 +23,8 @@ func AssertRuleFound(t *testing.T, ruleID string, results scan.Results, message meta := &m for meta != nil { assert.NotNil(t, meta.Range(), 0) - assert.Greater(t, meta.Range().GetStartLine(), 0) - assert.Greater(t, meta.Range().GetEndLine(), 0) + assert.Positive(t, meta.Range().GetStartLine()) + assert.Positive(t, meta.Range().GetEndLine()) meta = meta.Parent() } } diff --git a/magefiles/magefile.go b/magefiles/magefile.go index ca90864c2e54..b23dde046e94 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -71,7 +71,7 @@ func (Tool) Wire() error { // GolangciLint installs golangci-lint func (t Tool) GolangciLint() error { - const version = "v1.58.2" + const version = "v1.59.1" bin := filepath.Join(GOBIN, "golangci-lint") if exists(bin) && t.matchGolangciLintVersion(bin, version) { return nil diff --git a/pkg/downloader/downloader_test.go b/pkg/downloader/downloader_test.go index 80e7ef530310..0487d7384d41 100644 --- a/pkg/downloader/downloader_test.go +++ b/pkg/downloader/downloader_test.go @@ -17,7 +17,7 @@ func TestDownload(t *testing.T) { // Set up a test server with a self-signed certificate server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := w.Write([]byte("test content")) - require.NoError(t, err) + assert.NoError(t, err) })) defer server.Close() diff --git a/pkg/fanal/artifact/image/remote_sbom_test.go b/pkg/fanal/artifact/image/remote_sbom_test.go index 29fcc10f52fc..b6c144129929 100644 --- a/pkg/fanal/artifact/image/remote_sbom_test.go +++ b/pkg/fanal/artifact/image/remote_sbom_test.go @@ -181,7 +181,7 @@ func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { switch r.URL.Path { case "/v2": _, err := w.Write([]byte("ok")) - require.NoError(t, err) + assert.NoError(t, err) case "/v2/test/image/referrers/sha256:782143e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02": http.ServeFile(w, r, "testdata/index.json") case "/v2/test/image/manifests/sha256:37c89af4907fa0af078aeba12d6f18dc0c63937c010030baaaa88e958f0719a5": diff --git a/pkg/iac/scanners/terraform/scanner_test.go b/pkg/iac/scanners/terraform/scanner_test.go index cfa7cd0d0775..20e839ff91ab 100644 --- a/pkg/iac/scanners/terraform/scanner_test.go +++ b/pkg/iac/scanners/terraform/scanner_test.go @@ -80,7 +80,7 @@ func Test_OptionWithDebugWriter(t *testing.T) { _ = scanWithOptions(t, ` resource "something" "else" {} `, scannerOpts...) - require.Greater(t, buffer.Len(), 0) + require.Positive(t, buffer.Len()) } func Test_OptionWithPolicyDirs(t *testing.T) { @@ -217,9 +217,13 @@ func Test_OptionWithPolicyNamespaces(t *testing.T) { wantFailure: true, }, { - includedNamespaces: []string{"a", "users", "b"}, - policyNamespace: "users", - wantFailure: true, + includedNamespaces: []string{ + "a", + "users", + "b", + }, + policyNamespace: "users", + wantFailure: true, }, { includedNamespaces: []string{"user"}, diff --git a/pkg/plugin/index_test.go b/pkg/plugin/index_test.go index 5e3f4cd017bd..8515ddcd4e78 100644 --- a/pkg/plugin/index_test.go +++ b/pkg/plugin/index_test.go @@ -21,7 +21,7 @@ func TestManager_Update(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := w.Write([]byte(`this is index`)) - require.NoError(t, err) + assert.NoError(t, err) })) t.Cleanup(ts.Close) diff --git a/pkg/plugin/manager_unix_test.go b/pkg/plugin/manager_unix_test.go index 728d3c7cf041..f0b55f0eeda6 100644 --- a/pkg/plugin/manager_unix_test.go +++ b/pkg/plugin/manager_unix_test.go @@ -70,11 +70,11 @@ func TestManager_Install(t *testing.T) { zr := zip.NewWriter(w) switch r.URL.Path { case "/test_plugin.zip": - require.NoError(t, zr.AddFS(os.DirFS("testdata/test_plugin/test_plugin"))) + assert.NoError(t, zr.AddFS(os.DirFS("testdata/test_plugin/test_plugin"))) case "/test_nested.zip": - require.NoError(t, zr.AddFS(os.DirFS("testdata/test_plugin"))) + assert.NoError(t, zr.AddFS(os.DirFS("testdata/test_plugin"))) } - require.NoError(t, zr.Close()) + assert.NoError(t, zr.Close()) })) t.Cleanup(ts.Close) diff --git a/pkg/rekortest/server.go b/pkg/rekortest/server.go index 0e9207507492..965ca82bfa38 100644 --- a/pkg/rekortest/server.go +++ b/pkg/rekortest/server.go @@ -11,7 +11,7 @@ import ( slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" "github.com/samber/lo" "github.com/sigstore/rekor/pkg/generated/models" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" "github.com/aquasecurity/trivy/pkg/attestation" ) @@ -316,19 +316,19 @@ func NewServer(t *testing.T) *Server { case "/api/v1/index/retrieve": var params models.SearchIndex err := json.NewDecoder(r.Body).Decode(¶ms) - require.NoError(t, err) + assert.NoError(t, err) if res, ok := indexRes[params.Hash]; ok { w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(res) - require.NoError(t, err) + assert.NoError(t, err) } else { http.Error(w, "something wrong", http.StatusNotFound) } case "/api/v1/log/entries/retrieve": var params models.SearchLogQuery err := json.NewDecoder(r.Body).Decode(¶ms) - require.NoError(t, err) + assert.NoError(t, err) resEntries := models.LogEntry{} for _, uuid := range params.EntryUUIDs { @@ -341,7 +341,7 @@ func NewServer(t *testing.T) *Server { } w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode([]models.LogEntry{resEntries}) - require.NoError(t, err) + assert.NoError(t, err) } return })) From 1f5f34895823fae81bf521fc939bee743a50e304 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:48:20 +0600 Subject: [PATCH 212/352] feat: add `log.FilePath()` function for logger (#7080) --- pkg/commands/app.go | 4 ++-- pkg/dependency/parser/java/jar/parse.go | 2 +- pkg/dependency/parser/java/pom/parse.go | 2 +- pkg/fanal/analyzer/analyzer.go | 2 +- pkg/fanal/analyzer/language/analyze.go | 2 +- pkg/fanal/analyzer/language/dart/pub/pubspec.go | 2 +- pkg/fanal/analyzer/language/java/gradle/pom.go | 2 +- pkg/fanal/analyzer/language/julia/pkg/pkg.go | 2 +- pkg/fanal/analyzer/language/nodejs/license/license.go | 2 +- pkg/fanal/analyzer/language/nodejs/yarn/yarn.go | 4 ++-- pkg/fanal/analyzer/language/php/composer/composer.go | 4 ++-- pkg/fanal/analyzer/language/python/pip/pip.go | 2 +- pkg/fanal/analyzer/language/python/poetry/poetry.go | 4 ++-- pkg/fanal/analyzer/language/rust/cargo/cargo.go | 4 ++-- pkg/fanal/analyzer/licensing/license.go | 2 +- pkg/fanal/analyzer/pkg/dpkg/dpkg.go | 6 +++--- pkg/fanal/handler/unpackaged/unpackaged.go | 2 +- pkg/log/handler.go | 5 +++++ pkg/module/module.go | 2 +- pkg/parallel/walk.go | 4 ++-- pkg/result/ignore.go | 4 ++-- pkg/scanner/langpkg/scan.go | 2 +- pkg/scanner/local/scan.go | 4 ++-- pkg/utils/fsutils/fs.go | 2 +- 24 files changed, 38 insertions(+), 33 deletions(-) diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 3f88b9e9b37e..92483babb293 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -157,12 +157,12 @@ func initConfig(configFile string) error { viper.SetConfigType("yaml") if err := viper.ReadInConfig(); err != nil { if errors.Is(err, os.ErrNotExist) { - log.Debug("Config file not found", log.String("file_path", configFile)) + log.Debug("Config file not found", log.FilePath(configFile)) return nil } return xerrors.Errorf("config file %q loading error: %s", configFile, err) } - log.Info("Loaded", log.String("file_path", configFile)) + log.Info("Loaded", log.FilePath(configFile)) return nil } diff --git a/pkg/dependency/parser/java/jar/parse.go b/pkg/dependency/parser/java/jar/parse.go index 86bfb64a5ed6..8cb81d1fc48e 100644 --- a/pkg/dependency/parser/java/jar/parse.go +++ b/pkg/dependency/parser/java/jar/parse.go @@ -83,7 +83,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc } func (p *Parser) parseArtifact(filePath string, size int64, r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { - p.logger.Debug("Parsing Java artifacts...", log.String("file", filePath)) + p.logger.Debug("Parsing Java artifacts...", log.FilePath(filePath)) // Try to extract artifactId and version from the file name // e.g. spring-core-5.3.4-SNAPSHOT.jar => sprint-core, 5.3.4-SNAPSHOT diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index e905196b6fff..550c5761c6ee 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -196,7 +196,7 @@ func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ft moduleArtifact, err := p.parseModule(result.filePath, relativePath) if err != nil { p.logger.Debug("Unable to parse the module", - log.String("file_path", result.filePath), log.Err(err)) + log.FilePath(result.filePath), log.Err(err)) continue } diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go index d6defb45aae5..119a589190a2 100644 --- a/pkg/fanal/analyzer/analyzer.go +++ b/pkg/fanal/analyzer/analyzer.go @@ -413,7 +413,7 @@ func (ag AnalyzerGroup) AnalyzeFile(ctx context.Context, wg *sync.WaitGroup, lim } rc, err := opener() if errors.Is(err, fs.ErrPermission) { - ag.logger.Debug("Permission error", log.String("file_path", filePath)) + ag.logger.Debug("Permission error", log.FilePath(filePath)) break } else if err != nil { return xerrors.Errorf("unable to open %s: %w", filePath, err) diff --git a/pkg/fanal/analyzer/language/analyze.go b/pkg/fanal/analyzer/language/analyze.go index 6ecbaba65752..35d4d994c7d2 100644 --- a/pkg/fanal/analyzer/language/analyze.go +++ b/pkg/fanal/analyzer/language/analyze.go @@ -87,7 +87,7 @@ func toApplication(fileType types.LangType, filePath, libFilePath string, r xio. // Calculate the file digest when one of `spdx` formats is selected d, err := calculateDigest(r) if err != nil { - log.Warn("Unable to get checksum", log.String("file_path", filePath), log.Err(err)) + log.Warn("Unable to get checksum", log.FilePath(filePath), log.Err(err)) } deps := make(map[string][]string) diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec.go b/pkg/fanal/analyzer/language/dart/pub/pubspec.go index fc1ebc9bc586..9def336d406f 100644 --- a/pkg/fanal/analyzer/language/dart/pub/pubspec.go +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec.go @@ -114,7 +114,7 @@ func (a pubSpecLockAnalyzer) findDependsOn() (map[string][]string, error) { if err := fsutils.WalkDir(os.DirFS(dir), ".", required, func(path string, d fs.DirEntry, r io.Reader) error { id, dependsOn, err := parsePubSpecYaml(r) if err != nil { - a.logger.Debug("Unable to parse pubspec.yaml", log.String("path", path), log.Err(err)) + a.logger.Debug("Unable to parse pubspec.yaml", log.FilePath(path), log.Err(err)) return nil } if id != "" { diff --git a/pkg/fanal/analyzer/language/java/gradle/pom.go b/pkg/fanal/analyzer/language/java/gradle/pom.go index cf24e4716054..fcad9969b306 100644 --- a/pkg/fanal/analyzer/language/java/gradle/pom.go +++ b/pkg/fanal/analyzer/language/java/gradle/pom.go @@ -80,7 +80,7 @@ func (a gradleLockAnalyzer) parsePoms() (map[string]pomXML, error) { err := fsutils.WalkDir(os.DirFS(cacheDir), ".", required, func(path string, _ fs.DirEntry, r io.Reader) error { pom, err := parsePom(r, path) if err != nil { - a.logger.Debug("Unable to parse pom", log.String("file_path", path), log.Err(err)) + a.logger.Debug("Unable to parse pom", log.FilePath(path), log.Err(err)) return nil } diff --git a/pkg/fanal/analyzer/language/julia/pkg/pkg.go b/pkg/fanal/analyzer/language/julia/pkg/pkg.go index 4e69cb43c326..66d715e061b2 100644 --- a/pkg/fanal/analyzer/language/julia/pkg/pkg.go +++ b/pkg/fanal/analyzer/language/julia/pkg/pkg.go @@ -69,7 +69,7 @@ func (a juliaAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysi // Parse Project.toml alongside Manifest.toml to identify the direct dependencies. This mutates `app`. if err = a.analyzeDependencies(input.FS, filepath.Dir(path), app); err != nil { a.logger.Warn("Unable to parse file to analyze dependencies", - log.String("FILEPATH", filepath.Join(filepath.Dir(path), types.JuliaProject)), log.Err(err)) + log.FilePath(filepath.Join(filepath.Dir(path), types.JuliaProject)), log.Err(err)) } sort.Sort(app.Packages) diff --git a/pkg/fanal/analyzer/language/nodejs/license/license.go b/pkg/fanal/analyzer/language/nodejs/license/license.go index 529a8137d359..b6a0f3fb7678 100644 --- a/pkg/fanal/analyzer/language/nodejs/license/license.go +++ b/pkg/fanal/analyzer/language/nodejs/license/license.go @@ -45,7 +45,7 @@ func (l *License) Traverse(fsys fs.FS, root string) (map[string][]string, error) } l.logger.Debug("License names are missing, an attempt to find them in the license file", - log.String("file", pkgJSONPath), log.String("license_file", licenseFileName)) + log.FilePath(pkgJSONPath), log.String("license_file", licenseFileName)) licenseFilePath := path.Join(path.Dir(pkgJSONPath), licenseFileName) if findings, err := classifyLicense(licenseFilePath, l.classifierConfidenceLevel, fsys); err != nil { diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go index 70d0d1ee8951..687d147a63bc 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go @@ -81,7 +81,7 @@ func (a yarnAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis // Parse package.json alongside yarn.lock to find direct deps and mark dev deps if err = a.analyzeDependencies(input.FS, path.Dir(filePath), app); err != nil { a.logger.Warn("Unable to parse package.json to remove dev dependencies", - log.String("file_path", path.Join(path.Dir(filePath), types.NpmPkg)), log.Err(err)) + log.FilePath(path.Join(path.Dir(filePath), types.NpmPkg)), log.Err(err)) } // Fill licenses @@ -157,7 +157,7 @@ func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.App packageJsonPath := path.Join(dir, types.NpmPkg) directDeps, directDevDeps, err := a.parsePackageJsonDependencies(fsys, packageJsonPath) if errors.Is(err, fs.ErrNotExist) { - a.logger.Debug("package.json not found", log.String("path", packageJsonPath)) + a.logger.Debug("package.json not found", log.FilePath(packageJsonPath)) return nil } else if err != nil { return xerrors.Errorf("unable to parse %s: %w", dir, err) diff --git a/pkg/fanal/analyzer/language/php/composer/composer.go b/pkg/fanal/analyzer/language/php/composer/composer.go index 15d2a2e8ec27..d0b3f4466352 100644 --- a/pkg/fanal/analyzer/language/php/composer/composer.go +++ b/pkg/fanal/analyzer/language/php/composer/composer.go @@ -62,7 +62,7 @@ func (a composerAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnal // Parse composer.json alongside composer.lock to identify the direct dependencies if err = a.mergeComposerJson(input.FS, filepath.Dir(path), app); err != nil { log.Warn("Unable to parse composer.json to identify direct dependencies", - log.String("path", filepath.Join(filepath.Dir(path), types.ComposerJson)), log.Err(err)) + log.FilePath(filepath.Join(filepath.Dir(path), types.ComposerJson)), log.Err(err)) } sort.Sort(app.Packages) apps = append(apps, *app) @@ -109,7 +109,7 @@ func (a composerAnalyzer) mergeComposerJson(fsys fs.FS, dir string, app *types.A p, err := a.parseComposerJson(fsys, path) if errors.Is(err, fs.ErrNotExist) { // Assume all the packages are direct dependencies as it cannot identify them from composer.lock - log.Debug("Unable to determine the direct dependencies, composer.json not found", log.String("path", path)) + log.Debug("Unable to determine the direct dependencies, composer.json not found", log.FilePath(path)) return nil } else if err != nil { return xerrors.Errorf("unable to parse %s: %w", path, err) diff --git a/pkg/fanal/analyzer/language/python/pip/pip.go b/pkg/fanal/analyzer/language/python/pip/pip.go index 85b20a79465c..e08a90e7a70b 100644 --- a/pkg/fanal/analyzer/language/python/pip/pip.go +++ b/pkg/fanal/analyzer/language/python/pip/pip.go @@ -117,7 +117,7 @@ func (a pipLibraryAnalyzer) pkgLicense(pkgName, pkgVer, spDir string) []string { metadataPkg, _, err := a.metadataParser.Parse(metadataFile) if err != nil { - a.logger.Warn("Unable to parse METADATA file", log.String("path", metadataPath), log.Err(err)) + a.logger.Warn("Unable to parse METADATA file", log.FilePath(metadataPath), log.Err(err)) return nil } diff --git a/pkg/fanal/analyzer/language/python/poetry/poetry.go b/pkg/fanal/analyzer/language/python/poetry/poetry.go index b16ba88481c5..3b751f747621 100644 --- a/pkg/fanal/analyzer/language/python/poetry/poetry.go +++ b/pkg/fanal/analyzer/language/python/poetry/poetry.go @@ -59,7 +59,7 @@ func (a poetryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalys // Parse pyproject.toml alongside poetry.lock to identify the direct dependencies if err = a.mergePyProject(input.FS, filepath.Dir(path), app); err != nil { a.logger.Warn("Unable to parse pyproject.toml to identify direct dependencies", - log.String("path", filepath.Join(filepath.Dir(path), types.PyProject)), log.Err(err)) + log.FilePath(filepath.Join(filepath.Dir(path), types.PyProject)), log.Err(err)) } apps = append(apps, *app) @@ -97,7 +97,7 @@ func (a poetryAnalyzer) mergePyProject(fsys fs.FS, dir string, app *types.Applic p, err := a.parsePyProject(fsys, path) if errors.Is(err, fs.ErrNotExist) { // Assume all the packages are direct dependencies as it cannot identify them from poetry.lock - a.logger.Debug("pyproject.toml not found", log.String("path", path)) + a.logger.Debug("pyproject.toml not found", log.FilePath(path)) return nil } else if err != nil { return xerrors.Errorf("unable to parse %s: %w", path, err) diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo.go b/pkg/fanal/analyzer/language/rust/cargo/cargo.go index bd54273552c6..ab436bf95fe2 100644 --- a/pkg/fanal/analyzer/language/rust/cargo/cargo.go +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo.go @@ -72,7 +72,7 @@ func (a cargoAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysi // Parse Cargo.toml alongside Cargo.lock to identify the direct dependencies if err = a.removeDevDependencies(input.FS, path.Dir(filePath), app); err != nil { a.logger.Warn("Unable to parse Cargo.toml q to identify direct dependencies", - log.String("path", path.Join(path.Dir(filePath), types.CargoToml)), log.Err(err)) + log.FilePath(path.Join(path.Dir(filePath), types.CargoToml)), log.Err(err)) } sort.Sort(app.Packages) apps = append(apps, *app) @@ -109,7 +109,7 @@ func (a cargoAnalyzer) removeDevDependencies(fsys fs.FS, dir string, app *types. cargoTOMLPath := path.Join(dir, types.CargoToml) directDeps, err := a.parseRootCargoTOML(fsys, cargoTOMLPath) if errors.Is(err, fs.ErrNotExist) { - a.logger.Debug("Cargo.toml not found", log.String("path", cargoTOMLPath)) + a.logger.Debug("Cargo.toml not found", log.FilePath(cargoTOMLPath)) return nil } else if err != nil { return xerrors.Errorf("unable to parse %s: %w", cargoTOMLPath, err) diff --git a/pkg/fanal/analyzer/licensing/license.go b/pkg/fanal/analyzer/licensing/license.go index ceef1d90cecc..e48e08b2814f 100644 --- a/pkg/fanal/analyzer/licensing/license.go +++ b/pkg/fanal/analyzer/licensing/license.go @@ -92,7 +92,7 @@ func newLicenseFileAnalyzer() *licenseFileAnalyzer { func (a *licenseFileAnalyzer) Analyze(ctx context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { ctx = log.WithContextPrefix(ctx, "license") - log.DebugContext(ctx, "License scanning", log.String("file_path", input.FilePath)) + log.DebugContext(ctx, "License scanning", log.FilePath(input.FilePath)) // need files to be text based, readable files readable, err := isHumanReadable(input.Content, input.Info.Size()) diff --git a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go index 1d8435ecb686..9002af2949ab 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go +++ b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go @@ -61,7 +61,7 @@ func (a dpkgAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis // parse `available` file to get digest for packages digests, err := a.parseDpkgAvailable(input.FS) if err != nil { - a.logger.Debug("Unable to parse the available file", log.String("file", availableFile), log.Err(err)) + a.logger.Debug("Unable to parse the available file", log.FilePath(availableFile), log.Err(err)) } required := func(path string, d fs.DirEntry) bool { @@ -169,7 +169,7 @@ func (a dpkgAnalyzer) parseDpkgAvailable(fsys fs.FS) (map[string]digest.Digest, for scanner.Scan() { header, err := scanner.Header() if !errors.Is(err, io.EOF) && err != nil { - a.logger.Warn("Parse error", log.String("file", availableFile), log.Err(err)) + a.logger.Warn("Parse error", log.FilePath(availableFile), log.Err(err)) continue } name, version, checksum := header.Get("Package"), header.Get("Version"), header.Get("SHA256") @@ -195,7 +195,7 @@ func (a dpkgAnalyzer) parseDpkgStatus(filePath string, r io.Reader, digests map[ for scanner.Scan() { header, err := scanner.Header() if !errors.Is(err, io.EOF) && err != nil { - a.logger.Warn("Parse error", log.String("file", filePath), log.Err(err)) + a.logger.Warn("Parse error", log.FilePath(filePath), log.Err(err)) continue } diff --git a/pkg/fanal/handler/unpackaged/unpackaged.go b/pkg/fanal/handler/unpackaged/unpackaged.go index ed380de49cb0..ba0a459a073d 100644 --- a/pkg/fanal/handler/unpackaged/unpackaged.go +++ b/pkg/fanal/handler/unpackaged/unpackaged.go @@ -70,7 +70,7 @@ func (h unpackagedHook) Handle(ctx context.Context, res *analyzer.AnalysisResult } if len(bom.Applications) > 0 { - h.logger.Info("Found SBOM attestation in Rekor", log.String("file_path", filePath)) + h.logger.Info("Found SBOM attestation in Rekor", log.FilePath(filePath)) // Take the first app since this SBOM should contain a single application. app := bom.Applications[0] app.FilePath = filePath // Use the original file path rather than the one in the SBOM. diff --git a/pkg/log/handler.go b/pkg/log/handler.go index b2474cbee3c5..d5e75a77049c 100644 --- a/pkg/log/handler.go +++ b/pkg/log/handler.go @@ -278,6 +278,11 @@ func Prefix(prefix string) slog.Attr { return slog.Any(prefixKey, logPrefix("["+prefix+"] ")) } +// FilePath returns an Attr that represents a filePath. +func FilePath(filePath string) slog.Attr { + return String("file_path", filePath) +} + func isLogPrefix(a slog.Attr) bool { _, ok := a.Value.Any().(logPrefix) return ok diff --git a/pkg/module/module.go b/pkg/module/module.go index 69eb35df2c0b..0f0e0e84a450 100644 --- a/pkg/module/module.go +++ b/pkg/module/module.go @@ -445,7 +445,7 @@ func (m *wasmModule) Required(filePath string, _ os.FileInfo) bool { func (m *wasmModule) Analyze(ctx context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { filePath := "/" + filepath.ToSlash(input.FilePath) - log.Debug("Module analyzing...", log.String("module", m.name), log.String("file_path", filePath)) + log.Debug("Module analyzing...", log.String("module", m.name), log.FilePath(filePath)) // Wasm module instances are not Goroutine safe, so we take look here since Analyze might be called concurrently. // TODO: This is temporary solution and we could improve the Analyze performance by having module instance pool. diff --git a/pkg/parallel/walk.go b/pkg/parallel/walk.go index a5e2523f8920..be569e5bc0e8 100644 --- a/pkg/parallel/walk.go +++ b/pkg/parallel/walk.go @@ -39,7 +39,7 @@ func WalkDir[T any](ctx context.Context, fsys fs.FS, root string, parallel int, if err != nil { return err } else if info.Size() == 0 { - log.Debug("Skip the empty file", log.String("file_path", path)) + log.Debug("Skip the empty file", log.FilePath(path)) return nil } @@ -105,7 +105,7 @@ func walk[T any](ctx context.Context, fsys fs.FS, path string, c chan T, onFile } res, err := onFile(path, info, rsa) if err != nil { - log.Debug("Walk error", log.String("file_path", path), log.Err(err)) + log.Debug("Walk error", log.FilePath(path), log.Err(err)) return nil } diff --git a/pkg/result/ignore.go b/pkg/result/ignore.go index 941680b3086f..dbd1cab83db9 100644 --- a/pkg/result/ignore.go +++ b/pkg/result/ignore.go @@ -223,7 +223,7 @@ func parseIgnoreYAML(ignoreFile string) (IgnoreConfig, error) { return IgnoreConfig{}, xerrors.Errorf("file open error: %w", err) } defer f.Close() - log.Debug("Found an ignore yaml", log.String("path", ignoreFile)) + log.Debug("Found an ignore yaml", log.FilePath(ignoreFile)) // Parse the YAML content var ignoreConfig IgnoreConfig @@ -239,7 +239,7 @@ func parseIgnore(ignoreFile string) (IgnoreFindings, error) { return nil, xerrors.Errorf("file open error: %w", err) } defer f.Close() - log.Debug("Found an ignore file", log.String("path", ignoreFile)) + log.Debug("Found an ignore file", log.FilePath(ignoreFile)) var ignoredFindings IgnoreFindings scanner := bufio.NewScanner(f) diff --git a/pkg/scanner/langpkg/scan.go b/pkg/scanner/langpkg/scan.go index d3923033e3f0..df6068b3ddd3 100644 --- a/pkg/scanner/langpkg/scan.go +++ b/pkg/scanner/langpkg/scan.go @@ -85,7 +85,7 @@ func (s *scanner) scanVulnerabilities(ctx context.Context, app ftypes.Applicatio printedTypes[app.Type] = struct{}{} } - log.DebugContext(ctx, "Scanning packages for vulnerabilities", log.String("file_path", app.FilePath)) + log.DebugContext(ctx, "Scanning packages for vulnerabilities", log.FilePath(app.FilePath)) vulns, err := library.Detect(ctx, app.Type, app.Packages) if err != nil { return nil, xerrors.Errorf("failed vulnerability detection of libraries: %w", err) diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 475fc1540086..18ba53eac7a0 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -198,7 +198,7 @@ func (s Scanner) MisconfsToResults(misconfs []ftypes.Misconfiguration) types.Res log.Info("Detected config files", log.Int("num", len(misconfs))) var results types.Results for _, misconf := range misconfs { - log.Debug("Scanned config file", log.String("path", misconf.FilePath)) + log.Debug("Scanned config file", log.FilePath(misconf.FilePath)) var detected []types.DetectedMisconfiguration @@ -237,7 +237,7 @@ func (s Scanner) secretsToResults(secrets []ftypes.Secret, options types.ScanOpt var results types.Results for _, secret := range secrets { - log.Debug("Secret file", log.String("path", secret.FilePath)) + log.Debug("Secret file", log.FilePath(secret.FilePath)) results = append(results, types.Result{ Target: secret.FilePath, diff --git a/pkg/utils/fsutils/fs.go b/pkg/utils/fsutils/fs.go index b8efbf1cbc60..c15302deed1d 100644 --- a/pkg/utils/fsutils/fs.go +++ b/pkg/utils/fsutils/fs.go @@ -88,7 +88,7 @@ func WalkDir(fsys fs.FS, root string, required WalkDirRequiredFunc, fn WalkDirFu defer f.Close() if err = fn(path, d, f); err != nil { - log.Debug("Walk error", log.String("file_path", path), log.Err(err)) + log.Debug("Walk error", log.FilePath(path), log.Err(err)) } return nil }) From 266d9b1f4bd2e89f1d58a16698f755a1734c644e Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 3 Jul 2024 10:24:33 +0600 Subject: [PATCH 213/352] refactor(sbom): add sbom prefix + filepaths for decode log messages (#7074) --- pkg/fanal/analyzer/sbom/sbom.go | 6 ++-- pkg/fanal/artifact/sbom/sbom.go | 5 ++-- pkg/fanal/handler/unpackaged/unpackaged.go | 3 +- pkg/sbom/cyclonedx/unmarshal_test.go | 3 +- pkg/sbom/io/decode.go | 33 +++++++++++----------- pkg/sbom/sbom.go | 5 ++-- pkg/sbom/spdx/unmarshal_test.go | 3 +- 7 files changed, 33 insertions(+), 25 deletions(-) diff --git a/pkg/fanal/analyzer/sbom/sbom.go b/pkg/fanal/analyzer/sbom/sbom.go index 6392f0200da3..55768cff3a9b 100644 --- a/pkg/fanal/analyzer/sbom/sbom.go +++ b/pkg/fanal/analyzer/sbom/sbom.go @@ -10,6 +10,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/sbom" "github.com/aquasecurity/trivy/pkg/types" ) @@ -29,14 +30,15 @@ var requiredSuffixes = []string{ type sbomAnalyzer struct{} -func (a sbomAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { +func (a sbomAnalyzer) Analyze(ctx context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { // Format auto-detection format, err := sbom.DetectFormat(input.Content) if err != nil { return nil, xerrors.Errorf("failed to detect SBOM format: %w", err) } - bom, err := sbom.Decode(input.Content, format) + ctx = log.WithContextAttrs(ctx, log.FilePath(input.FilePath)) + bom, err := sbom.Decode(ctx, input.Content, format) if err != nil { return nil, xerrors.Errorf("SBOM decode error: %w", err) } diff --git a/pkg/fanal/artifact/sbom/sbom.go b/pkg/fanal/artifact/sbom/sbom.go index a5b646c18889..782a32d9b1f8 100644 --- a/pkg/fanal/artifact/sbom/sbom.go +++ b/pkg/fanal/artifact/sbom/sbom.go @@ -37,7 +37,7 @@ func NewArtifact(filePath string, c cache.ArtifactCache, opt artifact.Option) (a }, nil } -func (a Artifact) Inspect(_ context.Context) (artifact.Reference, error) { +func (a Artifact) Inspect(ctx context.Context) (artifact.Reference, error) { f, err := os.Open(a.filePath) if err != nil { return artifact.Reference{}, xerrors.Errorf("failed to open sbom file error: %w", err) @@ -51,7 +51,8 @@ func (a Artifact) Inspect(_ context.Context) (artifact.Reference, error) { } log.Info("Detected SBOM format", log.String("format", string(format))) - bom, err := sbom.Decode(f, format) + ctx = log.WithContextAttrs(ctx, log.FilePath(a.filePath)) + bom, err := sbom.Decode(ctx, f, format) if err != nil { return artifact.Reference{}, xerrors.Errorf("SBOM decode error: %w", err) } diff --git a/pkg/fanal/handler/unpackaged/unpackaged.go b/pkg/fanal/handler/unpackaged/unpackaged.go index ba0a459a073d..07925d9596f0 100644 --- a/pkg/fanal/handler/unpackaged/unpackaged.go +++ b/pkg/fanal/handler/unpackaged/unpackaged.go @@ -64,7 +64,8 @@ func (h unpackagedHook) Handle(ctx context.Context, res *analyzer.AnalysisResult } // Parse the fetched SBOM - bom, err := sbom.Decode(bytes.NewReader(raw), format) + ctx = log.WithContextAttrs(ctx, log.FilePath(filePath)) + bom, err := sbom.Decode(ctx, bytes.NewReader(raw), format) if err != nil { return err } diff --git a/pkg/sbom/cyclonedx/unmarshal_test.go b/pkg/sbom/cyclonedx/unmarshal_test.go index 834e886c46dd..7008efb8e9eb 100644 --- a/pkg/sbom/cyclonedx/unmarshal_test.go +++ b/pkg/sbom/cyclonedx/unmarshal_test.go @@ -1,6 +1,7 @@ package cyclonedx_test import ( + "context" "encoding/json" "os" "testing" @@ -757,7 +758,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { require.NoError(t, err) var got types.SBOM - err = sbomio.NewDecoder(cdx.BOM).Decode(&got) + err = sbomio.NewDecoder(cdx.BOM).Decode(context.Background(), &got) require.NoError(t, err) got.BOM = nil diff --git a/pkg/sbom/io/decode.go b/pkg/sbom/io/decode.go index 7544cf215a3e..379e1af32d52 100644 --- a/pkg/sbom/io/decode.go +++ b/pkg/sbom/io/decode.go @@ -1,6 +1,7 @@ package io import ( + "context" "errors" "slices" "sort" @@ -46,14 +47,14 @@ func NewDecoder(bom *core.BOM) *Decoder { } } -func (m *Decoder) Decode(sbom *types.SBOM) error { +func (m *Decoder) Decode(ctx context.Context, sbom *types.SBOM) error { // Parse the root component if err := m.decodeRoot(sbom); err != nil { return xerrors.Errorf("failed to decode root component: %w", err) } // Parse all components - if err := m.decodeComponents(sbom); err != nil { + if err := m.decodeComponents(ctx, sbom); err != nil { return xerrors.Errorf("failed to decode components: %w", err) } @@ -67,7 +68,7 @@ func (m *Decoder) Decode(sbom *types.SBOM) error { m.addLangPkgs(sbom) // Add remaining packages - if err := m.addOrphanPkgs(sbom); err != nil { + if err := m.addOrphanPkgs(ctx, sbom); err != nil { return xerrors.Errorf("failed to aggregate packages: %w", err) } @@ -109,9 +110,9 @@ func (m *Decoder) decodeRoot(s *types.SBOM) error { return nil } -func (m *Decoder) decodeComponents(sbom *types.SBOM) error { +func (m *Decoder) decodeComponents(ctx context.Context, sbom *types.SBOM) error { onceMultiOSWarn := sync.OnceFunc(func() { - m.logger.Warn("Multiple OS components are not supported, taking the first one and ignoring the rest") + m.logger.WarnContext(ctx, "Multiple OS components are not supported, taking the first one and ignoring the rest") }) for id, c := range m.bom.Components() { @@ -136,7 +137,7 @@ func (m *Decoder) decodeComponents(sbom *types.SBOM) error { // Third-party SBOMs may contain packages in types other than "Library" if c.Type == core.TypeLibrary || c.PkgIdentifier.PURL != nil { - pkg, err := m.decodeLibrary(c) + pkg, err := m.decodeLibrary(ctx, c) if errors.Is(err, ErrUnsupportedType) || errors.Is(err, ErrPURLEmpty) { continue } else if err != nil { @@ -183,17 +184,17 @@ func (m *Decoder) decodeApplication(c *core.Component) *ftypes.Application { return &app } -func (m *Decoder) decodeLibrary(c *core.Component) (*ftypes.Package, error) { +func (m *Decoder) decodeLibrary(ctx context.Context, c *core.Component) (*ftypes.Package, error) { p := (*purl.PackageURL)(c.PkgIdentifier.PURL) if p == nil { - log.Debug("Skipping a component without PURL", + m.logger.DebugContext(ctx, "Skipping a component without PURL", log.String("name", c.Name), log.String("version", c.Version)) return nil, ErrPURLEmpty } pkg := p.Package() if p.Class() == types.ClassUnknown { - log.Debug("Skipping a component with an unsupported type", + m.logger.DebugContext(ctx, "Skipping a component with an unsupported type", log.String("name", c.Name), log.String("version", c.Version), log.String("type", p.Type)) return nil, ErrUnsupportedType } @@ -240,7 +241,7 @@ func (m *Decoder) decodeLibrary(c *core.Component) (*ftypes.Package, error) { } if p.Class() == types.ClassOSPkg { - m.fillSrcPkg(c, pkg) + m.fillSrcPkg(ctx, c, pkg) } return pkg, nil @@ -278,11 +279,11 @@ func (m *Decoder) pkgName(pkg *ftypes.Package, c *core.Component) string { return c.Name } -func (m *Decoder) fillSrcPkg(c *core.Component, pkg *ftypes.Package) { +func (m *Decoder) fillSrcPkg(ctx context.Context, c *core.Component, pkg *ftypes.Package) { if c.SrcName != "" && pkg.SrcName == "" { pkg.SrcName = c.SrcName } - m.parseSrcVersion(pkg, c.SrcVersion) + m.parseSrcVersion(ctx, pkg, c.SrcVersion) // Source info was added from component or properties if pkg.SrcName != "" && pkg.SrcVersion != "" { @@ -305,7 +306,7 @@ func (m *Decoder) fillSrcPkg(c *core.Component, pkg *ftypes.Package) { } // parseSrcVersion parses the version of the source package. -func (m *Decoder) parseSrcVersion(pkg *ftypes.Package, ver string) { +func (m *Decoder) parseSrcVersion(ctx context.Context, pkg *ftypes.Package, ver string) { if ver == "" { return } @@ -318,7 +319,7 @@ func (m *Decoder) parseSrcVersion(pkg *ftypes.Package, ver string) { case packageurl.TypeDebian: v, err := debver.NewVersion(ver) if err != nil { - log.Debug("Failed to parse Debian version", log.Err(err)) + m.logger.DebugContext(ctx, "Failed to parse Debian version", log.Err(err)) return } pkg.SrcEpoch = v.Epoch() @@ -361,7 +362,7 @@ func (m *Decoder) addLangPkgs(sbom *types.SBOM) { // addOrphanPkgs adds orphan packages. // Orphan packages are packages that are not related to any components. -func (m *Decoder) addOrphanPkgs(sbom *types.SBOM) error { +func (m *Decoder) addOrphanPkgs(ctx context.Context, sbom *types.SBOM) error { osPkgMap := make(map[string]ftypes.Packages) langPkgMap := make(map[ftypes.LangType]ftypes.Packages) for _, pkg := range m.pkgs { @@ -382,7 +383,7 @@ func (m *Decoder) addOrphanPkgs(sbom *types.SBOM) error { // Add OS packages only when OS is detected. for _, pkgs := range osPkgMap { if sbom.Metadata.OS == nil || !sbom.Metadata.OS.Detected() { - log.Warn("Ignore the OS package as no OS is detected.") + m.logger.WarnContext(ctx, "Ignore the OS package as no OS is detected.") break } diff --git a/pkg/sbom/sbom.go b/pkg/sbom/sbom.go index 26ae9f9158d1..6a943e3f2822 100644 --- a/pkg/sbom/sbom.go +++ b/pkg/sbom/sbom.go @@ -2,6 +2,7 @@ package sbom import ( "bufio" + "context" "encoding/json" "encoding/xml" "io" @@ -180,7 +181,7 @@ func decodeAttestCycloneDXJSONFormat(r io.ReadSeeker) (Format, bool) { return FormatAttestCycloneDXJSON, true } -func Decode(f io.Reader, format Format) (types.SBOM, error) { +func Decode(ctx context.Context, f io.Reader, format Format) (types.SBOM, error) { var ( v any bom = core.NewBOM(core.Options{}) @@ -227,7 +228,7 @@ func Decode(f io.Reader, format Format) (types.SBOM, error) { } var sbom types.SBOM - if err := sbomio.NewDecoder(bom).Decode(&sbom); err != nil { + if err := sbomio.NewDecoder(bom).Decode(ctx, &sbom); err != nil { return types.SBOM{}, xerrors.Errorf("failed to decode: %w", err) } diff --git a/pkg/sbom/spdx/unmarshal_test.go b/pkg/sbom/spdx/unmarshal_test.go index c68d9f32e654..7efcaa87a29e 100644 --- a/pkg/sbom/spdx/unmarshal_test.go +++ b/pkg/sbom/spdx/unmarshal_test.go @@ -1,6 +1,7 @@ package spdx_test import ( + "context" "encoding/json" "os" "sort" @@ -357,7 +358,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { } var got types.SBOM - err = sbomio.NewDecoder(v.BOM).Decode(&got) + err = sbomio.NewDecoder(v.BOM).Decode(context.Background(), &got) require.NoError(t, err) // Not compare BOM From 03ac93dc208f1b40896f3fa11fa1d45293176dca Mon Sep 17 00:00:00 2001 From: Oscar Blanco Date: Wed, 3 Jul 2024 14:13:24 +0200 Subject: [PATCH 214/352] feat(nodejs): add license parser to pnpm analyser (#7036) Co-authored-by: DmitriyLewen --- docs/docs/coverage/language/nodejs.md | 3 +- integration/repo_test.go | 5 +- .../node_modules/jquery/package.json | 108 ++++++++++++++ .../node_modules/lodash/package.json | 17 +++ .../fixtures/repo/pnpm/pnpm-lock.yaml | 20 ++- integration/testdata/pnpm.json.golden | 34 ++++- .../analyzer/language/nodejs/pnpm/pnpm.go | 122 +++++++++++++-- .../language/nodejs/pnpm/pnpm_test.go | 139 +++++++++++++++--- .../ms@2.1.3/node_modules/ms/package.json | 38 +++++ .../nodejs/pnpm/testdata/happy/pnpm-lock.yaml | 16 ++ .../testdata/no-node_modules/pnpm-lock.yaml | 51 +++++++ .../nodejs/pnpm/testdata/pnpm-lock.yaml | 13 -- .../nodejs/pnpm/testdata/sad/pnpm-lock.yaml | 1 + 13 files changed, 507 insertions(+), 60 deletions(-) create mode 100644 integration/testdata/fixtures/repo/pnpm/node_modules/.pnpm/jquery@3.3.9/node_modules/jquery/package.json create mode 100644 integration/testdata/fixtures/repo/pnpm/node_modules/.pnpm/lodash@4.17.4/node_modules/lodash/package.json create mode 100644 pkg/fanal/analyzer/language/nodejs/pnpm/testdata/happy/node_modules/.pnpm/ms@2.1.3/node_modules/ms/package.json create mode 100644 pkg/fanal/analyzer/language/nodejs/pnpm/testdata/happy/pnpm-lock.yaml create mode 100644 pkg/fanal/analyzer/language/nodejs/pnpm/testdata/no-node_modules/pnpm-lock.yaml delete mode 100644 pkg/fanal/analyzer/language/nodejs/pnpm/testdata/pnpm-lock.yaml create mode 100644 pkg/fanal/analyzer/language/nodejs/pnpm/testdata/sad/pnpm-lock.yaml diff --git a/docs/docs/coverage/language/nodejs.md b/docs/docs/coverage/language/nodejs.md index ded9ea144535..623f54d0ca6c 100644 --- a/docs/docs/coverage/language/nodejs.md +++ b/docs/docs/coverage/language/nodejs.md @@ -8,7 +8,7 @@ The following scanners are supported. |----------|:----:|:-------------:|:-------:| | npm | ✓ | ✓ | ✓ | | Yarn | ✓ | ✓ | ✓ | -| pnpm | ✓ | ✓ | - | +| pnpm | ✓ | ✓ | ✓ | | Bun | ✓ | ✓ | ✓ | The following table provides an outline of the features Trivy offers. @@ -54,6 +54,7 @@ By default, Trivy doesn't report development dependencies. Use the `--include-de ### pnpm Trivy parses `pnpm-lock.yaml`, then finds production dependencies and builds a [tree][dependency-graph] of dependencies with vulnerabilities. +To identify licenses, you need to download dependencies to `node_modules` beforehand. Trivy analyzes `node_modules` for licenses. #### lock file v9 version Trivy supports `Dev` field for `pnpm-lock.yaml` v9 or later. Use the `--include-dev-deps` flag to include the developer's dependencies in the result. diff --git a/integration/repo_test.go b/integration/repo_test.go index e07b48b950b7..b95b4f4f461d 100644 --- a/integration/repo_test.go +++ b/integration/repo_test.go @@ -105,8 +105,9 @@ func TestRepository(t *testing.T) { { name: "pnpm", args: args{ - scanner: types.VulnerabilityScanner, - input: "testdata/fixtures/repo/pnpm", + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/pnpm", + listAllPkgs: true, }, golden: "testdata/pnpm.json.golden", }, diff --git a/integration/testdata/fixtures/repo/pnpm/node_modules/.pnpm/jquery@3.3.9/node_modules/jquery/package.json b/integration/testdata/fixtures/repo/pnpm/node_modules/.pnpm/jquery@3.3.9/node_modules/jquery/package.json new file mode 100644 index 000000000000..54dd7c03a0bd --- /dev/null +++ b/integration/testdata/fixtures/repo/pnpm/node_modules/.pnpm/jquery@3.3.9/node_modules/jquery/package.json @@ -0,0 +1,108 @@ +{ + "name": "jquery", + "title": "jQuery", + "description": "JavaScript library for DOM operations", + "version": "3.3.9", + "main": "dist/jquery.js", + "homepage": "https://jquery.com", + "author": { + "name": "JS Foundation and other contributors", + "url": "https://github.com/jquery/jquery/blob/3.3.1/AUTHORS.txt" + }, + "repository": { + "type": "git", + "url": "https://github.com/jquery/jquery.git" + }, + "keywords": [ + "jquery", + "javascript", + "browser", + "library" + ], + "bugs": { + "url": "https://github.com/jquery/jquery/issues" + }, + "license": "MIT", + "dependencies": {}, + "devDependencies": { + "babel-core": "7.0.0-beta.0", + "babel-plugin-transform-es2015-for-of": "7.0.0-beta.0", + "commitplease": "2.7.10", + "core-js": "2.4.1", + "eslint-config-jquery": "1.0.1", + "grunt": "1.0.1", + "grunt-babel": "7.0.0", + "grunt-cli": "1.2.0", + "grunt-compare-size": "0.4.2", + "grunt-contrib-uglify": "3.0.1", + "grunt-contrib-watch": "1.0.0", + "grunt-eslint": "20.0.0", + "grunt-git-authors": "3.2.0", + "grunt-jsonlint": "1.1.0", + "grunt-karma": "2.0.0", + "grunt-newer": "1.3.0", + "grunt-npmcopy": "0.1.0", + "gzip-js": "0.3.2", + "husky": "0.14.3", + "insight": "0.8.4", + "jsdom": "5.6.1", + "karma": "1.7.0", + "karma-browserstack-launcher": "1.3.0", + "karma-chrome-launcher": "2.2.0", + "karma-firefox-launcher": "1.0.1", + "karma-qunit": "1.2.1", + "load-grunt-tasks": "3.5.2", + "native-promise-only": "0.8.1", + "promises-aplus-tests": "2.1.2", + "q": "1.5.0", + "qunit-assert-step": "1.0.3", + "qunitjs": "1.23.1", + "raw-body": "2.2.0", + "requirejs": "2.3.3", + "sinon": "2.3.7", + "sizzle": "2.3.3", + "strip-json-comments": "2.0.1", + "testswarm": "1.1.0", + "uglify-js": "3.3.4" + }, + "scripts": { + "build": "npm install && grunt", + "start": "grunt watch", + "test:browserless": "grunt && grunt test:slow", + "test:browser": "grunt && grunt karma:main", + "test": "grunt && grunt test:slow && grunt karma:main", + "jenkins": "npm run test:browserless", + "precommit": "grunt lint:newer qunit_fixture", + "commitmsg": "node node_modules/commitplease" + }, + "commitplease": { + "nohook": true, + "components": [ + "Docs", + "Tests", + "Build", + "Support", + "Release", + "Core", + "Ajax", + "Attributes", + "Callbacks", + "CSS", + "Data", + "Deferred", + "Deprecated", + "Dimensions", + "Effects", + "Event", + "Manipulation", + "Offset", + "Queue", + "Selector", + "Serialize", + "Traversing", + "Wrap" + ], + "markerPattern": "^((clos|fix|resolv)(e[sd]|ing))|^(refs?)", + "ticketPattern": "^((Closes|Fixes) ([a-zA-Z]{2,}-)[0-9]+)|^(Refs? [^#])" + } +} \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/pnpm/node_modules/.pnpm/lodash@4.17.4/node_modules/lodash/package.json b/integration/testdata/fixtures/repo/pnpm/node_modules/.pnpm/lodash@4.17.4/node_modules/lodash/package.json new file mode 100644 index 000000000000..3fa8669cb337 --- /dev/null +++ b/integration/testdata/fixtures/repo/pnpm/node_modules/.pnpm/lodash@4.17.4/node_modules/lodash/package.json @@ -0,0 +1,17 @@ +{ + "name": "lodash", + "version": "4.17.4", + "description": "Lodash modular utilities.", + "keywords": "modules, stdlib, util", + "homepage": "https://lodash.com/", + "repository": "lodash/lodash", + "icon": "https://lodash.com/icon.svg", + "license": "MIT", + "main": "lodash.js", + "author": "John-David Dalton (http://allyoucanleet.com/)", + "contributors": [ + "John-David Dalton (http://allyoucanleet.com/)", + "Mathias Bynens (https://mathiasbynens.be/)" + ], + "scripts": { "test": "echo \"See https://travis-ci.org/lodash/lodash-cli for testing details.\"" } +} \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/pnpm/pnpm-lock.yaml b/integration/testdata/fixtures/repo/pnpm/pnpm-lock.yaml index be652ed3a442..f2b129bc448d 100644 --- a/integration/testdata/fixtures/repo/pnpm/pnpm-lock.yaml +++ b/integration/testdata/fixtures/repo/pnpm/pnpm-lock.yaml @@ -1,19 +1,23 @@ -lockfileVersion: 5.4 +lockfileVersion: '6.0' -specifiers: - jquery: 3.3.9 - lodash: 4.17.4 +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false dependencies: - jquery: 3.3.9 - lodash: 4.17.4 + jquery: + specifier: ^3.3.9 + version: 3.3.9 + lodash: + specifier: ^4.17.4 + version: 4.17.4 packages: - /jquery/3.3.9: + /jquery@3.3.9: resolution: {integrity: sha512-ggRCXln9zEqv6OqAGXFEcshF5dSBvCkzj6Gm2gzuR5fWawaX8t7cxKVkkygKODrDAzKdoYw3l/e3pm3vlT4IbQ==} dev: false - /lodash/4.17.4: + /lodash@4.17.4: resolution: {integrity: sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=} dev: false diff --git a/integration/testdata/pnpm.json.golden b/integration/testdata/pnpm.json.golden index 1ae0c6400db0..3edd379d10cb 100644 --- a/integration/testdata/pnpm.json.golden +++ b/integration/testdata/pnpm.json.golden @@ -20,6 +20,36 @@ "Target": "pnpm-lock.yaml", "Class": "lang-pkgs", "Type": "pnpm", + "Packages": [ + { + "ID": "jquery@3.3.9", + "Name": "jquery", + "Identifier": { + "PURL": "pkg:npm/jquery@3.3.9", + "UID": "53ca18565a4b6a47" + }, + "Version": "3.3.9", + "Licenses": [ + "MIT" + ], + "Relationship": "direct", + "Layer": {} + }, + { + "ID": "lodash@4.17.4", + "Name": "lodash", + "Identifier": { + "PURL": "pkg:npm/lodash@4.17.4", + "UID": "31eadfcf58a6b128" + }, + "Version": "4.17.4", + "Licenses": [ + "MIT" + ], + "Relationship": "direct", + "Layer": {} + } + ], "Vulnerabilities": [ { "VulnerabilityID": "CVE-2019-11358", @@ -27,7 +57,7 @@ "PkgName": "jquery", "PkgIdentifier": { "PURL": "pkg:npm/jquery@3.3.9", - "UID": "d002d4ebac4ee286" + "UID": "53ca18565a4b6a47" }, "InstalledVersion": "3.3.9", "FixedVersion": "3.4.0", @@ -160,7 +190,7 @@ "PkgName": "lodash", "PkgIdentifier": { "PURL": "pkg:npm/lodash@4.17.4", - "UID": "68507e8301071074" + "UID": "31eadfcf58a6b128" }, "InstalledVersion": "4.17.4", "FixedVersion": "4.17.12", diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm.go b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm.go index 594e5e02a82e..9c8f51a38265 100644 --- a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm.go +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm.go @@ -2,45 +2,141 @@ package pnpm import ( "context" + "errors" + "io" + "io/fs" "os" + "path" "path/filepath" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/pnpm" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/fanal/utils" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" + xpath "github.com/aquasecurity/trivy/pkg/x/path" ) func init() { - analyzer.RegisterAnalyzer(&pnpmLibraryAnalyzer{}) + analyzer.RegisterPostAnalyzer(analyzer.TypePnpm, newPnpmAnalyzer) } -const version = 1 +const version = 2 -var requiredFiles = []string{types.PnpmLock} +type pnpmAnalyzer struct { + logger *log.Logger + packageJsonParser *packagejson.Parser + lockParser language.Parser +} + +func newPnpmAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &pnpmAnalyzer{ + logger: log.WithPrefix("pnpm"), + packageJsonParser: packagejson.NewParser(), + lockParser: pnpm.NewParser(), + }, nil +} + +func (a pnpmAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + var apps []types.Application + + required := func(path string, d fs.DirEntry) bool { + return filepath.Base(path) == types.PnpmLock + } -type pnpmLibraryAnalyzer struct{} + err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, d fs.DirEntry, r io.Reader) error { + // Find licenses + licenses, err := a.findLicenses(input.FS, filePath) + if err != nil { + a.logger.Error("Unable to collect licenses", log.Err(err)) + licenses = make(map[string][]string) + } -func (a pnpmLibraryAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { - res, err := language.Analyze(types.Pnpm, input.FilePath, input.Content, pnpm.NewParser()) + // Parse pnpm-lock.yaml + app, err := language.Parse(types.Pnpm, filePath, r, a.lockParser) + if err != nil { + return xerrors.Errorf("parse error: %w", err) + } else if app == nil { + return nil + } + + // Fill licenses + for i, lib := range app.Packages { + if l, ok := licenses[lib.ID]; ok { + app.Packages[i].Licenses = l + } + } + + apps = append(apps, *app) + + return nil + }) if err != nil { - return nil, xerrors.Errorf("unable to parse %s: %w", input.FilePath, err) + return nil, xerrors.Errorf("pnpm walk error: %w", err) } - return res, nil + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil } -func (a pnpmLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool { +func (a pnpmAnalyzer) Required(filePath string, _ os.FileInfo) bool { fileName := filepath.Base(filePath) - return utils.StringInSlice(fileName, requiredFiles) + // Don't save pnpm-lock.yaml from the `node_modules` directory to avoid duplication and mistakes. + if fileName == types.PnpmLock && !xpath.Contains(filePath, "node_modules") { + return true + } + + // Save package.json files only from the `node_modules` directory. + // Required to search for licenses. + if fileName == types.NpmPkg && xpath.Contains(filePath, "node_modules") { + return true + } + + return false } -func (a pnpmLibraryAnalyzer) Type() analyzer.Type { +func (a pnpmAnalyzer) Type() analyzer.Type { return analyzer.TypePnpm } -func (a pnpmLibraryAnalyzer) Version() int { +func (a pnpmAnalyzer) Version() int { return version } + +func (a pnpmAnalyzer) findLicenses(fsys fs.FS, lockPath string) (map[string][]string, error) { + dir := path.Dir(lockPath) + root := path.Join(dir, "node_modules") + if _, err := fs.Stat(fsys, root); errors.Is(err, fs.ErrNotExist) { + a.logger.Info(`To collect the license information of packages, "pnpm install" needs to be performed beforehand`, + log.String("dir", root)) + return nil, nil + } + + // Parse package.json + required := func(path string, _ fs.DirEntry) bool { + return filepath.Base(path) == types.NpmPkg + } + + // Traverse node_modules dir and find licenses + // Note that fs.FS is always slashed regardless of the platform, + // and path.Join should be used rather than filepath.Join. + licenses := make(map[string][]string) + err := fsutils.WalkDir(fsys, root, required, func(filePath string, d fs.DirEntry, r io.Reader) error { + pkg, err := a.packageJsonParser.Parse(r) + if err != nil { + return xerrors.Errorf("unable to parse %q: %w", filePath, err) + } + + licenses[pkg.ID] = pkg.Licenses + return nil + }) + if err != nil { + return nil, xerrors.Errorf("walk error: %w", err) + } + return licenses, nil +} diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go index 4fe9fd75874e..b0623b3f609e 100644 --- a/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/pnpm_test.go @@ -3,6 +3,7 @@ package pnpm import ( "context" "os" + "sort" "testing" "github.com/stretchr/testify/assert" @@ -14,24 +15,24 @@ import ( func Test_pnpmPkgLibraryAnalyzer_Analyze(t *testing.T) { tests := []struct { - name string - inputFile string - want *analyzer.AnalysisResult - wantErr string + name string + dir string + want *analyzer.AnalysisResult }{ { - name: "happy path", - inputFile: "testdata/pnpm-lock.yaml", + name: "with node_modules", + dir: "testdata/happy", want: &analyzer.AnalysisResult{ Applications: []types.Application{ { Type: types.Pnpm, - FilePath: "testdata/pnpm-lock.yaml", + FilePath: "pnpm-lock.yaml", Packages: types.Packages{ { - ID: "lodash@4.17.21", - Name: "lodash", - Version: "4.17.21", + ID: "ms@2.1.3", + Name: "ms", + Version: "2.1.3", + Licenses: []string{"MIT"}, Relationship: types.RelationshipDirect, }, }, @@ -39,27 +40,123 @@ func Test_pnpmPkgLibraryAnalyzer_Analyze(t *testing.T) { }, }, }, + { + name: "without node_modules", + dir: "testdata/no-node_modules", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Pnpm, + FilePath: "pnpm-lock.yaml", + Packages: types.Packages{ + { + ID: "@babel/parser@7.24.7", + Name: "@babel/parser", + Version: "7.24.7", + Relationship: types.RelationshipDirect, + DependsOn: []string{"@babel/types@7.24.7"}, + }, + { + ID: "ms@2.1.3", + Name: "ms", + Version: "2.1.3", + Relationship: types.RelationshipDirect, + }, + { + ID: "@babel/helper-string-parser@7.24.7", + Name: "@babel/helper-string-parser", + Version: "7.24.7", + Relationship: types.RelationshipIndirect, + Indirect: true, + }, + { + ID: "@babel/helper-validator-identifier@7.24.7", + Name: "@babel/helper-validator-identifier", + Version: "7.24.7", + Relationship: types.RelationshipIndirect, + Indirect: true, + }, + { + ID: "@babel/types@7.24.7", + Name: "@babel/types", + Version: "7.24.7", + Relationship: types.RelationshipIndirect, + Indirect: true, + DependsOn: []string{ + "@babel/helper-string-parser@7.24.7", + "@babel/helper-validator-identifier@7.24.7", + "to-fast-properties@2.0.0", + }, + }, + { + ID: "to-fast-properties@2.0.0", + Name: "to-fast-properties", + Version: "2.0.0", + Relationship: types.RelationshipIndirect, + Indirect: true, + }, + }, + }, + }, + }, + }, + { + name: "sad path", + dir: "testdata/sad", + want: &analyzer.AnalysisResult{}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - f, err := os.Open(tt.inputFile) + a, err := newPnpmAnalyzer(analyzer.AnalyzerOptions{}) require.NoError(t, err) - defer f.Close() - a := pnpmLibraryAnalyzer{} - ctx := context.Background() - got, err := a.Analyze(ctx, analyzer.AnalysisInput{ - FilePath: tt.inputFile, - Content: f, + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), }) - if tt.wantErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr) - return + require.NoError(t, err) + if len(got.Applications) > 0 { + sort.Sort(got.Applications[0].Packages) } + assert.Equal(t, tt.want, got) + }) + } +} +func Test_pnpmPkgLibraryAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "lock file", + filePath: "pnpm/pnpm-lock.yaml", + want: true, + }, + { + name: "lock file in node_modules", + filePath: "pnpm/node_modules/html2canvas/pnpm-lock.yaml", + want: false, + }, + { + name: "package.json in node_modules", + filePath: "pnpm/node_modules/ms/package.json", + want: true, + }, + { + name: "sad path", + filePath: "pnpm/package.json", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newPnpmAnalyzer(analyzer.AnalyzerOptions{}) require.NoError(t, err) + + got := a.Required(tt.filePath, nil) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/happy/node_modules/.pnpm/ms@2.1.3/node_modules/ms/package.json b/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/happy/node_modules/.pnpm/ms@2.1.3/node_modules/ms/package.json new file mode 100644 index 000000000000..49971890df8e --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/happy/node_modules/.pnpm/ms@2.1.3/node_modules/ms/package.json @@ -0,0 +1,38 @@ +{ + "name": "ms", + "version": "2.1.3", + "description": "Tiny millisecond conversion utility", + "repository": "vercel/ms", + "main": "./index", + "files": [ + "index.js" + ], + "scripts": { + "precommit": "lint-staged", + "lint": "eslint lib/* bin/*", + "test": "mocha tests.js" + }, + "eslintConfig": { + "extends": "eslint:recommended", + "env": { + "node": true, + "es6": true + } + }, + "lint-staged": { + "*.js": [ + "npm run lint", + "prettier --single-quote --write", + "git add" + ] + }, + "license": "MIT", + "devDependencies": { + "eslint": "4.18.2", + "expect.js": "0.3.1", + "husky": "0.14.3", + "lint-staged": "5.0.0", + "mocha": "4.0.1", + "prettier": "2.0.5" + } +} diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/happy/pnpm-lock.yaml b/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/happy/pnpm-lock.yaml new file mode 100644 index 000000000000..50dfa1c43db3 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/happy/pnpm-lock.yaml @@ -0,0 +1,16 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + ms: + specifier: ^2.1.3 + version: 2.1.3 + +packages: + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/no-node_modules/pnpm-lock.yaml b/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/no-node_modules/pnpm-lock.yaml new file mode 100644 index 000000000000..31997b0756cf --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/no-node_modules/pnpm-lock.yaml @@ -0,0 +1,51 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + '@babel/parser': + specifier: ^7.24.7 + version: 7.24.7 + ms: + specifier: ^2.1.3 + version: 2.1.3 + +packages: + + /@babel/helper-string-parser@7.24.7: + resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-validator-identifier@7.24.7: + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/parser@7.24.7: + resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.24.7 + dev: false + + /@babel/types@7.24.7: + resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + dev: false + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false + + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: false diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/pnpm-lock.yaml b/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/pnpm-lock.yaml deleted file mode 100644 index a319ff8e7d4f..000000000000 --- a/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/pnpm-lock.yaml +++ /dev/null @@ -1,13 +0,0 @@ -lockfileVersion: 5.4 - -specifiers: - lodash: ^4.17.21 - -dependencies: - lodash: 4.17.21 - -packages: - - /lodash/4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: false \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/sad/pnpm-lock.yaml b/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/sad/pnpm-lock.yaml new file mode 100644 index 000000000000..80c4ed2eb6fa --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/pnpm/testdata/sad/pnpm-lock.yaml @@ -0,0 +1 @@ +{broken} \ No newline at end of file From cb89fbb124a2ea7c2c49adcf27b8a2bdebc895f7 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:19:30 +0600 Subject: [PATCH 215/352] refactor(secret): add warning about large files (#7085) --- pkg/fanal/analyzer/secret/secret.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/fanal/analyzer/secret/secret.go b/pkg/fanal/analyzer/secret/secret.go index cad32e00ad8f..e26caaea5401 100644 --- a/pkg/fanal/analyzer/secret/secret.go +++ b/pkg/fanal/analyzer/secret/secret.go @@ -17,6 +17,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/secret" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/utils" + "github.com/aquasecurity/trivy/pkg/log" ) // To make sure SecretAnalyzer implements analyzer.Initializer @@ -165,6 +166,9 @@ func (a *SecretAnalyzer) Required(filePath string, fi os.FileInfo) bool { return false } + if size := fi.Size(); size > 10485760 { // 10MB + log.WithPrefix("secret").Warn("The size of the scanned file is too large. It is recommended to use `--skip-files` for this file to avoid high memory consumption.", log.FilePath(filePath), log.Int64("size (MB)", size/1048576)) + } return true } From a7a304d53e1ce230f881c28c4f35885774cf3b9a Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 4 Jul 2024 15:52:23 +0600 Subject: [PATCH 216/352] fix(java): use `go-mvn-version` to remove `Package` duplicates (#7088) Co-authored-by: Teppei Fukuda --- pkg/dependency/parser/java/jar/parse.go | 27 ++++++++-- pkg/dependency/parser/java/jar/parse_test.go | 48 +++++++++++++++--- ...nal.jar => io.quarkus.gizmo.gizmo-1.1.jar} | Bin 882367 -> 884288 bytes 3 files changed, 62 insertions(+), 13 deletions(-) rename pkg/dependency/parser/java/jar/testdata/{io.quarkus.gizmo.gizmo-1.1.1.Final.jar => io.quarkus.gizmo.gizmo-1.1.jar} (99%) diff --git a/pkg/dependency/parser/java/jar/parse.go b/pkg/dependency/parser/java/jar/parse.go index 8cb81d1fc48e..be767e0cf248 100644 --- a/pkg/dependency/parser/java/jar/parse.go +++ b/pkg/dependency/parser/java/jar/parse.go @@ -6,15 +6,15 @@ import ( "crypto/sha1" // nolint:gosec "encoding/hex" "errors" - "fmt" "io" "os" "path" "path/filepath" "regexp" + "slices" "strings" - "github.com/samber/lo" + mavenversion "github.com/masahiro331/go-mvn-version" "golang.org/x/xerrors" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -439,7 +439,24 @@ func (m manifest) determineVersion() (string, error) { } func removePackageDuplicates(pkgs []ftypes.Package) []ftypes.Package { - return lo.UniqBy(pkgs, func(pkg ftypes.Package) string { - return fmt.Sprintf("%s::%s::%s", pkg.Name, pkg.Version, pkg.FilePath) - }) + // name::filePath => versions + var uniq = make(map[string][]mavenversion.Version) + var uniqPkgs []ftypes.Package + for _, pkg := range pkgs { + uniqID := pkg.Name + "::" + pkg.FilePath + // err is always nil + // cf. https://github.com/masahiro331/go-mvn-version/blob/d3157d602a08806ad94464c443e0cef1370694a1/version.go#L20-L25 + pkgVer, _ := mavenversion.NewVersion(pkg.Version) + savedVers, ok := uniq[uniqID] + if !ok || !slices.ContainsFunc(savedVers, func(v mavenversion.Version) bool { + // There are times when patch `0` is omitted. + // So we can't compare versions just as strings + // for example `2.17.0` and `2.17` must be equal + return v.Equal(pkgVer) + }) { + uniq[uniqID] = append(uniq[uniqID], pkgVer) + uniqPkgs = append(uniqPkgs, pkg) + } + } + return uniqPkgs } diff --git a/pkg/dependency/parser/java/jar/parse_test.go b/pkg/dependency/parser/java/jar/parse_test.go index 20f9c682be3b..958b2d0667a5 100644 --- a/pkg/dependency/parser/java/jar/parse_test.go +++ b/pkg/dependency/parser/java/jar/parse_test.go @@ -168,22 +168,54 @@ var ( }, } - // manually created + // Manually created. + // Files of `io.quarkus.gizmo.gizmo-1.1.jar` (gizmo:1.1.0 (from sha1)): + //├── bar + //│ ├── bar + //│ │ └── pom.properties (jackson-databind:2.13.4) + //│ └── foo + //│ └── pom.properties (jackson-databind:2.12.3) + //├── foo + //│ ├── bar + //│ │ └── pom.properties (jackson-databind:2.12.3) + //│ └── foo + //│ └── pom.properties (jackson-databind:2.13.4) + //├── jars + //│ ├── log4j-1.2.16.jar (log4j:1.2.16) + //│ └── log4j-1.2.17.jar (log4j:1.2.17) + //└── META-INF + // ├── INDEX.LIST + // ├── MANIFEST.MF + // └── maven + // └── io.quarkus.gizmo + // └── gizmo + // ├── pom.properties (gizmo:1.1) + // └── pom.xml wantDuplicatesJar = []ftypes.Package{ { Name: "io.quarkus.gizmo:gizmo", - Version: "1.1.1.Final", - FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar", + Version: "1.1", + FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.jar", }, { Name: "log4j:log4j", Version: "1.2.16", - FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar/jars/log4j-1.2.16.jar", + FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.jar/jars/log4j-1.2.16.jar", }, { Name: "log4j:log4j", Version: "1.2.17", - FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar/jars/log4j-1.2.17.jar", + FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.jar/jars/log4j-1.2.17.jar", + }, + { + Name: "com.fasterxml.jackson.core:jackson-databind", + Version: "2.12.3", + FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.jar", + }, + { + Name: "com.fasterxml.jackson.core:jackson-databind", + Version: "2.13.4", + FilePath: "testdata/io.quarkus.gizmo.gizmo-1.1.jar", }, } ) @@ -251,7 +283,7 @@ func TestParse(t *testing.T) { }, { name: "duplicate libraries", - file: "testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar", + file: "testdata/io.quarkus.gizmo.gizmo-1.1.jar", want: wantDuplicatesJar, }, } @@ -277,13 +309,13 @@ func TestParse(t *testing.T) { } case strings.Contains(r.URL.Query().Get("q"), "Gizmo"): res.Response.NumFound = 0 - case strings.Contains(r.URL.Query().Get("q"), "85d30c06026afd9f5be26da3194d4698c447a904"): + case strings.Contains(r.URL.Query().Get("q"), "1c78bbc4d8c58b9af8eee82b84f2c26ec48e9a2b"): res.Response.Docs = []doc{ { ID: "io.quarkus.gizmo.gizmo", GroupID: "io.quarkus.gizmo", ArtifactID: "gizmo", - Version: "1.1.1.Final", + Version: "1.1.0", }, } case strings.Contains(r.URL.Query().Get("q"), "heuristic"): diff --git a/pkg/dependency/parser/java/jar/testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar b/pkg/dependency/parser/java/jar/testdata/io.quarkus.gizmo.gizmo-1.1.jar similarity index 99% rename from pkg/dependency/parser/java/jar/testdata/io.quarkus.gizmo.gizmo-1.1.1.Final.jar rename to pkg/dependency/parser/java/jar/testdata/io.quarkus.gizmo.gizmo-1.1.jar index 080b15ba5e40ec9431d97d41fd2cd9afa29be811..3b82fae55ec99e93c949375061ebf7af82aa96bd 100644 GIT binary patch delta 2596 zcmdo0*7U$1(}ott7N!>F7M2#)7Pc1l7LFFq7OocV7M?A<<{9-KuoJ-S+y-uqLU451)7(nDefM>7oVO`O3+jtiLoI8ejWJ%`BGq zs2ek_DRS|9tGB+(CIu#^9~93lay&5ikH(j!-@Obrm#h|AZ1FYyGQ-brECw&W2AulETq4o7 zer@JSE{m&PoN0di*yj(KiX!#)Ang+sX(K0=$m{-5PErub$ zo0)|RsFi`?TgsCNFat`k0Ao8Tu}B|m%zEdxGzHf-bYl>@Wm8dgb3k>c<>y08_z5(D z%>~7TdQd6>86g6+l7m4BXhdDFMyw~0=L^JQNJbRo=js&{7!J(cf5T-?WyARu ztUjSDGFN~6X|*HnonDrXt*eBxPh#lKbC&9p*hJ5E+r6!C;LS9D>99z3fmOK5|7-7^ z-Hx|b^XcPH)F79^g9ntTS@FfYInZyuYRooXXnq5kP*9JVsJVb117S4JAt!1I;=O5fh-~_@r{X+?_tVC!CE2NZ1bK7*@QeG{_ zlhZ9qd2N`#W%7bbAjRo3fP%~>Szw;}^fw@01%#)k3vvU@Qp_R@YAMj_Esbi$U;`ip zT0PhWSPC0KxrGzFUTfglxXz~$VRc0K#YQ<1)y=D zw1Cw(T%{YbN8ON2Lr?QSvq5EDX+3JqitJX*T!`${`%uFnc@k(GsJO!8RLsH(Y+5}7 zupI!56va}A&#@Ir7gocGiJ)CTv*96%7D3SJ0+e=e70bxJ{sA?LSYKlnL&yY S*+6++6bP$7#IK@@7W0e delta 663 zcmX@`$8`T&(}ott7N!>F7M2#)7Pc1l7LFFq7OocV7M?A<<{9;20Y0IZ3thH{FfuTt zFflNQFfcIqx`sIFdiuHP`#So0y1532==r+!PB@)+$UwmL`@~QD{~9g$eOTf|SN!r= zxYj8#+H#w4Op>*)jNIQ}Qj3CIB167;81LFwe9Tzc>&YI&dy}sBUf^mLU|VOr($a8F ziJlB(ZTGsBZJi5}fZC&vR7d;KbZPUN2`J6P| zAs!zTpOwDHexGW%&vglv^@pNnb6i#0V-L)oViu)D*Gd;0d_n9|<*_)YVx^j>57!7tn zY%*Oii&uGiN-?j<^eM%>`Wy-`rp=r_<1p*?b>+NfjCLZR2;<;jz!r@_?O?E_Q8$AZ zYot!Ms^ryT+&n$ClGldmLFV+2mAp#RkAlSHrhlm94Q5ONQJT|pt9ZFJkgY;-BFr8j jy`@p07-WjZ^sHiDKCu9ARyL3 Date: Mon, 8 Jul 2024 08:18:59 +0300 Subject: [PATCH 217/352] fix: ignore nodes when listing permission is not allowed (#7107) Signed-off-by: chenk --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bae728a67f96..4d2866dcf306 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/aquasecurity/trivy-checks v0.13.0 github.com/aquasecurity/trivy-db v0.0.0-20240701103400-8e907467e9ab github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 - github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240627095026-cf9d48837f6d + github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b github.com/aws/aws-sdk-go-v2 v1.30.1 github.com/aws/aws-sdk-go-v2/config v1.27.23 github.com/aws/aws-sdk-go-v2/credentials v1.17.23 diff --git a/go.sum b/go.sum index 606c3be8e5fc..d039b51ebbd1 100644 --- a/go.sum +++ b/go.sum @@ -775,8 +775,8 @@ github.com/aquasecurity/trivy-db v0.0.0-20240701103400-8e907467e9ab h1:EmpLGFgRJ github.com/aquasecurity/trivy-db v0.0.0-20240701103400-8e907467e9ab/go.mod h1:f+wSW9D5txv8S+tw4D4WNOibaUJYwvNnQuQlGQ8gO6c= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240627095026-cf9d48837f6d h1:z5Ug+gqNjgHzCo7rmv6wKTmyJ8E3bAVEU2AASo3740s= -github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240627095026-cf9d48837f6d/go.mod h1:HOhrqoyIeTxpwnKr1EyWtQ+rt2XahV8b0UDBrRpSfEQ= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b h1:h7gsIzHyrxpQnayOuQI0kX7+8rVcqhV6G5bM3KVFyJU= +github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b/go.mod h1:HOhrqoyIeTxpwnKr1EyWtQ+rt2XahV8b0UDBrRpSfEQ= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= From ec3e0ca1472058d0cdd79e887d4783aab791a064 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 07:30:57 +0400 Subject: [PATCH 218/352] chore(deps): bump the aws group with 4 updates (#7115) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 4d2866dcf306..8715db94813e 100644 --- a/go.mod +++ b/go.mod @@ -30,11 +30,11 @@ require ( github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b github.com/aws/aws-sdk-go-v2 v1.30.1 - github.com/aws/aws-sdk-go-v2/config v1.27.23 - github.com/aws/aws-sdk-go-v2/credentials v1.17.23 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.167.1 + github.com/aws/aws-sdk-go-v2/config v1.27.24 + github.com/aws/aws-sdk-go-v2/credentials v1.17.24 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.168.0 github.com/aws/aws-sdk-go-v2/service/ecr v1.30.1 - github.com/aws/aws-sdk-go-v2/service/s3 v1.57.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0 github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect github.com/aws/smithy-go v1.20.3 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c @@ -176,7 +176,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/briandowns/spinner v1.23.0 // indirect diff --git a/go.sum b/go.sum index d039b51ebbd1..549eb3305577 100644 --- a/go.sum +++ b/go.sum @@ -791,10 +791,10 @@ github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o= github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= -github.com/aws/aws-sdk-go-v2/config v1.27.23 h1:Cr/gJEa9NAS7CDAjbnB7tHYb3aLZI2gVggfmSAasDac= -github.com/aws/aws-sdk-go-v2/config v1.27.23/go.mod h1:WMMYHqLCFu5LH05mFOF5tsq1PGEMfKbu083VKqLCd0o= -github.com/aws/aws-sdk-go-v2/credentials v1.17.23 h1:G1CfmLVoO2TdQ8z9dW+JBc/r8+MqyPQhXCafNZcXVZo= -github.com/aws/aws-sdk-go-v2/credentials v1.17.23/go.mod h1:V/DvSURn6kKgcuKEk4qwSwb/fZ2d++FFARtWSbXnLqY= +github.com/aws/aws-sdk-go-v2/config v1.27.24 h1:NM9XicZ5o1CBU/MZaHwFtimRpWx9ohAUAqkG6AqSqPo= +github.com/aws/aws-sdk-go-v2/config v1.27.24/go.mod h1:aXzi6QJTuQRVVusAO8/NxpdTeTyr/wRcybdDtfUwJSs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.24 h1:YclAsrnb1/GTQNt2nzv+756Iw4mF8AOzcDfweWwwm/M= +github.com/aws/aws-sdk-go-v2/credentials v1.17.24/go.mod h1:Hld7tmnAkoBQdTMNYZGzztzKRdA4fCdn9L83LOoigac= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc= @@ -805,20 +805,20 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.167.1 h1:194kHl9h0FnIZ9PTWeBiAYVX8lKYJ9OT3rZXFM79X2M= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.167.1/go.mod h1:CtLD6CPq9z9dyMxV+H6/M5d9+/ea3dO80um029GXqV0= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.168.0 h1:xOPq0agGC1WMZvFpSZCKEjDVAQnLPZJZGvjuPVF2t9M= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.168.0/go.mod h1:CtLD6CPq9z9dyMxV+H6/M5d9+/ea3dO80um029GXqV0= github.com/aws/aws-sdk-go-v2/service/ecr v1.30.1 h1:zV3FlyuyPzfyFOXKu6mJW9JBGzdtOgpdlj3va+naOD8= github.com/aws/aws-sdk-go-v2/service/ecr v1.30.1/go.mod h1:l0zC7cSb2vAH1fr8+BRlolWT9cwlKpbRC8PjW6tyyIU= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.57.1 h1:aHPtNY87GZ214N4rShgIo+5JQz7ICrJ50i17JbueUTw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.57.1/go.mod h1:hdV0NTYd0RwV4FvNKhKUNbPLZoq9CTr/lke+3I7aCAI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0 h1:4rhV0Hn+bf8IAIUphRX1moBcEvKJipCPmswMCl6Q5mw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0/go.mod h1:hdV0NTYd0RwV4FvNKhKUNbPLZoq9CTr/lke+3I7aCAI= github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 h1:p1GahKIjyMDZtiKoIn0/jAj/TkMzfzndDv5+zi2Mhgc= github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.1 h1:lCEv9f8f+zJ8kcFeAjRZsekLd/x5SAm96Cva+VbUdo8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.1/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 h1:ORnrOK0C4WmYV/uYt3koHEWBLYsRDwk2Np+eEoyV4Z0= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4= github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ= github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ= github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= From dc68a662a701980d6529f61a65006f1e4728a3e5 Mon Sep 17 00:00:00 2001 From: Colm O hEigeartaigh Date: Tue, 9 Jul 2024 04:43:08 +0100 Subject: [PATCH 219/352] fix: Add dependencyManagement exclusions to the child exclusions (#6969) Co-authored-by: DmitriyLewen --- pkg/dependency/parser/java/pom/parse_test.go | 54 +++++++++++++++++++ pkg/dependency/parser/java/pom/pom.go | 5 +- .../child/pom.xml | 38 +++++++++++++ .../pom.xml | 37 +++++++++++++ .../3.0.0/example-exclusions-3.0.0.pom | 27 ++++++++++ 5 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 pkg/dependency/parser/java/pom/testdata/exclusions-parent-dependency-management/child/pom.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/exclusions-parent-dependency-management/pom.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-exclusions/3.0.0/example-exclusions-3.0.0.pom diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go index 15740d599eb9..934085d5d536 100644 --- a/pkg/dependency/parser/java/pom/parse_test.go +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -979,6 +979,60 @@ func TestPom_Parse(t *testing.T) { }, }, }, + // ➜ mvn dependency:tree + // ... + // [INFO] + // [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ child --- + // [INFO] com.example:child:jar:3.0.0 + // [INFO] \- org.example:example-exclusions:jar:3.0.0:compile + // [INFO] \- org.example:example-nested:jar:3.3.3:compile + // [INFO] ------------------------------------------------------------------------ + { + name: "exclusions in child and parent dependency management", + inputFile: filepath.Join("testdata", "exclusions-parent-dependency-management", "child", "pom.xml"), + local: true, + want: []ftypes.Package{ + { + ID: "com.example:child:3.0.0", + Name: "com.example:child", + Version: "3.0.0", + Licenses: []string{"Apache 2.0"}, + Relationship: ftypes.RelationshipRoot, + }, + { + ID: "org.example:example-exclusions:3.0.0", + Name: "org.example:example-exclusions", + Version: "3.0.0", + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ + { + StartLine: 26, + EndLine: 35, + }, + }, + }, + { + ID: "org.example:example-nested:3.3.3", + Name: "org.example:example-nested", + Version: "3.3.3", + Relationship: ftypes.RelationshipIndirect, + }, + }, + wantDeps: []ftypes.Dependency{ + { + ID: "com.example:child:3.0.0", + DependsOn: []string{ + "org.example:example-exclusions:3.0.0", + }, + }, + { + ID: "org.example:example-exclusions:3.0.0", + DependsOn: []string{ + "org.example:example-nested:3.3.3", + }, + }, + }, + }, { name: "exclusions with wildcards", inputFile: filepath.Join("testdata", "wildcard-exclusions", "pom.xml"), diff --git a/pkg/dependency/parser/java/pom/pom.go b/pkg/dependency/parser/java/pom/pom.go index 3a0170d36811..889d107c3c6c 100644 --- a/pkg/dependency/parser/java/pom/pom.go +++ b/pkg/dependency/parser/java/pom/pom.go @@ -266,9 +266,8 @@ func (d pomDependency) Resolve(props map[string]string, depManagement, rootDepMa if !dep.Optional { dep.Optional = managed.Optional } - if len(dep.Exclusions.Exclusion) == 0 { - dep.Exclusions = managed.Exclusions - } + // `mvn` always merges exceptions for pom and parent + dep.Exclusions.Exclusion = append(dep.Exclusions.Exclusion, managed.Exclusions.Exclusion...) } return dep } diff --git a/pkg/dependency/parser/java/pom/testdata/exclusions-parent-dependency-management/child/pom.xml b/pkg/dependency/parser/java/pom/testdata/exclusions-parent-dependency-management/child/pom.xml new file mode 100644 index 000000000000..967033369b92 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/exclusions-parent-dependency-management/child/pom.xml @@ -0,0 +1,38 @@ + + 4.0.0 + + child + 3.0.0 + + child + Child + + + com.example + parent + 2.0.0 + + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + org.example + example-exclusions + + + org.example + example-dependency + + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/exclusions-parent-dependency-management/pom.xml b/pkg/dependency/parser/java/pom/testdata/exclusions-parent-dependency-management/pom.xml new file mode 100644 index 000000000000..d5093a29ab59 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/exclusions-parent-dependency-management/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + + com.example + parent + 2.0.0 + + pom + parent + Parent + + + + Apache 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + + + + + + org.example + example-exclusions + 3.0.0 + + + org.example + example-dependency2 + + + + + + + diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-exclusions/3.0.0/example-exclusions-3.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-exclusions/3.0.0/example-exclusions-3.0.0.pom new file mode 100644 index 000000000000..57f908f362f5 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-exclusions/3.0.0/example-exclusions-3.0.0.pom @@ -0,0 +1,27 @@ + + 4.0.0 + + org.example + example-exclusions + 3.0.0 + + + + org.example + example-dependency + 1.2.3 + + + org.example + example-dependency2 + 2.3.4 + + + org.example + example-nested + 3.3.3 + + + + From a64993e83a78baecdc55b3b20549c9bec4f975e0 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:24:42 +0600 Subject: [PATCH 220/352] test: add missing advisory details for integration tests database (#7122) --- integration/testdata/conan.json.golden | 31 ++++++++++++++++++- .../testdata/fixtures/db/vulnerability.yaml | 4 +-- .../testdata/spring4shell-jre11.json.golden | 4 ++- .../testdata/spring4shell-jre8.json.golden | 4 ++- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/integration/testdata/conan.json.golden b/integration/testdata/conan.json.golden index 1aac990b6304..d34caa079428 100644 --- a/integration/testdata/conan.json.golden +++ b/integration/testdata/conan.json.golden @@ -171,7 +171,36 @@ "FixedVersion": "8.45", "Status": "fixed", "Layer": {}, - "Severity": "UNKNOWN" + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-14155", + "Title": "pcre: Integer overflow when parsing callout numeric arguments", + "Description": "libpcre in PCRE before 8.44 allows an integer overflow via a large number after a (?C substring.", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-190" + ], + "VendorSeverity": { + "alma": 1, + "nvd": 2 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V3Score": 5.3 + } + }, + "References": [ + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14155", + "https://nvd.nist.gov/vuln/detail/CVE-2020-14155" + ], + "PublishedDate": "2020-06-15T17:15:00Z", + "LastModifiedDate": "2022-04-28T15:06:00Z" } ] } diff --git a/integration/testdata/fixtures/db/vulnerability.yaml b/integration/testdata/fixtures/db/vulnerability.yaml index 1cc7882214be..5b66fd7b9acc 100644 --- a/integration/testdata/fixtures/db/vulnerability.yaml +++ b/integration/testdata/fixtures/db/vulnerability.yaml @@ -1364,7 +1364,7 @@ V3Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H" V3Score: 8.1 References: - - "https://github.com/advisories/GHSA-36p3-wjmg-h94x", + - "https://github.com/advisories/GHSA-36p3-wjmg-h94x" PublishedDate: "2022-04-01T23:15:00Z" LastModifiedDate: "2022-05-19T14:21:00Z" - key: CVE-2020-14155 @@ -1387,7 +1387,7 @@ V3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L" V3Score: 5.3 References: - - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14155", + - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14155" - "https://nvd.nist.gov/vuln/detail/CVE-2020-14155" PublishedDate: "2020-06-15T17:15:00Z" LastModifiedDate: "2022-04-28T15:06:00Z" diff --git a/integration/testdata/spring4shell-jre11.json.golden b/integration/testdata/spring4shell-jre11.json.golden index 98c49376cf53..927db3df8d96 100644 --- a/integration/testdata/spring4shell-jre11.json.golden +++ b/integration/testdata/spring4shell-jre11.json.golden @@ -245,7 +245,9 @@ }, "References": [ "https://github.com/advisories/GHSA-36p3-wjmg-h94x" - ] + ], + "PublishedDate": "2022-04-01T23:15:00Z", + "LastModifiedDate": "2022-05-19T14:21:00Z" } ] }, diff --git a/integration/testdata/spring4shell-jre8.json.golden b/integration/testdata/spring4shell-jre8.json.golden index 45da22c7f39c..b41aa8878c5a 100644 --- a/integration/testdata/spring4shell-jre8.json.golden +++ b/integration/testdata/spring4shell-jre8.json.golden @@ -245,7 +245,9 @@ }, "References": [ "https://github.com/advisories/GHSA-36p3-wjmg-h94x" - ] + ], + "PublishedDate": "2022-04-01T23:15:00Z", + "LastModifiedDate": "2022-05-19T14:21:00Z" } ] }, From 17b5dbfa12180414b87859c6c46bfe6cc5ecf7ba Mon Sep 17 00:00:00 2001 From: Marcus Meissner Date: Tue, 9 Jul 2024 06:25:39 +0200 Subject: [PATCH 221/352] feat: add openSUSE tumbleweed detection and scanning (#6965) Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Co-authored-by: DmitriyLewen --- docs/docs/coverage/os/index.md | 1 + docs/docs/coverage/os/suse.md | 5 +- integration/client_server_test.go | 7 + integration/docker_engine_test.go | 6 + integration/standalone_tar_test.go | 8 + .../testdata/fixtures/db/opensuse.yaml | 9 +- .../testdata/fixtures/db/vulnerability.yaml | 11 +- .../testdata/opensuse-tumbleweed.json.golden | 94 + pkg/detector/ospkg/detect.go | 31 +- pkg/detector/ospkg/suse/suse.go | 9 + pkg/detector/ospkg/suse/suse_test.go | 50 + .../suse/testdata/fixtures/data-source.yaml | 7 +- .../suse/testdata/fixtures/tumbleweed.yaml | 7 + pkg/fanal/test/integration/library_test.go | 9 + .../packages/opensuse-tumbleweed.json.golden | 3472 +++++++++++++++++ 15 files changed, 3706 insertions(+), 20 deletions(-) create mode 100644 integration/testdata/opensuse-tumbleweed.json.golden create mode 100644 pkg/detector/ospkg/suse/testdata/fixtures/tumbleweed.yaml create mode 100644 pkg/fanal/test/integration/testdata/goldens/packages/opensuse-tumbleweed.json.golden diff --git a/docs/docs/coverage/os/index.md b/docs/docs/coverage/os/index.md index a8d2670d7d65..49982b1b2d69 100644 --- a/docs/docs/coverage/os/index.md +++ b/docs/docs/coverage/os/index.md @@ -22,6 +22,7 @@ Trivy supports operating systems for | [CBL-Mariner](cbl-mariner.md) | 1.0, 2.0 | dnf/yum/rpm | | [Amazon Linux](amazon.md) | 1, 2, 2023 | dnf/yum/rpm | | [openSUSE Leap](suse.md) | 42, 15 | zypper/rpm | +| [openSUSE Tumbleweed](suse.md) | (n/a) | zypper/rpm | | [SUSE Enterprise Linux](suse.md) | 11, 12, 15 | zypper/rpm | | [Photon OS](photon.md) | 1.0, 2.0, 3.0, 4.0 | tndf/yum/rpm | | [Debian GNU/Linux](debian.md) | 7, 8, 9, 10, 11, 12 | apt/dpkg | diff --git a/docs/docs/coverage/os/suse.md b/docs/docs/coverage/os/suse.md index 6ff52de31c86..15cfb1e9379a 100644 --- a/docs/docs/coverage/os/suse.md +++ b/docs/docs/coverage/os/suse.md @@ -2,6 +2,7 @@ Trivy supports the following distributions: - openSUSE Leap +- openSUSE Tumbleweed - SUSE Enterprise Linux (SLE) Please see [here](index.md#supported-os) for supported versions. @@ -35,6 +36,6 @@ Trivy identifies licenses by examining the metadata of RPM packages. [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies -[cvrf]: http://ftp.suse.com/pub/projects/security/cvrf/ +[cvrf]: https://ftp.suse.com/pub/projects/security/cvrf/ -[vulnerability statuses]: ../../configuration/filtering.md#by-status \ No newline at end of file +[vulnerability statuses]: ../../configuration/filtering.md#by-status diff --git a/integration/client_server_test.go b/integration/client_server_test.go index a8a5f83f6941..c6dd7fb74dc5 100644 --- a/integration/client_server_test.go +++ b/integration/client_server_test.go @@ -212,6 +212,13 @@ func TestClientServer(t *testing.T) { }, golden: "testdata/opensuse-leap-151.json.golden", }, + { + name: "opensuse tumbleweed", + args: csArgs{ + Input: "testdata/fixtures/images/opensuse-tumbleweed.tar.gz", + }, + golden: "testdata/opensuse-tumbleweed.json.golden", + }, { name: "photon 3.0", args: csArgs{ diff --git a/integration/docker_engine_test.go b/integration/docker_engine_test.go index ad7d5020e9c3..79c89c5bc521 100644 --- a/integration/docker_engine_test.go +++ b/integration/docker_engine_test.go @@ -192,6 +192,12 @@ func TestDockerEngine(t *testing.T) { input: "testdata/fixtures/images/opensuse-leap-151.tar.gz", golden: "testdata/opensuse-leap-151.json.golden", }, + { + name: "opensuse tumbleweed", + imageTag: "ghcr.io/aquasecurity/trivy-test-images:opensuse-tumbleweed", + input: "testdata/fixtures/images/opensuse-tumbleweed.tar.gz", + golden: "testdata/opensuse-tumbleweed.json.golden", + }, { name: "photon 3.0", imageTag: "ghcr.io/aquasecurity/trivy-test-images:photon-30", diff --git a/integration/standalone_tar_test.go b/integration/standalone_tar_test.go index 5b9d73c5d9ff..2cb372b86b0a 100644 --- a/integration/standalone_tar_test.go +++ b/integration/standalone_tar_test.go @@ -322,6 +322,14 @@ func TestTar(t *testing.T) { }, golden: "testdata/opensuse-leap-151.json.golden", }, + { + name: "opensuse tumbleweed", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/opensuse-tumbleweed.tar.gz", + }, + golden: "testdata/opensuse-tumbleweed.json.golden", + }, { name: "photon 3.0", args: args{ diff --git a/integration/testdata/fixtures/db/opensuse.yaml b/integration/testdata/fixtures/db/opensuse.yaml index 0eb4bf5c351c..76518a73d883 100644 --- a/integration/testdata/fixtures/db/opensuse.yaml +++ b/integration/testdata/fixtures/db/opensuse.yaml @@ -9,4 +9,11 @@ pairs: - key: "openSUSE-SU-2020:0062-1" value: - FixedVersion: 1.1.0i-lp151.8.6.1 \ No newline at end of file + FixedVersion: 1.1.0i-lp151.8.6.1 +- bucket: "openSUSE Tumbleweed" + pairs: + - bucket: libopenssl3 + pairs: + - key: "openSUSE-SU-2024:13065-1" + value: + FixedVersion: 3.1.5-9.1 # changed for test diff --git a/integration/testdata/fixtures/db/vulnerability.yaml b/integration/testdata/fixtures/db/vulnerability.yaml index 5b66fd7b9acc..9b16712d0dbc 100644 --- a/integration/testdata/fixtures/db/vulnerability.yaml +++ b/integration/testdata/fixtures/db/vulnerability.yaml @@ -1340,6 +1340,15 @@ - https://nvd.nist.gov/vuln/detail/CVE-2022-24775 PublishedDate: "2022-03-25T19:26:33Z" LastModifiedDate: "2022-06-14T20:02:29Z" + - key: openSUSE-SU-2024:13065-1 + value: + Title: "libopenssl-3-devel-3.1.1-3.1 on GA media" + Description: "These are all security issues fixed in the libopenssl-3-devel-3.1.1-3.1 package on the GA media of openSUSE Tumbleweed." + Severity: MEDIUM + References: + - "https://www.suse.com/security/cve/CVE-2023-2975/" + - "https://www.suse.com/security/cve/CVE-2023-3446/" + - "https://www.suse.com/support/security/rating/" - key: CVE-2022-22965 value: Title: "spring-framework: RCE via Data Binding on JDK 9+" @@ -1390,4 +1399,4 @@ - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14155" - "https://nvd.nist.gov/vuln/detail/CVE-2020-14155" PublishedDate: "2020-06-15T17:15:00Z" - LastModifiedDate: "2022-04-28T15:06:00Z" + LastModifiedDate: "2022-04-28T15:06:00Z" \ No newline at end of file diff --git a/integration/testdata/opensuse-tumbleweed.json.golden b/integration/testdata/opensuse-tumbleweed.json.golden new file mode 100644 index 000000000000..b3dc552bdfca --- /dev/null +++ b/integration/testdata/opensuse-tumbleweed.json.golden @@ -0,0 +1,94 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/opensuse-tumbleweed.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "opensuse.tumbleweed", + "Name": "20240607" + }, + "ImageID": "sha256:580e73f5c823232e6587136e9f5428a89afdf77a123bb8575d08208e0cc34b12", + "DiffIDs": [ + "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + ], + "ImageConfig": { + "architecture": "amd64", + "author": "Fabian Vogt \u003cfvogt@suse.com\u003e", + "created": "2024-06-07T17:19:44Z", + "history": [ + { + "author": "Fabian Vogt \u003cfvogt@suse.com\u003e", + "created": "2024-06-07T17:19:44Z", + "created_by": "KIWI 10.0.19", + "comment": "openSUSE Tumbleweed 20240607 Base Container" + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Labels": { + "org.openbuildservice.disturl": "obs://build.opensuse.org/openSUSE:Factory/images/b068e2522114e1c009e9bfa1b5cb1146-opensuse-tumbleweed-image:docker", + "org.opencontainers.image.created": "2024-06-07T17:19:38.229693664Z", + "org.opencontainers.image.description": "Image containing a minimal environment for containers based on openSUSE Tumbleweed.", + "org.opencontainers.image.source": "https://build.opensuse.org/package/show/openSUSE:Factory/opensuse-tumbleweed-image?rev=b068e2522114e1c009e9bfa1b5cb1146", + "org.opencontainers.image.title": "openSUSE Tumbleweed Base Container", + "org.opencontainers.image.url": "https://www.opensuse.org/", + "org.opencontainers.image.vendor": "openSUSE Project", + "org.opencontainers.image.version": "20240607.30.45", + "org.opensuse.base.created": "2024-06-07T17:19:38.229693664Z", + "org.opensuse.base.description": "Image containing a minimal environment for containers based on openSUSE Tumbleweed.", + "org.opensuse.base.disturl": "obs://build.opensuse.org/openSUSE:Factory/images/b068e2522114e1c009e9bfa1b5cb1146-opensuse-tumbleweed-image:docker", + "org.opensuse.base.reference": "registry.opensuse.org/opensuse/tumbleweed:20240607.30.45", + "org.opensuse.base.source": "https://build.opensuse.org/package/show/openSUSE:Factory/opensuse-tumbleweed-image?rev=b068e2522114e1c009e9bfa1b5cb1146", + "org.opensuse.base.title": "openSUSE Tumbleweed Base Container", + "org.opensuse.base.url": "https://www.opensuse.org/", + "org.opensuse.base.vendor": "openSUSE Project", + "org.opensuse.base.version": "20240607.30.45", + "org.opensuse.reference": "registry.opensuse.org/opensuse/tumbleweed:20240607.30.45" + } + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/opensuse-tumbleweed.tar.gz (opensuse.tumbleweed 20240607)", + "Class": "os-pkgs", + "Type": "opensuse.tumbleweed", + "Vulnerabilities": [ + { + "VulnerabilityID": "openSUSE-SU-2024:13065-1", + "PkgID": "libopenssl3@3.1.4-9.1.x86_64", + "PkgName": "libopenssl3", + "PkgIdentifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libopenssl3@3.1.4-9.1?arch=x86_64\u0026distro=opensuse.tumbleweed-20240607", + "UID": "f051425f385d2b99" + }, + "InstalledVersion": "3.1.4-9.1", + "FixedVersion": "3.1.5-9.1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Title": "libopenssl-3-devel-3.1.1-3.1 on GA media", + "Description": "These are all security issues fixed in the libopenssl-3-devel-3.1.1-3.1 package on the GA media of openSUSE Tumbleweed.", + "Severity": "MEDIUM", + "References": [ + "https://www.suse.com/security/cve/CVE-2023-2975/", + "https://www.suse.com/security/cve/CVE-2023-3446/", + "https://www.suse.com/support/security/rating/" + ] + } + ] + } + ] +} diff --git a/pkg/detector/ospkg/detect.go b/pkg/detector/ospkg/detect.go index bbeb8e8649d8..e05b590107ca 100644 --- a/pkg/detector/ospkg/detect.go +++ b/pkg/detector/ospkg/detect.go @@ -30,21 +30,22 @@ var ( ErrUnsupportedOS = xerrors.New("unsupported os") drivers = map[ftypes.OSType]Driver{ - ftypes.Alpine: alpine.NewScanner(), - ftypes.Alma: alma.NewScanner(), - ftypes.Amazon: amazon.NewScanner(), - ftypes.CBLMariner: mariner.NewScanner(), - ftypes.Debian: debian.NewScanner(), - ftypes.Ubuntu: ubuntu.NewScanner(), - ftypes.RedHat: redhat.NewScanner(), - ftypes.CentOS: redhat.NewScanner(), - ftypes.Rocky: rocky.NewScanner(), - ftypes.Oracle: oracle.NewScanner(), - ftypes.OpenSUSELeap: suse.NewScanner(suse.OpenSUSE), - ftypes.SLES: suse.NewScanner(suse.SUSEEnterpriseLinux), - ftypes.Photon: photon.NewScanner(), - ftypes.Wolfi: wolfi.NewScanner(), - ftypes.Chainguard: chainguard.NewScanner(), + ftypes.Alpine: alpine.NewScanner(), + ftypes.Alma: alma.NewScanner(), + ftypes.Amazon: amazon.NewScanner(), + ftypes.CBLMariner: mariner.NewScanner(), + ftypes.Debian: debian.NewScanner(), + ftypes.Ubuntu: ubuntu.NewScanner(), + ftypes.RedHat: redhat.NewScanner(), + ftypes.CentOS: redhat.NewScanner(), + ftypes.Rocky: rocky.NewScanner(), + ftypes.Oracle: oracle.NewScanner(), + ftypes.OpenSUSETumbleweed: suse.NewScanner(suse.OpenSUSETumbleweed), + ftypes.OpenSUSELeap: suse.NewScanner(suse.OpenSUSE), + ftypes.SLES: suse.NewScanner(suse.SUSEEnterpriseLinux), + ftypes.Photon: photon.NewScanner(), + ftypes.Wolfi: wolfi.NewScanner(), + ftypes.Chainguard: chainguard.NewScanner(), } ) diff --git a/pkg/detector/ospkg/suse/suse.go b/pkg/detector/ospkg/suse/suse.go index eb2fed82cda0..b999e1dafb22 100644 --- a/pkg/detector/ospkg/suse/suse.go +++ b/pkg/detector/ospkg/suse/suse.go @@ -68,6 +68,7 @@ const ( SUSEEnterpriseLinux Type = iota // OpenSUSE for open versions OpenSUSE + OpenSUSETumbleweed ) // Scanner implements the SUSE scanner @@ -86,6 +87,10 @@ func NewScanner(t Type) *Scanner { return &Scanner{ vs: susecvrf.NewVulnSrc(susecvrf.OpenSUSE), } + case OpenSUSETumbleweed: + return &Scanner{ + vs: susecvrf.NewVulnSrc(susecvrf.OpenSUSETumbleweed), + } } return nil } @@ -130,5 +135,9 @@ func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType if osFamily == ftypes.SLES { return osver.Supported(ctx, slesEolDates, osFamily, osVer) } + // tumbleweed is a rolling release, it has no version and no eol + if osFamily == ftypes.OpenSUSETumbleweed { + return true + } return osver.Supported(ctx, opensuseEolDates, osFamily, osVer) } diff --git a/pkg/detector/ospkg/suse/suse_test.go b/pkg/detector/ospkg/suse/suse_test.go index 011fc3332b6a..4db052743e82 100644 --- a/pkg/detector/ospkg/suse/suse_test.go +++ b/pkg/detector/ospkg/suse/suse_test.go @@ -71,6 +71,46 @@ func TestScanner_Detect(t *testing.T) { }, }, }, + { + name: "happy path: tumbleweed", + fixtures: []string{ + "testdata/fixtures/tumbleweed.yaml", + "testdata/fixtures/data-source.yaml", + }, + distribution: suse.OpenSUSETumbleweed, + args: args{ + osVer: "", + pkgs: []ftypes.Package{ + { + Name: "singularity-ce", + Version: "4.1.3", + Release: "1.0", + SrcName: "postgresql", + SrcVersion: "4.1.3", + SrcRelease: "1.1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "singularity-ce", + VulnerabilityID: "openSUSE-SU-2024:14059-1", + InstalledVersion: "4.1.3-1.0", + FixedVersion: "4.1.3-1.1", + Layer: ftypes.Layer{ + DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", + }, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.SuseCVRF, + Name: "SUSE CVRF", + URL: "https://ftp.suse.com/pub/projects/security/cvrf/", + }, + }, + }, + }, { name: "broken bucket", fixtures: []string{ @@ -122,6 +162,16 @@ func TestScanner_IsSupportedVersion(t *testing.T) { args args want bool }{ + { + name: "opensuse.tumbleweed", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "opensuse.tumbleweed", + osVer: "", + }, + distribution: suse.OpenSUSETumbleweed, + want: true, + }, { name: "opensuse.leap42.3", now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), diff --git a/pkg/detector/ospkg/suse/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/suse/testdata/fixtures/data-source.yaml index 13eb48d0a0ea..b917b7e19da1 100644 --- a/pkg/detector/ospkg/suse/testdata/fixtures/data-source.yaml +++ b/pkg/detector/ospkg/suse/testdata/fixtures/data-source.yaml @@ -1,5 +1,10 @@ - bucket: data-source pairs: + - key: openSUSE Tumbleweed + value: + ID: "suse-cvrf" + Name: "SUSE CVRF" + URL: "https://ftp.suse.com/pub/projects/security/cvrf/" - key: openSUSE Leap 15.3 value: ID: "suse-cvrf" @@ -9,4 +14,4 @@ value: ID: "suse-cvrf" Name: "SUSE CVRF" - URL: "https://ftp.suse.com/pub/projects/security/cvrf/" \ No newline at end of file + URL: "https://ftp.suse.com/pub/projects/security/cvrf/" diff --git a/pkg/detector/ospkg/suse/testdata/fixtures/tumbleweed.yaml b/pkg/detector/ospkg/suse/testdata/fixtures/tumbleweed.yaml new file mode 100644 index 000000000000..2912dda8a711 --- /dev/null +++ b/pkg/detector/ospkg/suse/testdata/fixtures/tumbleweed.yaml @@ -0,0 +1,7 @@ +- bucket: openSUSE Tumbleweed + pairs: + - bucket: singularity-ce + pairs: + - key: openSUSE-SU-2024:14059-1 + value: + FixedVersion: "4.1.3-1.1" diff --git a/pkg/fanal/test/integration/library_test.go b/pkg/fanal/test/integration/library_test.go index f06a8c3f5c6c..5c5c3fd7615d 100644 --- a/pkg/fanal/test/integration/library_test.go +++ b/pkg/fanal/test/integration/library_test.go @@ -98,6 +98,15 @@ var tests = []testCase{ Family: "opensuse.leap", }, }, + { + name: "happy path, opensuse tumbleweed", + remoteImageName: "ghcr.io/aquasecurity/trivy-test-images:opensuse-tumbleweed", + imageFile: "../../../../integration/testdata/fixtures/images/opensuse-tumbleweed.tar.gz", + wantOS: types.OS{ + Name: "20240607", + Family: "opensuse.tumbleweed", + }, + }, { // from registry.suse.com/suse/sle15:15.3.17.8.16 name: "happy path, suse 15.3 (NDB)", diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/opensuse-tumbleweed.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/opensuse-tumbleweed.json.golden new file mode 100644 index 000000000000..a651f86e3862 --- /dev/null +++ b/pkg/fanal/test/integration/testdata/goldens/packages/opensuse-tumbleweed.json.golden @@ -0,0 +1,3472 @@ +[ + { + "ID": "aaa_base@84.87+git20240523.10a5692-1.1.x86_64", + "Name": "aaa_base", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/aaa_base@84.87%2Bgit20240523.10a5692-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "fe755017155caefc" + }, + "Version": "84.87+git20240523.10a5692", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "aaa_base", + "SrcVersion": "84.87+git20240523.10a5692", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "coreutils@9.5-1.1.x86_64", + "filesystem@84.87-15.3.x86_64", + "fillup@1.42-281.1.x86_64", + "glibc@2.39-9.1.x86_64", + "openSUSE-release@20240607-2943.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:cf2eea4941f2d5951a8f9b5b50eeff77" + }, + { + "ID": "bash@5.2.26-12.1.x86_64", + "Name": "bash", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/bash@5.2.26-12.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "ce56393f87add219" + }, + "Version": "5.2.26", + "Release": "12.1", + "Arch": "x86_64", + "SrcName": "bash", + "SrcVersion": "5.2.26", + "SrcRelease": "12.1", + "Licenses": [ + "GPL-3.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libreadline8@8.2.10-1.3.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:b928fafe598e634c8eee09bc1427405b" + }, + { + "ID": "bash-sh@5.2.26-12.1.noarch", + "Name": "bash-sh", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/bash-sh@5.2.26-12.1?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "7363186d472571e0" + }, + "Version": "5.2.26", + "Release": "12.1", + "Arch": "noarch", + "SrcName": "bash", + "SrcVersion": "5.2.26", + "SrcRelease": "12.1", + "Licenses": [ + "GPL-3.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash@5.2.26-12.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:276307457bc668cec35e0405d1a68772" + }, + { + "ID": "boost-license1_85_0@1.85.0-1.2.noarch", + "Name": "boost-license1_85_0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/boost-license1_85_0@1.85.0-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "2d87c856df6862ee" + }, + "Version": "1.85.0", + "Release": "1.2", + "Arch": "noarch", + "SrcName": "boost-base", + "SrcVersion": "1.85.0", + "SrcRelease": "1.2", + "Licenses": [ + "BSL-1.0" + ], + "Maintainer": "openSUSE", + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:7264f57d08c08b81df42e0d62859f66b" + }, + { + "ID": "branding-openSUSE@84.87.20240405-1.2.noarch", + "Name": "branding-openSUSE", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/branding-openSUSE@84.87.20240405-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "acda90b5f91cb463" + }, + "Version": "84.87.20240405", + "Release": "1.2", + "Arch": "noarch", + "SrcName": "branding-openSUSE", + "SrcVersion": "84.87.20240405", + "SrcRelease": "1.2", + "Licenses": [ + "BSD-3-Clause AND CC-BY-SA-3.0 AND GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:a838c8ad09b0db51a4e381ad4330b9d4" + }, + { + "ID": "ca-certificates@2+git20240415.3fe9324-1.1.noarch", + "Name": "ca-certificates", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/ca-certificates@2%2Bgit20240415.3fe9324-1.1?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "7a18ce239fe8c044" + }, + "Version": "2+git20240415.3fe9324", + "Release": "1.1", + "Arch": "noarch", + "SrcName": "ca-certificates", + "SrcVersion": "2+git20240415.3fe9324", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "bash@5.2.26-12.1.x86_64", + "p11-kit-tools@0.25.3-1.3.x86_64", + "p11-kit@0.25.3-1.3.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:0f842fe4e07e1b19623eca20bec31a76" + }, + { + "ID": "ca-certificates-mozilla@2.66-1.2.noarch", + "Name": "ca-certificates-mozilla", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/ca-certificates-mozilla@2.66-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "362f343aa3c5d416" + }, + "Version": "2.66", + "Release": "1.2", + "Arch": "noarch", + "SrcName": "ca-certificates-mozilla", + "SrcVersion": "2.66", + "SrcRelease": "1.2", + "Licenses": [ + "MPL-2.0" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "ca-certificates@2+git20240415.3fe9324-1.1.noarch" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:1faa2c8ddc324cf2b0105947359e26ba" + }, + { + "ID": "coreutils@9.5-1.1.x86_64", + "Name": "coreutils", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/coreutils@9.5-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "9483aa372c47866d" + }, + "Version": "9.5", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "coreutils", + "SrcVersion": "9.5", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-3.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "glibc@2.39-9.1.x86_64", + "libacl1@2.3.2-2.1.x86_64", + "libattr1@2.5.2-1.2.x86_64", + "libcap2@2.70-1.1.x86_64", + "libgmp10@6.3.0-3.2.x86_64", + "libselinux1@3.6-1.3.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:a734c4c0e7fdce4fd161626a6803f829" + }, + { + "ID": "cracklib-dict-small@2.9.11-1.4.x86_64", + "Name": "cracklib-dict-small", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/cracklib-dict-small@2.9.11-1.4?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "cc38aea883124e41" + }, + "Version": "2.9.11", + "Release": "1.4", + "Arch": "x86_64", + "SrcName": "cracklib", + "SrcVersion": "2.9.11", + "SrcRelease": "1.4", + "Licenses": [ + "LGPL-2.1-only" + ], + "Maintainer": "openSUSE", + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:0baa91cb703ac508130480c5a8f1e172" + }, + { + "ID": "crypto-policies@20230920.570ea89-3.2.noarch", + "Name": "crypto-policies", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/crypto-policies@20230920.570ea89-3.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "ebef887879b412aa" + }, + "Version": "20230920.570ea89", + "Release": "3.2", + "Arch": "noarch", + "SrcName": "crypto-policies", + "SrcVersion": "20230920.570ea89", + "SrcRelease": "3.2", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:44a1f7f5727c68cf2657294d84918189" + }, + { + "ID": "curl@8.8.0-1.1.x86_64", + "Name": "curl", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/curl@8.8.0-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "50b6514bae052e62" + }, + "Version": "8.8.0", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "8.8.0", + "SrcRelease": "1.1", + "Licenses": [ + "curl" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libcurl4@8.8.0-1.1.x86_64", + "libz1@1.3.1-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:8de55615716726f81d4c3b3d475541aa" + }, + { + "ID": "file-magic@5.45-2.2.noarch", + "Name": "file-magic", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/file-magic@5.45-2.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "9318efe3deebc83a" + }, + "Version": "5.45", + "Release": "2.2", + "Arch": "noarch", + "SrcName": "file", + "SrcVersion": "5.45", + "SrcRelease": "2.2", + "Licenses": [ + "BSD-2-Clause" + ], + "Maintainer": "openSUSE", + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:0ae8b7c39976e312a269aa6b93e186a6" + }, + { + "ID": "filesystem@84.87-15.3.x86_64", + "Name": "filesystem", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/filesystem@84.87-15.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "379508af5bc6bae5" + }, + "Version": "84.87", + "Release": "15.3", + "Arch": "x86_64", + "SrcName": "filesystem", + "SrcVersion": "84.87", + "SrcRelease": "15.3", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "system-user-root@20190513-2.16.noarch" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:b8bd269c23fee97d3bf8ccfb57f13b3c" + }, + { + "ID": "fillup@1.42-281.1.x86_64", + "Name": "fillup", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/fillup@1.42-281.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "7d48bfb3846c8056" + }, + "Version": "1.42", + "Release": "281.1", + "Arch": "x86_64", + "SrcName": "fillup", + "SrcVersion": "1.42", + "SrcRelease": "281.1", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:9dc6901aa64edeb66421882e55fe84f8" + }, + { + "ID": "glibc@2.39-9.1.x86_64", + "Name": "glibc", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/glibc@2.39-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "77433316d747193b" + }, + "Version": "2.39", + "Release": "9.1", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.39", + "SrcRelease": "9.1", + "Licenses": [ + "GPL-2.0-or-later AND LGPL-2.1-or-later AND LGPL-2.1-or-later WITH GCC-exception-2.0" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "filesystem@84.87-15.3.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:321d0a98dc38d7dc8f1f69b770ea3098" + }, + { + "ID": "glibc-locale-base@2.39-9.1.x86_64", + "Name": "glibc-locale-base", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/glibc-locale-base@2.39-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "7f6f8a5c2e27af75" + }, + "Version": "2.39", + "Release": "9.1", + "Arch": "x86_64", + "SrcName": "glibc", + "SrcVersion": "2.39", + "SrcRelease": "9.1", + "Licenses": [ + "GPL-2.0-or-later AND MIT AND LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:6747af325d5fea27b265aca121ba81c2" + }, + { + "ID": "gpg-pubkey@29b700a4-62b07e22.", + "Name": "gpg-pubkey", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/gpg-pubkey@29b700a4-62b07e22?arch=None&distro=opensuse.tumbleweed-20240607", + "UID": "562934f3f56669a5" + }, + "Version": "29b700a4", + "Release": "62b07e22", + "Arch": "None", + "Licenses": [ + "pubkey" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + } + }, + { + "ID": "gpg-pubkey@39db7c82-510a966b.", + "Name": "gpg-pubkey", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/gpg-pubkey@39db7c82-510a966b?arch=None&distro=opensuse.tumbleweed-20240607", + "UID": "5e72dadde79df0d4" + }, + "Version": "39db7c82", + "Release": "510a966b", + "Arch": "None", + "Licenses": [ + "pubkey" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + } + }, + { + "ID": "gpg2@2.4.5-1.1.x86_64", + "Name": "gpg2", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/gpg2@2.4.5-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "e95cc1c58ec7e824" + }, + "Version": "2.4.5", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "gpg2", + "SrcVersion": "2.4.5", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-3.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "glibc@2.39-9.1.x86_64", + "libassuan0@2.5.7-1.1.x86_64", + "libbz2-1@1.0.8-5.10.x86_64", + "libgcrypt20@1.10.3-3.3.x86_64", + "libgpg-error0@1.49-1.1.x86_64", + "libksba8@1.6.6-1.1.x86_64", + "libnpth0@1.7-1.1.x86_64", + "libreadline8@8.2.10-1.3.x86_64", + "libsqlite3-0@3.46.0-1.1.x86_64", + "libusb-1_0-0@1.0.27-1.2.x86_64", + "libz1@1.3.1-1.1.x86_64", + "pinentry@1.2.1-3.5.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:f4c0ede44a4077c8afa99115a85543b5" + }, + { + "ID": "grep@3.11-3.1.x86_64", + "Name": "grep", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/grep@3.11-3.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "7c3b5ec5d53fa9f9" + }, + "Version": "3.11", + "Release": "3.1", + "Arch": "x86_64", + "SrcName": "grep", + "SrcVersion": "3.11", + "SrcRelease": "3.1", + "Licenses": [ + "GPL-3.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "glibc@2.39-9.1.x86_64", + "libpcre2-8-0@10.43-3.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:8cf6e31c86baf335a20230a15ae3eb38" + }, + { + "ID": "gzip@1.13-3.1.x86_64", + "Name": "gzip", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/gzip@1.13-3.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "f51af60e831e41e" + }, + "Version": "1.13", + "Release": "3.1", + "Arch": "x86_64", + "SrcName": "gzip", + "SrcVersion": "1.13", + "SrcRelease": "3.1", + "Licenses": [ + "GPL-3.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:7d30cd51f2fcb71858f9d954318efb1e" + }, + { + "ID": "krb5@1.21.2-5.1.x86_64", + "Name": "krb5", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/krb5@1.21.2-5.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "f22a7694d8a232ac" + }, + "Version": "1.21.2", + "Release": "5.1", + "Arch": "x86_64", + "SrcName": "krb5", + "SrcVersion": "1.21.2", + "SrcRelease": "5.1", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "crypto-policies@20230920.570ea89-3.2.noarch", + "glibc@2.39-9.1.x86_64", + "libcom_err2@1.47.0-4.2.x86_64", + "libkeyutils1@1.6.3-7.2.x86_64", + "libopenssl3@3.1.4-9.1.x86_64", + "libselinux1@3.6-1.3.x86_64", + "libverto1@0.3.2-3.3.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:ce0b6bf0c616391bb4fbcc31bd64cb2d" + }, + { + "ID": "libabsl_lite_2401_0_0@20240116.2-2.1.x86_64", + "Name": "libabsl_lite_2401_0_0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libabsl_lite_2401_0_0@20240116.2-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "24307f175234d50" + }, + "Version": "20240116.2", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "abseil-cpp", + "SrcVersion": "20240116.2", + "SrcRelease": "2.1", + "Licenses": [ + "Apache-2.0" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libgcc_s1@14.1.0+git10173-1.1.x86_64", + "libstdc++6@14.1.0+git10173-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:778a33ec1b48e136eafc0d469a595e41" + }, + { + "ID": "libacl1@2.3.2-2.1.x86_64", + "Name": "libacl1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libacl1@2.3.2-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "6e55e249889869ed" + }, + "Version": "2.3.2", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "acl", + "SrcVersion": "2.3.2", + "SrcRelease": "2.1", + "Licenses": [ + "GPL-2.0-or-later AND LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:6625e66bf4f3c31a1d8ae4acf6390040" + }, + { + "ID": "libassuan0@2.5.7-1.1.x86_64", + "Name": "libassuan0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libassuan0@2.5.7-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "4a9f149fc3b4d802" + }, + "Version": "2.5.7", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "libassuan", + "SrcVersion": "2.5.7", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-3.0-or-later AND LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libgpg-error0@1.49-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:28d13153a6981eb3437a105a9201e24b" + }, + { + "ID": "libattr1@2.5.2-1.2.x86_64", + "Name": "libattr1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libattr1@2.5.2-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "bf6e596e053cc667" + }, + "Version": "2.5.2", + "Release": "1.2", + "Arch": "x86_64", + "SrcName": "attr", + "SrcVersion": "2.5.2", + "SrcRelease": "1.2", + "Licenses": [ + "GPL-2.0-or-later AND LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:80891fecd7fe8b1e74d8428ac9a16a94" + }, + { + "ID": "libaudit1@3.1.1-1.6.x86_64", + "Name": "libaudit1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libaudit1@3.1.1-1.6?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "c2ab09cc3b09bf56" + }, + "Version": "3.1.1", + "Release": "1.6", + "Arch": "x86_64", + "SrcName": "audit", + "SrcVersion": "3.1.1", + "SrcRelease": "1.6", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:69ddc3a89569854756944b89dd3450c3" + }, + { + "ID": "libaugeas0@1.14.1-1.3.x86_64", + "Name": "libaugeas0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libaugeas0@1.14.1-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "bc9b541f623eec37" + }, + "Version": "1.14.1", + "Release": "1.3", + "Arch": "x86_64", + "SrcName": "augeas", + "SrcVersion": "1.14.1", + "SrcRelease": "1.3", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libfa1@1.14.1-1.3.x86_64", + "libxml2-2@2.12.7-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:5dc49ed23cc4b8f0ab2f5f33ac59da2c" + }, + { + "ID": "libblkid1@2.40.1-2.1.x86_64", + "Name": "libblkid1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libblkid1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "bcf4491906d1eb4d" + }, + "Version": "2.40.1", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.40.1", + "SrcRelease": "2.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libeconf0@0.6.3-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:7457513a706ab2caa1946711bd3fff72" + }, + { + "ID": "libboost_thread1_85_0@1.85.0-1.2.x86_64", + "Name": "libboost_thread1_85_0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libboost_thread1_85_0@1.85.0-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "b8612fd1d8aa51a7" + }, + "Version": "1.85.0", + "Release": "1.2", + "Arch": "x86_64", + "SrcName": "boost-base", + "SrcVersion": "1.85.0", + "SrcRelease": "1.2", + "Licenses": [ + "BSL-1.0" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "boost-license1_85_0@1.85.0-1.2.noarch", + "glibc@2.39-9.1.x86_64", + "libgcc_s1@14.1.0+git10173-1.1.x86_64", + "libstdc++6@14.1.0+git10173-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:96160202812c7becdcb6d522abb854b2" + }, + { + "ID": "libbrotlicommon1@1.1.0-1.3.x86_64", + "Name": "libbrotlicommon1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libbrotlicommon1@1.1.0-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "f1d7b84b18abde08" + }, + "Version": "1.1.0", + "Release": "1.3", + "Arch": "x86_64", + "SrcName": "brotli", + "SrcVersion": "1.1.0", + "SrcRelease": "1.3", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:ca02a6aecfea2b3d57d04064a367e602" + }, + { + "ID": "libbrotlidec1@1.1.0-1.3.x86_64", + "Name": "libbrotlidec1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libbrotlidec1@1.1.0-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "5c297a82e6701a0d" + }, + "Version": "1.1.0", + "Release": "1.3", + "Arch": "x86_64", + "SrcName": "brotli", + "SrcVersion": "1.1.0", + "SrcRelease": "1.3", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libbrotlicommon1@1.1.0-1.3.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:2f58823f191edb538f645fbfb514d72a" + }, + { + "ID": "libbz2-1@1.0.8-5.10.x86_64", + "Name": "libbz2-1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libbz2-1@1.0.8-5.10?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "702f3dd378cba8f0" + }, + "Version": "1.0.8", + "Release": "5.10", + "Arch": "x86_64", + "SrcName": "bzip2", + "SrcVersion": "1.0.8", + "SrcRelease": "5.10", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:8f4093e8d5c9c8ee155ee429735145a9" + }, + { + "ID": "libcap-ng0@0.8.5-1.1.x86_64", + "Name": "libcap-ng0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libcap-ng0@0.8.5-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "b40d6fdd09912405" + }, + "Version": "0.8.5", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "libcap-ng", + "SrcVersion": "0.8.5", + "SrcRelease": "1.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:69ef67a757800b0f6a688248e3ce30cb" + }, + { + "ID": "libcap2@2.70-1.1.x86_64", + "Name": "libcap2", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libcap2@2.70-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "c33018bbf8c4bdfa" + }, + "Version": "2.70", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "libcap", + "SrcVersion": "2.70", + "SrcRelease": "1.1", + "Licenses": [ + "BSD-3-Clause OR GPL-2.0-only" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:daa4f5476d39850d83a760025fb34568" + }, + { + "ID": "libcom_err2@1.47.0-4.2.x86_64", + "Name": "libcom_err2", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libcom_err2@1.47.0-4.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "58b023020895cfea" + }, + "Version": "1.47.0", + "Release": "4.2", + "Arch": "x86_64", + "SrcName": "e2fsprogs", + "SrcVersion": "1.47.0", + "SrcRelease": "4.2", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:0053838627e56edddaf2afcfc03d2720" + }, + { + "ID": "libcrypt1@4.4.36-1.6.x86_64", + "Name": "libcrypt1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libcrypt1@4.4.36-1.6?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "541be9a801034440" + }, + "Version": "4.4.36", + "Release": "1.6", + "Arch": "x86_64", + "SrcName": "libxcrypt", + "SrcVersion": "4.4.36", + "SrcRelease": "1.6", + "Licenses": [ + "BSD-2-Clause AND LGPL-2.1-or-later AND BSD-3-Clause AND SUSE-Public-Domain" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:5a4e568c25e5813cdb8754037deed14e" + }, + { + "ID": "libcurl4@8.8.0-1.1.x86_64", + "Name": "libcurl4", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libcurl4@8.8.0-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "59c3c7a8962c110a" + }, + "Version": "8.8.0", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "curl", + "SrcVersion": "8.8.0", + "SrcRelease": "1.1", + "Licenses": [ + "curl" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "krb5@1.21.2-5.1.x86_64", + "libbrotlidec1@1.1.0-1.3.x86_64", + "libidn2-0@2.3.7-1.2.x86_64", + "libldap2@2.6.7-2.1.x86_64", + "libnghttp2-14@1.61.0-1.1.x86_64", + "libopenssl3@3.1.4-9.1.x86_64", + "libpsl5@0.21.5-1.2.x86_64", + "libssh4@0.10.6-2.1.x86_64", + "libz1@1.3.1-1.1.x86_64", + "libzstd1@1.5.6-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:3728b9a9aadd28312e0277af86b7f3e3" + }, + { + "ID": "libeconf0@0.6.3-1.1.x86_64", + "Name": "libeconf0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libeconf0@0.6.3-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "9e3e97464bc6164b" + }, + "Version": "0.6.3", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "libeconf", + "SrcVersion": "0.6.3", + "SrcRelease": "1.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:edd1a61e72f89d7bb173d8440ad14c31" + }, + { + "ID": "libfa1@1.14.1-1.3.x86_64", + "Name": "libfa1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libfa1@1.14.1-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "9df420b84b79a62" + }, + "Version": "1.14.1", + "Release": "1.3", + "Arch": "x86_64", + "SrcName": "augeas", + "SrcVersion": "1.14.1", + "SrcRelease": "1.3", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:d6d8385f2cacc9abbc702442c526bc74" + }, + { + "ID": "libfdisk1@2.40.1-2.1.x86_64", + "Name": "libfdisk1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libfdisk1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "ab47b44e7c45eab1" + }, + "Version": "2.40.1", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.40.1", + "SrcRelease": "2.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libblkid1@2.40.1-2.1.x86_64", + "libuuid1@2.40.1-2.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:e2b65a32eff96f906d9a7d7d127219d1" + }, + { + "ID": "libffi8@3.4.6-1.1.x86_64", + "Name": "libffi8", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libffi8@3.4.6-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "a569681a5276bde6" + }, + "Version": "3.4.6", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "libffi", + "SrcVersion": "3.4.6", + "SrcRelease": "1.1", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:1d883217e99019653694d59093ea31d9" + }, + { + "ID": "libgcc_s1@14.1.0+git10173-1.1.x86_64", + "Name": "libgcc_s1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libgcc_s1@14.1.0%2Bgit10173-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "3130b825fbc3a81e" + }, + "Version": "14.1.0+git10173", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "gcc14", + "SrcVersion": "14.1.0+git10173", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-3.0-or-later WITH GCC-exception-3.1" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:66a1e535aaa04759b618a89de4b96263" + }, + { + "ID": "libgcrypt20@1.10.3-3.3.x86_64", + "Name": "libgcrypt20", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libgcrypt20@1.10.3-3.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "ac38e6e75132d1c6" + }, + "Version": "1.10.3", + "Release": "3.3", + "Arch": "x86_64", + "SrcName": "libgcrypt", + "SrcVersion": "1.10.3", + "SrcRelease": "3.3", + "Licenses": [ + "GPL-2.0-or-later AND LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libgpg-error0@1.49-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:a5fc0701ea2296a9ea9fb33dede7ad8b" + }, + { + "ID": "libglib-2_0-0@2.80.2-1.1.x86_64", + "Name": "libglib-2_0-0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libglib-2_0-0@2.80.2-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "e3dccc27a6f44a3d" + }, + "Version": "2.80.2", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "glib2", + "SrcVersion": "2.80.2", + "SrcRelease": "1.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libpcre2-8-0@10.43-3.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:857026c6ca40d7aad33078d194897771" + }, + { + "ID": "libgmp10@6.3.0-3.2.x86_64", + "Name": "libgmp10", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libgmp10@6.3.0-3.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "fb3994e26d59ae4f" + }, + "Version": "6.3.0", + "Release": "3.2", + "Arch": "x86_64", + "SrcName": "gmp", + "SrcVersion": "6.3.0", + "SrcRelease": "3.2", + "Licenses": [ + "GPL-2.0-or-later OR LGPL-3.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:06266f7fe821bd1b68880e1af233babb" + }, + { + "ID": "libgpg-error0@1.49-1.1.x86_64", + "Name": "libgpg-error0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libgpg-error0@1.49-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "a3b16ea69b05fe60" + }, + "Version": "1.49", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "libgpg-error", + "SrcVersion": "1.49", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-2.0-or-later AND LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:01512ff7c90dfd290dfdec1108378521" + }, + { + "ID": "libgpgme11@1.23.2-4.2.x86_64", + "Name": "libgpgme11", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libgpgme11@1.23.2-4.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "6d9271ab523fb009" + }, + "Version": "1.23.2", + "Release": "4.2", + "Arch": "x86_64", + "SrcName": "gpgme", + "SrcVersion": "1.23.2", + "SrcRelease": "4.2", + "Licenses": [ + "GPL-3.0-or-later AND LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "gpg2@2.4.5-1.1.x86_64", + "libassuan0@2.5.7-1.1.x86_64", + "libgpg-error0@1.49-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:b6a31a19c3c5075a8e160112dcd5aa27" + }, + { + "ID": "libidn2-0@2.3.7-1.2.x86_64", + "Name": "libidn2-0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libidn2-0@2.3.7-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "ae81c3e9fc0d0fc3" + }, + "Version": "2.3.7", + "Release": "1.2", + "Arch": "x86_64", + "SrcName": "libidn2", + "SrcVersion": "2.3.7", + "SrcRelease": "1.2", + "Licenses": [ + "GPL-2.0-or-later OR LGPL-3.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libunistring5@1.2-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:f1c8db3cf3c52509ef21988ea0f703b5" + }, + { + "ID": "libkeyutils1@1.6.3-7.2.x86_64", + "Name": "libkeyutils1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libkeyutils1@1.6.3-7.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "f9f931edfe4b540c" + }, + "Version": "1.6.3", + "Release": "7.2", + "Arch": "x86_64", + "SrcName": "keyutils", + "SrcVersion": "1.6.3", + "SrcRelease": "7.2", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:ec230cda6f7a81a78d4b12d0756f1dbd" + }, + { + "ID": "libksba8@1.6.6-1.1.x86_64", + "Name": "libksba8", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libksba8@1.6.6-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "c532eef98bb36938" + }, + "Version": "1.6.6", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "libksba", + "SrcVersion": "1.6.6", + "SrcRelease": "1.1", + "Licenses": [ + "(GPL-2.0-or-later OR LGPL-3.0-or-later) AND GPL-3.0-or-later AND MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libgpg-error0@1.49-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:418e21da1d7d513e833f00bd0c1a28ae" + }, + { + "ID": "libldap2@2.6.7-2.1.x86_64", + "Name": "libldap2", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libldap2@2.6.7-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "55fa8e45be9ed78" + }, + "Version": "2.6.7", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "openldap2", + "SrcVersion": "2.6.7", + "SrcRelease": "2.1", + "Licenses": [ + "OLDAP-2.8" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libopenssl3@3.1.4-9.1.x86_64", + "libsasl2-3@2.1.28-8.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:e9fa3fbea41011f0a2d7edba27df9a90" + }, + { + "ID": "liblua5_4-5@5.4.6-3.3.x86_64", + "Name": "liblua5_4-5", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/liblua5_4-5@5.4.6-3.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "98b4001b2f59f46" + }, + "Version": "5.4.6", + "Release": "3.3", + "Arch": "x86_64", + "SrcName": "lua54", + "SrcVersion": "5.4.6", + "SrcRelease": "3.3", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:e3b6c7378997bc9b15c87fb7a9abc797" + }, + { + "ID": "liblz4-1@1.9.4-2.8.x86_64", + "Name": "liblz4-1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/liblz4-1@1.9.4-2.8?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "267a6bfb140f0d45" + }, + "Version": "1.9.4", + "Release": "2.8", + "Arch": "x86_64", + "SrcName": "lz4", + "SrcVersion": "1.9.4", + "SrcRelease": "2.8", + "Licenses": [ + "BSD-2-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:0c77203a333afafebaa4a5c88ca115b0" + }, + { + "ID": "liblzma5@5.6.2-1.1.x86_64", + "Name": "liblzma5", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/liblzma5@5.6.2-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "304510f1f6669e2c" + }, + "Version": "5.6.2", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "5.6.2", + "SrcRelease": "1.1", + "Licenses": [ + "0BSD" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:c15bab36d460d13f8c7643f663c45cc5" + }, + { + "ID": "libmagic1@5.45-2.2.x86_64", + "Name": "libmagic1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libmagic1@5.45-2.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "d8fdc2934df34a83" + }, + "Version": "5.45", + "Release": "2.2", + "Arch": "x86_64", + "SrcName": "file", + "SrcVersion": "5.45", + "SrcRelease": "2.2", + "Licenses": [ + "BSD-2-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "file-magic@5.45-2.2.noarch", + "glibc@2.39-9.1.x86_64", + "libbz2-1@1.0.8-5.10.x86_64", + "liblzma5@5.6.2-1.1.x86_64", + "libz1@1.3.1-1.1.x86_64", + "libzstd1@1.5.6-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:3101ffe63f469739c850c1bfba52bcd3" + }, + { + "ID": "libmount1@2.40.1-2.1.x86_64", + "Name": "libmount1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libmount1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "8386ec24a06557ea" + }, + "Version": "2.40.1", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.40.1", + "SrcRelease": "2.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libblkid1@2.40.1-2.1.x86_64", + "libselinux1@3.6-1.3.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:975e572b4b1103841af8aa1c1fd668c2" + }, + { + "ID": "libncurses6@6.5.20240601-38.1.x86_64", + "Name": "libncurses6", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libncurses6@6.5.20240601-38.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "9513bf16199cee6b" + }, + "Version": "6.5.20240601", + "Release": "38.1", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.5.20240601", + "SrcRelease": "38.1", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libgcc_s1@14.1.0+git10173-1.1.x86_64", + "libstdc++6@14.1.0+git10173-1.1.x86_64", + "terminfo-base@6.5.20240601-38.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:30e7aa713a44cf5b1de5f857b4373d16" + }, + { + "ID": "libnghttp2-14@1.61.0-1.1.x86_64", + "Name": "libnghttp2-14", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libnghttp2-14@1.61.0-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "de28696676fc1ebd" + }, + "Version": "1.61.0", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "nghttp2", + "SrcVersion": "1.61.0", + "SrcRelease": "1.1", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:59c3f5258c56df6653fb7dc5ad8d8d15" + }, + { + "ID": "libnpth0@1.7-1.1.x86_64", + "Name": "libnpth0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libnpth0@1.7-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "7bff27e583fb62b3" + }, + "Version": "1.7", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "npth", + "SrcVersion": "1.7", + "SrcRelease": "1.1", + "Licenses": [ + "LGPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:29bf6dc19e99e7f1d9177504ff52b120" + }, + { + "ID": "libnss_usrfiles2@2.27.1-1.2.x86_64", + "Name": "libnss_usrfiles2", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libnss_usrfiles2@2.27.1-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "d3c8c8f840c86b12" + }, + "Version": "2.27.1", + "Release": "1.2", + "Arch": "x86_64", + "SrcName": "libnss_usrfiles", + "SrcVersion": "2.27.1", + "SrcRelease": "1.2", + "Licenses": [ + "LGPL-2.1-only" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:6d9cf56d1102a90f9941430a74d3972b" + }, + { + "ID": "libopenssl-3-fips-provider@3.1.4-9.1.x86_64", + "Name": "libopenssl-3-fips-provider", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libopenssl-3-fips-provider@3.1.4-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "65c56c2870042412" + }, + "Version": "3.1.4", + "Release": "9.1", + "Arch": "x86_64", + "SrcName": "openssl-3", + "SrcVersion": "3.1.4", + "SrcRelease": "9.1", + "Licenses": [ + "Apache-2.0" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libopenssl3@3.1.4-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:f3cbf19faba479ff0c7660ca79deb5af" + }, + { + "ID": "libopenssl3@3.1.4-9.1.x86_64", + "Name": "libopenssl3", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libopenssl3@3.1.4-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "f051425f385d2b99" + }, + "Version": "3.1.4", + "Release": "9.1", + "Arch": "x86_64", + "SrcName": "openssl-3", + "SrcVersion": "3.1.4", + "SrcRelease": "9.1", + "Licenses": [ + "Apache-2.0" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "crypto-policies@20230920.570ea89-3.2.noarch", + "glibc@2.39-9.1.x86_64", + "libz1@1.3.1-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:ff311f853e3888b9a5ccd2072bc0859a" + }, + { + "ID": "libp11-kit0@0.25.3-1.3.x86_64", + "Name": "libp11-kit0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libp11-kit0@0.25.3-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "fbca9a69218ce8e7" + }, + "Version": "0.25.3", + "Release": "1.3", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.25.3", + "SrcRelease": "1.3", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libffi8@3.4.6-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:533c86c6aefb930664588820b1436730" + }, + { + "ID": "libpcre2-8-0@10.43-3.1.x86_64", + "Name": "libpcre2-8-0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libpcre2-8-0@10.43-3.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "dabdfbc56d214ae6" + }, + "Version": "10.43", + "Release": "3.1", + "Arch": "x86_64", + "SrcName": "pcre2", + "SrcVersion": "10.43", + "SrcRelease": "3.1", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:0ad4bc97afb3c55b6c3733ee1912356a" + }, + { + "ID": "libpopt0@1.19-1.8.x86_64", + "Name": "libpopt0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libpopt0@1.19-1.8?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "98fa32fcd9ee1e39" + }, + "Version": "1.19", + "Release": "1.8", + "Arch": "x86_64", + "SrcName": "popt", + "SrcVersion": "1.19", + "SrcRelease": "1.8", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:abbfa7e9f5897199cb003539738cee26" + }, + { + "ID": "libprocps8@3.3.17-17.1.x86_64", + "Name": "libprocps8", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libprocps8@3.3.17-17.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "f874f4997e1438be" + }, + "Version": "3.3.17", + "Release": "17.1", + "Arch": "x86_64", + "SrcName": "procps", + "SrcVersion": "3.3.17", + "SrcRelease": "17.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libsystemd0@255.7-2.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:436829036902fe507fa3bbe2237b18f9" + }, + { + "ID": "libprotobuf-lite25_3_0@25.3-11.2.x86_64", + "Name": "libprotobuf-lite25_3_0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libprotobuf-lite25_3_0@25.3-11.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "b306bfd6494e6405" + }, + "Version": "25.3", + "Release": "11.2", + "Arch": "x86_64", + "SrcName": "protobuf", + "SrcVersion": "25.3", + "SrcRelease": "11.2", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libabsl_lite_2401_0_0@20240116.2-2.1.x86_64", + "libgcc_s1@14.1.0+git10173-1.1.x86_64", + "libstdc++6@14.1.0+git10173-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:c7972035db8f6fde906f70ca5040f3e8" + }, + { + "ID": "libpsl5@0.21.5-1.2.x86_64", + "Name": "libpsl5", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libpsl5@0.21.5-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "5d2411f7ede68692" + }, + "Version": "0.21.5", + "Release": "1.2", + "Arch": "x86_64", + "SrcName": "libpsl", + "SrcVersion": "0.21.5", + "SrcRelease": "1.2", + "Licenses": [ + "MIT AND MPL-2.0" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libidn2-0@2.3.7-1.2.x86_64", + "libunistring5@1.2-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:3125d0c9e67ceb68c5d6c2f6a4c3c15b" + }, + { + "ID": "libreadline8@8.2.10-1.3.x86_64", + "Name": "libreadline8", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libreadline8@8.2.10-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "9271e2cd0119054c" + }, + "Version": "8.2.10", + "Release": "1.3", + "Arch": "x86_64", + "SrcName": "readline", + "SrcVersion": "8.2.10", + "SrcRelease": "1.3", + "Licenses": [ + "GPL-3.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libncurses6@6.5.20240601-38.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:341a3ffc98c7c1bda2cba68717f8388a" + }, + { + "ID": "libsasl2-3@2.1.28-8.1.x86_64", + "Name": "libsasl2-3", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libsasl2-3@2.1.28-8.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "fe2536ad8601f334" + }, + "Version": "2.1.28", + "Release": "8.1", + "Arch": "x86_64", + "SrcName": "cyrus-sasl", + "SrcVersion": "2.1.28", + "SrcRelease": "8.1", + "Licenses": [ + "BSD-4-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:df8beb494bc69ec9a7792eca141dfe21" + }, + { + "ID": "libselinux1@3.6-1.3.x86_64", + "Name": "libselinux1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libselinux1@3.6-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "6bc8fe60a073ba96" + }, + "Version": "3.6", + "Release": "1.3", + "Arch": "x86_64", + "SrcName": "libselinux", + "SrcVersion": "3.6", + "SrcRelease": "1.3", + "Licenses": [ + "SUSE-Public-Domain" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libpcre2-8-0@10.43-3.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:0925574494abc81ad7aceb540578c7eb" + }, + { + "ID": "libsemanage-conf@3.6-2.1.x86_64", + "Name": "libsemanage-conf", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libsemanage-conf@3.6-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "56c91988ca2e8ce5" + }, + "Version": "3.6", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "libsemanage", + "SrcVersion": "3.6", + "SrcRelease": "2.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:cf9cd2a9718a8b3ad443e4e11d808e9a" + }, + { + "ID": "libsemanage2@3.6-2.1.x86_64", + "Name": "libsemanage2", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libsemanage2@3.6-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "d945b0271ed45cf5" + }, + "Version": "3.6", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "libsemanage", + "SrcVersion": "3.6", + "SrcRelease": "2.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libaudit1@3.1.1-1.6.x86_64", + "libbz2-1@1.0.8-5.10.x86_64", + "libselinux1@3.6-1.3.x86_64", + "libsemanage-conf@3.6-2.1.x86_64", + "libsepol2@3.6-1.3.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:746ca567785d7117e70f941ebf6f7df5" + }, + { + "ID": "libsepol2@3.6-1.3.x86_64", + "Name": "libsepol2", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libsepol2@3.6-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "f2aaf81754d3169d" + }, + "Version": "3.6", + "Release": "1.3", + "Arch": "x86_64", + "SrcName": "libsepol", + "SrcVersion": "3.6", + "SrcRelease": "1.3", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:4e48f510faaea0a80614970d56adf441" + }, + { + "ID": "libsigc-2_0-0@2.12.1-2.3.x86_64", + "Name": "libsigc-2_0-0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libsigc-2_0-0@2.12.1-2.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "c4d52d6f33dee391" + }, + "Version": "2.12.1", + "Release": "2.3", + "Arch": "x86_64", + "SrcName": "libsigc++2", + "SrcVersion": "2.12.1", + "SrcRelease": "2.3", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libgcc_s1@14.1.0+git10173-1.1.x86_64", + "libstdc++6@14.1.0+git10173-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:526e412ee054bf5befe0c4e9d2dfa27f" + }, + { + "ID": "libsmartcols1@2.40.1-2.1.x86_64", + "Name": "libsmartcols1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libsmartcols1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "5302abe63411170d" + }, + "Version": "2.40.1", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.40.1", + "SrcRelease": "2.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:52af669f1027cf05ebc0e18bd2a1a175" + }, + { + "ID": "libsolv-tools-base@0.7.29-1.1.x86_64", + "Name": "libsolv-tools-base", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libsolv-tools-base@0.7.29-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "f2adb3efc201c696" + }, + "Version": "0.7.29", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "libsolv", + "SrcVersion": "0.7.29", + "SrcRelease": "1.1", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libbz2-1@1.0.8-5.10.x86_64", + "liblzma5@5.6.2-1.1.x86_64", + "libxml2-2@2.12.7-1.1.x86_64", + "libz1@1.3.1-1.1.x86_64", + "libzstd1@1.5.6-1.1.x86_64", + "rpm@4.19.1.1-3.2.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:d143fbc74ef620b428acdb4b2fffe882" + }, + { + "ID": "libsqlite3-0@3.46.0-1.1.x86_64", + "Name": "libsqlite3-0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libsqlite3-0@3.46.0-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "d9bf1a49d16f0c" + }, + "Version": "3.46.0", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "sqlite3", + "SrcVersion": "3.46.0", + "SrcRelease": "1.1", + "Licenses": [ + "SUSE-Public-Domain" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:503106160b0ae60089c4c955cbe6488d" + }, + { + "ID": "libssh-config@0.10.6-2.1.x86_64", + "Name": "libssh-config", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libssh-config@0.10.6-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "8628d51e34c2f5b1" + }, + "Version": "0.10.6", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "libssh", + "SrcVersion": "0.10.6", + "SrcRelease": "2.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:c1430a20f9a866e13b66f1830e36ee11" + }, + { + "ID": "libssh4@0.10.6-2.1.x86_64", + "Name": "libssh4", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libssh4@0.10.6-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "d07880785aee16c8" + }, + "Version": "0.10.6", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "libssh", + "SrcVersion": "0.10.6", + "SrcRelease": "2.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "krb5@1.21.2-5.1.x86_64", + "libopenssl3@3.1.4-9.1.x86_64", + "libssh-config@0.10.6-2.1.x86_64", + "libz1@1.3.1-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:02b8d13b4d1f162ec0bfd5b6f931451b" + }, + { + "ID": "libstdc++6@14.1.0+git10173-1.1.x86_64", + "Name": "libstdc++6", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libstdc%2B%2B6@14.1.0%2Bgit10173-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "f3345c3d3261e7e9" + }, + "Version": "14.1.0+git10173", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "gcc14", + "SrcVersion": "14.1.0+git10173", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-3.0-or-later WITH GCC-exception-3.1" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libgcc_s1@14.1.0+git10173-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:ce22f6cbef15d1c63feb68efb0a4c796" + }, + { + "ID": "libsubid4@4.15.1-1.2.x86_64", + "Name": "libsubid4", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libsubid4@4.15.1-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "e155b313aa6da812" + }, + "Version": "4.15.1", + "Release": "1.2", + "Arch": "x86_64", + "SrcName": "shadow", + "SrcVersion": "4.15.1", + "SrcRelease": "1.2", + "Licenses": [ + "BSD-3-Clause AND GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libaudit1@3.1.1-1.6.x86_64", + "libselinux1@3.6-1.3.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:ca31f71f11227d8bd4ecd4599d51c124" + }, + { + "ID": "libsystemd0@255.7-2.1.x86_64", + "Name": "libsystemd0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libsystemd0@255.7-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "4fa3c2608f054287" + }, + "Version": "255.7", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "systemd", + "SrcVersion": "255.7", + "SrcRelease": "2.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libcap2@2.70-1.1.x86_64", + "libgcrypt20@1.10.3-3.3.x86_64", + "liblz4-1@1.9.4-2.8.x86_64", + "liblzma5@5.6.2-1.1.x86_64", + "libzstd1@1.5.6-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:65e5b0ca62b2660c980d04a8a728d49f" + }, + { + "ID": "libtasn1-6@4.19.0-1.7.x86_64", + "Name": "libtasn1-6", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libtasn1-6@4.19.0-1.7?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "35e287fcdf033bd1" + }, + "Version": "4.19.0", + "Release": "1.7", + "Arch": "x86_64", + "SrcName": "libtasn1", + "SrcVersion": "4.19.0", + "SrcRelease": "1.7", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:137bba01542ac5ef725c26755545372b" + }, + { + "ID": "libudev1@255.7-2.1.x86_64", + "Name": "libudev1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libudev1@255.7-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "4ae1c62105f1f901" + }, + "Version": "255.7", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "systemd", + "SrcVersion": "255.7", + "SrcRelease": "2.1", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libcap2@2.70-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:e634afbeff2b6027ba928b24556c0af8" + }, + { + "ID": "libunistring5@1.2-1.1.x86_64", + "Name": "libunistring5", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libunistring5@1.2-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "e8be56f8ad59a760" + }, + "Version": "1.2", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "libunistring", + "SrcVersion": "1.2", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-3.0-or-later OR LGPL-3.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:3cd872e86a27cc3e2bb6babbbf45d596" + }, + { + "ID": "libusb-1_0-0@1.0.27-1.2.x86_64", + "Name": "libusb-1_0-0", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libusb-1_0-0@1.0.27-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "dab90c8d517b4ee4" + }, + "Version": "1.0.27", + "Release": "1.2", + "Arch": "x86_64", + "SrcName": "libusb-1_0", + "SrcVersion": "1.0.27", + "SrcRelease": "1.2", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libudev1@255.7-2.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:695fcf96bea1814a2c55c5dbe82e054e" + }, + { + "ID": "libuuid1@2.40.1-2.1.x86_64", + "Name": "libuuid1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libuuid1@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "bc5c46e1650d4a95" + }, + "Version": "2.40.1", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.40.1", + "SrcRelease": "2.1", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:4a5a3223a671e7231913b347b9e118f5" + }, + { + "ID": "libverto1@0.3.2-3.3.x86_64", + "Name": "libverto1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libverto1@0.3.2-3.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "8c13b7ac8ed99616" + }, + "Version": "0.3.2", + "Release": "3.3", + "Arch": "x86_64", + "SrcName": "libverto", + "SrcVersion": "0.3.2", + "SrcRelease": "3.3", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:3124d2445b585d664d7026b57997ae34" + }, + { + "ID": "libxml2-2@2.12.7-1.1.x86_64", + "Name": "libxml2-2", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libxml2-2@2.12.7-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "1285499ab636c5d9" + }, + "Version": "2.12.7", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "libxml2", + "SrcVersion": "2.12.7", + "SrcRelease": "1.1", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "liblzma5@5.6.2-1.1.x86_64", + "libz1@1.3.1-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:5afd1ff15492cc2acd4515fceea1cab3" + }, + { + "ID": "libyaml-cpp0_8@0.8.0-1.3.x86_64", + "Name": "libyaml-cpp0_8", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libyaml-cpp0_8@0.8.0-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "d743795a2d65f87b" + }, + "Version": "0.8.0", + "Release": "1.3", + "Arch": "x86_64", + "SrcName": "yaml-cpp", + "SrcVersion": "0.8.0", + "SrcRelease": "1.3", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libgcc_s1@14.1.0+git10173-1.1.x86_64", + "libstdc++6@14.1.0+git10173-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:38c2992316046244f7c7358ebdcc2ffc" + }, + { + "ID": "libz1@1.3.1-1.1.x86_64", + "Name": "libz1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libz1@1.3.1-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "f09857fffac622a" + }, + "Version": "1.3.1", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "zlib", + "SrcVersion": "1.3.1", + "SrcRelease": "1.1", + "Licenses": [ + "Zlib" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:7bffc41478c2facacb64a33ae6b6596f" + }, + { + "ID": "libzck1@1.4.0-2.1.x86_64", + "Name": "libzck1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libzck1@1.4.0-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "76b3d8e58402a974" + }, + "Version": "1.4.0", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "zchunk", + "SrcVersion": "1.4.0", + "SrcRelease": "2.1", + "Licenses": [ + "BSD-2-Clause AND MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libopenssl3@3.1.4-9.1.x86_64", + "libzstd1@1.5.6-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:04b990241339f25aa648586624e62be9" + }, + { + "ID": "libzstd1@1.5.6-1.1.x86_64", + "Name": "libzstd1", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libzstd1@1.5.6-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "4edc1117cd2019eb" + }, + "Version": "1.5.6", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "zstd", + "SrcVersion": "1.5.6", + "SrcRelease": "1.1", + "Licenses": [ + "BSD-3-Clause AND GPL-2.0-only" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:0305f6c09d13f7e0f12823ecc6e5e3a9" + }, + { + "ID": "libzypp@17.34.1-1.1.x86_64", + "Name": "libzypp", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/libzypp@17.34.1-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "3545239e91f3bd9" + }, + "Version": "17.34.1", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "libzypp", + "SrcVersion": "17.34.1", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "glibc@2.39-9.1.x86_64", + "libabsl_lite_2401_0_0@20240116.2-2.1.x86_64", + "libboost_thread1_85_0@1.85.0-1.2.x86_64", + "libcurl4@8.8.0-1.1.x86_64", + "libgcc_s1@14.1.0+git10173-1.1.x86_64", + "libglib-2_0-0@2.80.2-1.1.x86_64", + "libgpgme11@1.23.2-4.2.x86_64", + "libopenssl3@3.1.4-9.1.x86_64", + "libprotobuf-lite25_3_0@25.3-11.2.x86_64", + "libsigc-2_0-0@2.12.1-2.3.x86_64", + "libsolv-tools-base@0.7.29-1.1.x86_64", + "libstdc++6@14.1.0+git10173-1.1.x86_64", + "libudev1@255.7-2.1.x86_64", + "libxml2-2@2.12.7-1.1.x86_64", + "libyaml-cpp0_8@0.8.0-1.3.x86_64", + "libz1@1.3.1-1.1.x86_64", + "libzck1@1.4.0-2.1.x86_64", + "libzstd1@1.5.6-1.1.x86_64", + "rpm@4.19.1.1-3.2.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:ef3aae189501c6aed58d71a3354ae17f" + }, + { + "ID": "login_defs@4.15.1-1.2.noarch", + "Name": "login_defs", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/login_defs@4.15.1-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "1695371f9551a301" + }, + "Version": "4.15.1", + "Release": "1.2", + "Arch": "noarch", + "SrcName": "shadow", + "SrcVersion": "4.15.1", + "SrcRelease": "1.2", + "Licenses": [ + "BSD-3-Clause AND GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:92ca1b8d5fa70f855c17eee359a0cd1d" + }, + { + "ID": "lsb-release@3.3-1.3.noarch", + "Name": "lsb-release", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/lsb-release@3.3-1.3?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "8c82a3a248c52a13" + }, + "Version": "3.3", + "Release": "1.3", + "Arch": "noarch", + "SrcName": "lsb-release", + "SrcVersion": "3.3", + "SrcRelease": "1.3", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "util-linux@2.40.1-2.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:230f3fbd4104751fa5670646f4f11b3c" + }, + { + "ID": "ncurses-utils@6.5.20240601-38.1.x86_64", + "Name": "ncurses-utils", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/ncurses-utils@6.5.20240601-38.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "90d23a67ceb37784" + }, + "Version": "6.5.20240601", + "Release": "38.1", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.5.20240601", + "SrcRelease": "38.1", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libncurses6@6.5.20240601-38.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:937b5bd3edd4ea92a2e735b2e7a231c5" + }, + { + "ID": "netcfg@11.6-13.3.noarch", + "Name": "netcfg", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/netcfg@11.6-13.3?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "c32526003d9c5528" + }, + "Version": "11.6", + "Release": "13.3", + "Arch": "noarch", + "SrcName": "netcfg", + "SrcVersion": "11.6", + "SrcRelease": "13.3", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "libnss_usrfiles2@2.27.1-1.2.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:27dc71cda258e790d696b21a5d0df38a" + }, + { + "ID": "openSUSE-build-key@1.0-53.1.x86_64", + "Name": "openSUSE-build-key", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/openSUSE-build-key@1.0-53.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "ed8309d0e84993e4" + }, + "Version": "1.0", + "Release": "53.1", + "Arch": "x86_64", + "SrcName": "openSUSE-build-key", + "SrcVersion": "1.0", + "SrcRelease": "53.1", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "bash@5.2.26-12.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:22f70544440b7096a59edb3fd793bf9f" + }, + { + "ID": "openSUSE-release@20240607-2943.1.x86_64", + "Name": "openSUSE-release", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/openSUSE-release@20240607-2943.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "ad908712f8c8e5ab" + }, + "Version": "20240607", + "Release": "2943.1", + "Arch": "x86_64", + "SrcName": "openSUSE-release", + "SrcVersion": "20240607", + "SrcRelease": "2943.1", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "openSUSE-release-appliance-docker@20240607-2943.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:1cda5d872656a58eb3e41a07b6f7dec1" + }, + { + "ID": "openSUSE-release-appliance-docker@20240607-2943.1.x86_64", + "Name": "openSUSE-release-appliance-docker", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/openSUSE-release-appliance-docker@20240607-2943.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "46f06026407817a0" + }, + "Version": "20240607", + "Release": "2943.1", + "Arch": "x86_64", + "SrcName": "openSUSE-release", + "SrcVersion": "20240607", + "SrcRelease": "2943.1", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "openSUSE", + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:e65c65823b67726c329a5a3efded6b89" + }, + { + "ID": "openssl@3.1.4-3.2.noarch", + "Name": "openssl", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/openssl@3.1.4-3.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "cd2ead77021cf857" + }, + "Version": "3.1.4", + "Release": "3.2", + "Arch": "noarch", + "SrcName": "openssl", + "SrcVersion": "3.1.4", + "SrcRelease": "3.2", + "Licenses": [ + "Apache-2.0" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "openssl-3@3.1.4-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:030fc5cf40517543d3f93e9034e03cda" + }, + { + "ID": "openssl-3@3.1.4-9.1.x86_64", + "Name": "openssl-3", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/openssl-3@3.1.4-9.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "da148866e5ba5d92" + }, + "Version": "3.1.4", + "Release": "9.1", + "Arch": "x86_64", + "SrcName": "openssl-3", + "SrcVersion": "3.1.4", + "SrcRelease": "9.1", + "Licenses": [ + "Apache-2.0" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "bash@5.2.26-12.1.x86_64", + "crypto-policies@20230920.570ea89-3.2.noarch", + "glibc@2.39-9.1.x86_64", + "libopenssl3@3.1.4-9.1.x86_64", + "openssl@3.1.4-3.2.noarch" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:befabeed611131fe2bfdd87207a20c82" + }, + { + "ID": "p11-kit@0.25.3-1.3.x86_64", + "Name": "p11-kit", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/p11-kit@0.25.3-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "7da38dbf3cd84149" + }, + "Version": "0.25.3", + "Release": "1.3", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.25.3", + "SrcRelease": "1.3", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libp11-kit0@0.25.3-1.3.x86_64", + "libtasn1-6@4.19.0-1.7.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:9e0bb02693034c3f622869316820c967" + }, + { + "ID": "p11-kit-tools@0.25.3-1.3.x86_64", + "Name": "p11-kit-tools", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/p11-kit-tools@0.25.3-1.3?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "fb534863cc7b3050" + }, + "Version": "0.25.3", + "Release": "1.3", + "Arch": "x86_64", + "SrcName": "p11-kit", + "SrcVersion": "0.25.3", + "SrcRelease": "1.3", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libp11-kit0@0.25.3-1.3.x86_64", + "libtasn1-6@4.19.0-1.7.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:0c48a6aaacfc3bc94449b36569e43883" + }, + { + "ID": "pam@1.6.1-1.1.x86_64", + "Name": "pam", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/pam@1.6.1-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "2cc82a7c85091dc0" + }, + "Version": "1.6.1", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "pam", + "SrcVersion": "1.6.1", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-2.0-or-later OR BSD-3-Clause" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "glibc@2.39-9.1.x86_64", + "libaudit1@3.1.1-1.6.x86_64", + "libcrypt1@4.4.36-1.6.x86_64", + "libeconf0@0.6.3-1.1.x86_64", + "libselinux1@3.6-1.3.x86_64", + "permissions@1699_20240522-1.1.x86_64", + "system-user-root@20190513-2.16.noarch" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:c653cbd73ec73d214c9145281d819597" + }, + { + "ID": "patterns-base-fips@20200505-51.1.x86_64", + "Name": "patterns-base-fips", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/patterns-base-fips@20200505-51.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "70a74594ade38509" + }, + "Version": "20200505", + "Release": "51.1", + "Arch": "x86_64", + "SrcName": "patterns-base", + "SrcVersion": "20200505", + "SrcRelease": "51.1", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:b0c1afc7c8b61145107d4def715da3b2" + }, + { + "ID": "patterns-base-minimal_base@20200505-51.1.x86_64", + "Name": "patterns-base-minimal_base", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/patterns-base-minimal_base@20200505-51.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "22550c4b68de6581" + }, + "Version": "20200505", + "Release": "51.1", + "Arch": "x86_64", + "SrcName": "patterns-base", + "SrcVersion": "20200505", + "SrcRelease": "51.1", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "branding-openSUSE@84.87.20240405-1.2.noarch", + "filesystem@84.87-15.3.x86_64", + "openSUSE-build-key@1.0-53.1.x86_64", + "openSUSE-release@20240607-2943.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:695250701605e6fc55b70547e75f8547" + }, + { + "ID": "permctl@1699_20240522-1.1.x86_64", + "Name": "permctl", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/permctl@1699_20240522-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "cfcd9931dafbea39" + }, + "Version": "1699_20240522", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "permissions", + "SrcVersion": "1699_20240522", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libacl1@2.3.2-2.1.x86_64", + "libcap2@2.70-1.1.x86_64", + "libgcc_s1@14.1.0+git10173-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:49d76f9ea46de0f35908d343c7406bb7" + }, + { + "ID": "permissions@1699_20240522-1.1.x86_64", + "Name": "permissions", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/permissions@1699_20240522-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "971d93fae8da6b23" + }, + "Version": "1699_20240522", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "permissions", + "SrcVersion": "1699_20240522", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "permctl@1699_20240522-1.1.x86_64", + "permissions-config@1699_20240522-1.1.noarch" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:2c59bec9933e17b3209084584952c928" + }, + { + "ID": "permissions-config@1699_20240522-1.1.noarch", + "Name": "permissions-config", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/permissions-config@1699_20240522-1.1?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "8bd3994be34b3e73" + }, + "Version": "1699_20240522", + "Release": "1.1", + "Arch": "noarch", + "SrcName": "permissions", + "SrcVersion": "1699_20240522", + "SrcRelease": "1.1", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "fillup@1.42-281.1.x86_64", + "permctl@1699_20240522-1.1.x86_64", + "system-user-root@20190513-2.16.noarch" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:caef39a5d99ae750fe42bc67aed9f6ee" + }, + { + "ID": "pinentry@1.2.1-3.5.x86_64", + "Name": "pinentry", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/pinentry@1.2.1-3.5?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "90686edea2822ef8" + }, + "Version": "1.2.1", + "Release": "3.5", + "Arch": "x86_64", + "SrcName": "pinentry", + "SrcVersion": "1.2.1", + "SrcRelease": "3.5", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "glibc@2.39-9.1.x86_64", + "libassuan0@2.5.7-1.1.x86_64", + "libgpg-error0@1.49-1.1.x86_64", + "libncurses6@6.5.20240601-38.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:db417d208d0b858b5611d47cd5821d2e" + }, + { + "ID": "procps@3.3.17-17.1.x86_64", + "Name": "procps", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/procps@3.3.17-17.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "41a25e357a85fe17" + }, + "Version": "3.3.17", + "Release": "17.1", + "Arch": "x86_64", + "SrcName": "procps", + "SrcVersion": "3.3.17", + "SrcRelease": "17.1", + "Licenses": [ + "GPL-2.0-or-later AND LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libncurses6@6.5.20240601-38.1.x86_64", + "libprocps8@3.3.17-17.1.x86_64", + "libsystemd0@255.7-2.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:403b299eef7df6392759bf64fb10e0dd" + }, + { + "ID": "rpm@4.19.1.1-3.2.x86_64", + "Name": "rpm", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/rpm@4.19.1.1-3.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "6385ed7e7827135a" + }, + "Version": "4.19.1.1", + "Release": "3.2", + "Arch": "x86_64", + "SrcName": "rpm", + "SrcVersion": "4.19.1.1", + "SrcRelease": "3.2", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "fillup@1.42-281.1.x86_64", + "glibc@2.39-9.1.x86_64", + "libacl1@2.3.2-2.1.x86_64", + "libbz2-1@1.0.8-5.10.x86_64", + "libcap2@2.70-1.1.x86_64", + "libgcrypt20@1.10.3-3.3.x86_64", + "liblua5_4-5@5.4.6-3.3.x86_64", + "liblzma5@5.6.2-1.1.x86_64", + "libpopt0@1.19-1.8.x86_64", + "libselinux1@3.6-1.3.x86_64", + "libz1@1.3.1-1.1.x86_64", + "libzstd1@1.5.6-1.1.x86_64", + "rpm-config-SUSE@20240214-1.2.noarch" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:a37d0b995bba9a67c3befa295174bbae" + }, + { + "ID": "rpm-config-SUSE@20240214-1.2.noarch", + "Name": "rpm-config-SUSE", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/rpm-config-SUSE@20240214-1.2?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "b0a53b3b9cd8de6e" + }, + "Version": "20240214", + "Release": "1.2", + "Arch": "noarch", + "SrcName": "rpm-config-SUSE", + "SrcVersion": "20240214", + "SrcRelease": "1.2", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "bash@5.2.26-12.1.x86_64", + "rpm@4.19.1.1-3.2.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:94a64395a31621229c585a039d2491fd" + }, + { + "ID": "sed@4.9-2.6.x86_64", + "Name": "sed", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/sed@4.9-2.6?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "465c6c9c97824acd" + }, + "Version": "4.9", + "Release": "2.6", + "Arch": "x86_64", + "SrcName": "sed", + "SrcVersion": "4.9", + "SrcRelease": "2.6", + "Licenses": [ + "GPL-3.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libacl1@2.3.2-2.1.x86_64", + "libselinux1@3.6-1.3.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:8db04d2501d1712e4c7dd2352792ca30" + }, + { + "ID": "shadow@4.15.1-1.2.x86_64", + "Name": "shadow", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/shadow@4.15.1-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "7fefaa914168ef4f" + }, + "Version": "4.15.1", + "Release": "1.2", + "Arch": "x86_64", + "SrcName": "shadow", + "SrcVersion": "4.15.1", + "SrcRelease": "1.2", + "Licenses": [ + "BSD-3-Clause AND GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "glibc@2.39-9.1.x86_64", + "libacl1@2.3.2-2.1.x86_64", + "libattr1@2.5.2-1.2.x86_64", + "libaudit1@3.1.1-1.6.x86_64", + "libcrypt1@4.4.36-1.6.x86_64", + "libeconf0@0.6.3-1.1.x86_64", + "libselinux1@3.6-1.3.x86_64", + "libsemanage2@3.6-2.1.x86_64", + "libsubid4@4.15.1-1.2.x86_64", + "login_defs@4.15.1-1.2.noarch", + "pam@1.6.1-1.1.x86_64", + "permissions@1699_20240522-1.1.x86_64", + "system-user-root@20190513-2.16.noarch" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:b7bae5d8f8659004b37d62172e7eeb2a" + }, + { + "ID": "system-user-root@20190513-2.16.noarch", + "Name": "system-user-root", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/system-user-root@20190513-2.16?arch=noarch&distro=opensuse.tumbleweed-20240607", + "UID": "cc450033801f0db5" + }, + "Version": "20190513", + "Release": "2.16", + "Arch": "noarch", + "SrcName": "system-user-root", + "SrcVersion": "20190513", + "SrcRelease": "2.16", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:7f2d689d313623f89185902b38a792a3" + }, + { + "ID": "tar@1.35-2.2.x86_64", + "Name": "tar", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/tar@1.35-2.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "6f7d60b91f9b815f" + }, + "Version": "1.35", + "Release": "2.2", + "Arch": "x86_64", + "SrcName": "tar", + "SrcVersion": "1.35", + "SrcRelease": "2.2", + "Licenses": [ + "GPL-3.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "glibc@2.39-9.1.x86_64", + "libacl1@2.3.2-2.1.x86_64", + "libselinux1@3.6-1.3.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:b142350a0daf904f030454faaa597fa9" + }, + { + "ID": "terminfo-base@6.5.20240601-38.1.x86_64", + "Name": "terminfo-base", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/terminfo-base@6.5.20240601-38.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "ba53240ca965e6c0" + }, + "Version": "6.5.20240601", + "Release": "38.1", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.5.20240601", + "SrcRelease": "38.1", + "Licenses": [ + "MIT" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "libncurses6@6.5.20240601-38.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:a7a588007125716921186f1376a3a99d" + }, + { + "ID": "timezone@2024a-3.2.x86_64", + "Name": "timezone", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/timezone@2024a-3.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "aa7fc225c615b895" + }, + "Version": "2024a", + "Release": "3.2", + "Arch": "x86_64", + "SrcName": "timezone", + "SrcVersion": "2024a", + "SrcRelease": "3.2", + "Licenses": [ + "BSD-3-Clause AND SUSE-Public-Domain" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "glibc@2.39-9.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:e42ee9cfefcfaacbc7ea8c3bbdebdc51" + }, + { + "ID": "util-linux@2.40.1-2.1.x86_64", + "Name": "util-linux", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/util-linux@2.40.1-2.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "1440e3eb3dfc6c5" + }, + "Version": "2.40.1", + "Release": "2.1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.40.1", + "SrcRelease": "2.1", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "glibc@2.39-9.1.x86_64", + "libaudit1@3.1.1-1.6.x86_64", + "libblkid1@2.40.1-2.1.x86_64", + "libcap-ng0@0.8.5-1.1.x86_64", + "libcrypt1@4.4.36-1.6.x86_64", + "libeconf0@0.6.3-1.1.x86_64", + "libfdisk1@2.40.1-2.1.x86_64", + "libmagic1@5.45-2.2.x86_64", + "libmount1@2.40.1-2.1.x86_64", + "libncurses6@6.5.20240601-38.1.x86_64", + "libreadline8@8.2.10-1.3.x86_64", + "libselinux1@3.6-1.3.x86_64", + "libsmartcols1@2.40.1-2.1.x86_64", + "libuuid1@2.40.1-2.1.x86_64", + "libz1@1.3.1-1.1.x86_64", + "pam@1.6.1-1.1.x86_64", + "permissions@1699_20240522-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:5af635f5e09e64de185fcf61a9de50db" + }, + { + "ID": "xz@5.6.2-1.1.x86_64", + "Name": "xz", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/xz@5.6.2-1.1?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "1c46963e750a4a9" + }, + "Version": "5.6.2", + "Release": "1.1", + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "5.6.2", + "SrcRelease": "1.1", + "Licenses": [ + "0BSD AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-2.1-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "glibc@2.39-9.1.x86_64", + "liblzma5@5.6.2-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:5ce76c6cea39cdcbe3cc708d3258b77c" + }, + { + "ID": "zypper@1.14.73-1.2.x86_64", + "Name": "zypper", + "Identifier": { + "PURL": "pkg:rpm/opensuse.tumbleweed/zypper@1.14.73-1.2?arch=x86_64&distro=opensuse.tumbleweed-20240607", + "UID": "9d7cafcab0f1fed2" + }, + "Version": "1.14.73", + "Release": "1.2", + "Arch": "x86_64", + "SrcName": "zypper", + "SrcVersion": "1.14.73", + "SrcRelease": "1.2", + "Licenses": [ + "GPL-2.0-or-later" + ], + "Maintainer": "openSUSE", + "DependsOn": [ + "bash-sh@5.2.26-12.1.noarch", + "glibc@2.39-9.1.x86_64", + "libaugeas0@1.14.1-1.3.x86_64", + "libgcc_s1@14.1.0+git10173-1.1.x86_64", + "libreadline8@8.2.10-1.3.x86_64", + "libstdc++6@14.1.0+git10173-1.1.x86_64", + "libxml2-2@2.12.7-1.1.x86_64", + "libzypp@17.34.1-1.1.x86_64" + ], + "Layer": { + "Digest": "sha256:427d16a14c45614f51357aeebee0dfe209a1cebfc044b3b724b6ea35663b3111", + "DiffID": "sha256:7a335bdf2d91d6d158da360054aa7e477d708187d43fe9d0ac20144cdf90f763" + }, + "Digest": "md5:56cc7d6f2268fe4017122c12b9a19240" + } +] From ab0fd0d2e7daf35096b131e9d4aa180e1c15114e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:10:37 +0400 Subject: [PATCH 222/352] chore(deps): bump the docker group with 2 updates (#7116) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: DmitriyLewen --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- integration/docker_engine_test.go | 8 ++++---- pkg/fanal/analyzer/buildinfo/dockerfile.go | 2 +- pkg/fanal/test/integration/docker/docker.go | 12 +++++++----- pkg/fanal/test/integration/library_test.go | 6 +++--- 6 files changed, 29 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 8715db94813e..fb7cca2025db 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/cheggaaa/pb/v3 v3.1.5 github.com/containerd/containerd v1.7.18 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 - github.com/docker/docker v26.1.4+incompatible + github.com/docker/docker v27.0.3+incompatible github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.17.0 github.com/go-git/go-git/v5 v5.12.0 @@ -86,7 +86,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 - github.com/moby/buildkit v0.13.2 + github.com/moby/buildkit v0.14.1 github.com/open-policy-agent/opa v0.66.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 @@ -106,8 +106,8 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.31.0 - github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0 + github.com/testcontainers/testcontainers-go v0.32.0 + github.com/testcontainers/testcontainers-go/modules/localstack v0.32.0 github.com/tetratelabs/wazero v1.7.3 github.com/twitchtv/twirp v8.1.3+incompatible github.com/xeipuuv/gojsonschema v1.2.0 diff --git a/go.sum b/go.sum index 549eb3305577..ed9263ecb22c 100644 --- a/go.sum +++ b/go.sum @@ -1070,8 +1070,8 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= -github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE= +github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= @@ -1689,8 +1689,8 @@ github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQ 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/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.13.2 h1:nXNszM4qD9E7QtG7bFWPnDI1teUQFQglBzon/IU3SzI= -github.com/moby/buildkit v0.13.2/go.mod h1:2cyVOv9NoHM7arphK9ZfHIWKn9YVZRFd1wXB8kKmEzY= +github.com/moby/buildkit v0.14.1 h1:2epLCZTkn4CikdImtsLtIa++7DzCimrrZCT1sway+oI= +github.com/moby/buildkit v0.14.1/go.mod h1:1XssG7cAqv5Bz1xcGMxJL123iCv5TYN4Z/qf647gfuk= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= @@ -2011,10 +2011,10 @@ github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BG github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= -github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U= -github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI= -github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0 h1:pPz0J5Gbu7eAirpWP7QDT/v3s0zpNb/sNA8Ww/rjkoQ= -github.com/testcontainers/testcontainers-go/modules/localstack v0.31.0/go.mod h1:vqOXktUtHpTte9ilzE5enoUO8wt4FYDpZ3ARIAp28PM= +github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME= +github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E= +github.com/testcontainers/testcontainers-go/modules/localstack v0.32.0 h1:FITjE+DSDD136HQho7ThA6cEtUouZzDf7FvMBL2Muog= +github.com/testcontainers/testcontainers-go/modules/localstack v0.32.0/go.mod h1:JasdXHmUT8MTDYfyJza3JjO/k+QA3m8K2GQfnFQM++g= github.com/tetratelabs/wazero v1.7.3 h1:PBH5KVahrt3S2AHgEjKu4u+LlDbbk+nsGE3KLucy6Rw= github.com/tetratelabs/wazero v1.7.3/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= @@ -2972,8 +2972,8 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= helm.sh/helm/v3 v3.15.2 h1:/3XINUFinJOBjQplGnjw92eLGpgXXp1L8chWPkCkDuw= helm.sh/helm/v3 v3.15.2/go.mod h1:FzSIP8jDQaa6WAVg9F+OkKz7J0ZmAga4MABtTbsb9WQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/integration/docker_engine_test.go b/integration/docker_engine_test.go index 79c89c5bc521..5b62d391eead 100644 --- a/integration/docker_engine_test.go +++ b/integration/docker_engine_test.go @@ -12,7 +12,7 @@ import ( "github.com/aquasecurity/trivy/pkg/types" - api "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "github.com/stretchr/testify/require" ) @@ -243,7 +243,7 @@ func TestDockerEngine(t *testing.T) { require.NoError(t, err, tt.name) // ensure image doesnt already exists - _, _ = cli.ImageRemove(ctx, tt.input, api.ImageRemoveOptions{ + _, _ = cli.ImageRemove(ctx, tt.input, image.RemoveOptions{ Force: true, PruneChildren: true, }) @@ -262,11 +262,11 @@ func TestDockerEngine(t *testing.T) { // cleanup t.Cleanup(func() { - _, _ = cli.ImageRemove(ctx, tt.input, api.ImageRemoveOptions{ + _, _ = cli.ImageRemove(ctx, tt.input, image.RemoveOptions{ Force: true, PruneChildren: true, }) - _, _ = cli.ImageRemove(ctx, tt.imageTag, api.ImageRemoveOptions{ + _, _ = cli.ImageRemove(ctx, tt.imageTag, image.RemoveOptions{ Force: true, PruneChildren: true, }) diff --git a/pkg/fanal/analyzer/buildinfo/dockerfile.go b/pkg/fanal/analyzer/buildinfo/dockerfile.go index b14198aa3dd8..f17e5987b1b8 100644 --- a/pkg/fanal/analyzer/buildinfo/dockerfile.go +++ b/pkg/fanal/analyzer/buildinfo/dockerfile.go @@ -31,7 +31,7 @@ func (a dockerfileAnalyzer) Analyze(_ context.Context, target analyzer.AnalysisI return nil, xerrors.Errorf("dockerfile parse error: %w", err) } - stages, metaArgs, err := instructions.Parse(dockerfile.AST) + stages, metaArgs, err := instructions.Parse(dockerfile.AST, nil) if err != nil { return nil, xerrors.Errorf("instruction parse error: %w", err) } diff --git a/pkg/fanal/test/integration/docker/docker.go b/pkg/fanal/test/integration/docker/docker.go index 772aef0778f2..82a1671fb8d1 100644 --- a/pkg/fanal/test/integration/docker/docker.go +++ b/pkg/fanal/test/integration/docker/docker.go @@ -10,7 +10,7 @@ import ( "os" "os/exec" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/image" apiregistry "github.com/docker/docker/api/types/registry" "github.com/docker/docker/client" ) @@ -72,7 +72,7 @@ func (d Docker) Logout(conf RegistryConfig) error { // ReplicateImage tags the given imagePath and pushes it to the given dest registry. func (d Docker) ReplicateImage(ctx context.Context, imageRef, imagePath string, dest RegistryConfig) error { // remove existing Image if any - _, _ = d.cli.ImageRemove(ctx, imageRef, types.ImageRemoveOptions{ + _, _ = d.cli.ImageRemove(ctx, imageRef, image.RemoveOptions{ Force: true, PruneChildren: true, }) @@ -99,11 +99,11 @@ func (d Docker) ReplicateImage(ctx context.Context, imageRef, imagePath string, return err } defer func() { - _, _ = d.cli.ImageRemove(ctx, imageRef, types.ImageRemoveOptions{ + _, _ = d.cli.ImageRemove(ctx, imageRef, image.RemoveOptions{ Force: true, PruneChildren: true, }) - _, _ = d.cli.ImageRemove(ctx, targetImageRef, types.ImageRemoveOptions{ + _, _ = d.cli.ImageRemove(ctx, targetImageRef, image.RemoveOptions{ Force: true, PruneChildren: true, }) @@ -114,7 +114,9 @@ func (d Docker) ReplicateImage(ctx context.Context, imageRef, imagePath string, return err } - pushOut, err := d.cli.ImagePush(ctx, targetImageRef, types.ImagePushOptions{RegistryAuth: auth}) + pushOut, err := d.cli.ImagePush(ctx, targetImageRef, image.PushOptions{ + RegistryAuth: auth, + }) if err != nil { return err } diff --git a/pkg/fanal/test/integration/library_test.go b/pkg/fanal/test/integration/library_test.go index 5c5c3fd7615d..cf7ed95a1679 100644 --- a/pkg/fanal/test/integration/library_test.go +++ b/pkg/fanal/test/integration/library_test.go @@ -13,7 +13,7 @@ import ( "strings" "testing" - dtypes "github.com/docker/docker/api/types" + dimage "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -153,7 +153,7 @@ func TestFanal_Library_DockerLessMode(t *testing.T) { require.NoError(t, err) // remove existing Image if any - _, _ = cli.ImageRemove(ctx, tt.remoteImageName, dtypes.ImageRemoveOptions{ + _, _ = cli.ImageRemove(ctx, tt.remoteImageName, dimage.RemoveOptions{ Force: true, PruneChildren: true, }) @@ -239,7 +239,7 @@ func TestFanal_Library_DockerMode(t *testing.T) { // clear Cache require.NoError(t, c.Clear(), tt.name) - _, _ = cli.ImageRemove(ctx, tt.remoteImageName, dtypes.ImageRemoveOptions{ + _, _ = cli.ImageRemove(ctx, tt.remoteImageName, dimage.RemoveOptions{ Force: true, PruneChildren: true, }) From 5a9f1a66eea40a20b4d840fa2d7b36d1a7c54a04 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:03:08 +0600 Subject: [PATCH 223/352] refactor(secret): move warning about file size after `IsBinary` check (#7123) --- pkg/fanal/analyzer/secret/secret.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/fanal/analyzer/secret/secret.go b/pkg/fanal/analyzer/secret/secret.go index e26caaea5401..d2627a840c1b 100644 --- a/pkg/fanal/analyzer/secret/secret.go +++ b/pkg/fanal/analyzer/secret/secret.go @@ -100,6 +100,10 @@ func (a *SecretAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput return nil, nil } + if size := input.Info.Size(); size > 10485760 { // 10MB + log.WithPrefix("secret").Warn("The size of the scanned file is too large. It is recommended to use `--skip-files` for this file to avoid high memory consumption.", log.FilePath(input.FilePath), log.Int64("size (MB)", size/1048576)) + } + content, err := io.ReadAll(input.Content) if err != nil { return nil, xerrors.Errorf("read error %s: %w", input.FilePath, err) @@ -166,9 +170,6 @@ func (a *SecretAnalyzer) Required(filePath string, fi os.FileInfo) bool { return false } - if size := fi.Size(); size > 10485760 { // 10MB - log.WithPrefix("secret").Warn("The size of the scanned file is too large. It is recommended to use `--skip-files` for this file to avoid high memory consumption.", log.FilePath(filePath), log.Int64("size (MB)", size/1048576)) - } return true } From 7cbdb0a0b5dff33e506e1c1f3119951fa241b432 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:06:29 +0600 Subject: [PATCH 224/352] feat(cli): rename `--vuln-type` flag to `--pkg-types` flag (#7104) --- .../configuration/cli/trivy_filesystem.md | 2 +- .../configuration/cli/trivy_image.md | 2 +- .../configuration/cli/trivy_kubernetes.md | 2 +- .../configuration/cli/trivy_repository.md | 2 +- .../configuration/cli/trivy_rootfs.md | 2 +- .../configuration/cli/trivy_sbom.md | 2 +- .../references/configuration/cli/trivy_vm.md | 2 +- .../references/configuration/config-file.md | 13 +-- docs/docs/scanner/vulnerability.md | 4 +- pkg/commands/app.go | 3 + pkg/commands/artifact/run.go | 8 +- pkg/flag/report_flags.go | 19 +++++ pkg/flag/report_flags_test.go | 25 ++++++ pkg/flag/vulnerability_flags.go | 19 ----- pkg/flag/vulnerability_flags_test.go | 68 --------------- pkg/k8s/scanner/scanner.go | 2 +- pkg/rpc/client/client.go | 2 +- pkg/rpc/client/client_test.go | 4 +- pkg/rpc/server/server.go | 2 +- pkg/scanner/local/scan.go | 4 +- pkg/scanner/local/scan_test.go | 42 +++++----- pkg/scanner/scan_test.go | 10 +-- pkg/types/scan.go | 2 +- pkg/types/target.go | 22 ++--- rpc/scanner/service.pb.go | 12 +-- rpc/scanner/service.proto | 2 +- rpc/scanner/service.twirp.go | 84 +++++++++---------- 27 files changed, 161 insertions(+), 200 deletions(-) delete mode 100644 pkg/flag/vulnerability_flags_test.go diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index ae88ed8ca83b..2fb1ad1c984c 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -63,6 +63,7 @@ trivy filesystem [flags] PATH --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend @@ -89,7 +90,6 @@ trivy filesystem [flags] PATH --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. --vex string [EXPERIMENTAL] file path to VEX - --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 8c3fe309f929..2ae526d9405c 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -81,6 +81,7 @@ trivy image [flags] IMAGE_NAME --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) --platform string set platform in the form os/arch if image is multi-platform capable --podman-host string unix podman socket path to use for podman scanning --redis-ca string redis ca file location, if using redis as cache backend @@ -109,7 +110,6 @@ trivy image [flags] IMAGE_NAME --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. --vex string [EXPERIMENTAL] file path to VEX - --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 201eee466765..3f20a33e866d 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -78,6 +78,7 @@ trivy kubernetes [flags] [CONTEXT] --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) --qps float specify the maximum QPS to the master from this client (default 5) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend @@ -103,7 +104,6 @@ trivy kubernetes [flags] [CONTEXT] --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. --vex string [EXPERIMENTAL] file path to VEX - --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index cf85082bee7f..4831c2bad4ae 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -63,6 +63,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend @@ -89,7 +90,6 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. --vex string [EXPERIMENTAL] file path to VEX - --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 6ab7705ff633..ca433b327f0d 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -65,6 +65,7 @@ trivy rootfs [flags] ROOTDIR --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. + --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend @@ -90,7 +91,6 @@ trivy rootfs [flags] ROOTDIR --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. --vex string [EXPERIMENTAL] file path to VEX - --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index 2adbd12a253a..3d4d25e47fcb 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -43,6 +43,7 @@ trivy sbom [flags] SBOM_PATH --offline-scan do not issue API requests to identify dependencies -o, --output string output file name --output-plugin-arg string [EXPERIMENTAL] output plugin arguments + --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend @@ -61,7 +62,6 @@ trivy sbom [flags] SBOM_PATH --token string for authentication in client/server mode --token-header string specify a header name for token in client/server mode (default "Trivy-Token") --vex string [EXPERIMENTAL] file path to VEX - --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 5ad96c87b0df..dab35eeb93e1 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -56,6 +56,7 @@ trivy vm [flags] VM_IMAGE -o, --output string output file name --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) + --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend @@ -76,7 +77,6 @@ trivy vm [flags] VM_IMAGE --token string for authentication in client/server mode --token-header string specify a header name for token in client/server mode (default "Trivy-Token") --vex string [EXPERIMENTAL] file path to VEX - --vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library]) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index 1a7020d94fe4..f3c0006bfe07 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -81,6 +81,13 @@ severity: - MEDIUM - HIGH - CRITICAL + +# Same as '--pkg-types' +# Default is 'os,library' +pkg-types: + - os + - library + scan: # Same as '--compliance' @@ -261,12 +268,6 @@ Available with vulnerability scanning ```yaml vulnerability: - # Same as '--vuln-type' - # Default is 'os,library' - type: - - os - - library - # Same as '--ignore-unfixed' # Default is false ignore-unfixed: false diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md index 55403dda2207..ef233b4db4da 100644 --- a/docs/docs/scanner/vulnerability.md +++ b/docs/docs/scanner/vulnerability.md @@ -204,7 +204,7 @@ Other common options are documented [here](../configuration/index.md). ### Enabling a subset of package types It's possible to only enable certain package types if you prefer. -You can do so by passing the `--vuln-type` option. +You can do so by passing the `--pkg-types` option. This flag takes a comma-separated list of package types. Available values: @@ -215,7 +215,7 @@ Available values: - Scan language-specific packages (e.g. packages installed by `pip`, `npm`, or `gem`). ```bash -$ trivy image --vuln-type os ruby:2.4.0 +$ trivy image --pkg-types os ruby:2.4.0 ``` diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 92483babb293..5ca651193c35 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -512,6 +512,8 @@ func NewConvertCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { ScanFlagGroup: &flag.ScanFlagGroup{}, ReportFlagGroup: flag.NewReportFlagGroup(), } + convertFlags.ReportFlagGroup.PkgTypes = nil // disable '--pkg-types' + cmd := &cobra.Command{ Use: "convert [flags] RESULT_JSON", Aliases: []string{"conv"}, @@ -679,6 +681,7 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { configFlags.ReportFlagGroup.ListAllPkgs = nil // disable '--list-all-pkgs' configFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' configFlags.ReportFlagGroup.ShowSuppressed = nil // disable '--show-suppressed' + configFlags.ReportFlagGroup.PkgTypes = nil // disable '--pkg-types' configFlags.ReportFlagGroup.ReportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports configFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 82cbb439ecce..1343f4be61f0 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -201,7 +201,7 @@ func (r *runner) scanFS(ctx context.Context, opts flag.Options) (types.Report, e func (r *runner) ScanRepository(ctx context.Context, opts flag.Options) (types.Report, error) { // Do not scan OS packages - opts.VulnType = []string{types.VulnTypeLibrary} + opts.PkgTypes = []string{types.PkgTypeLibrary} // Disable the OS analyzers, individual package analyzers and SBOM analyzer opts.DisabledAnalyzers = append(analyzer.TypeIndividualPkgs, analyzer.TypeOSes...) @@ -405,7 +405,7 @@ func disabledAnalyzers(opts flag.Options) []analyzer.Type { } // Do not analyze programming language packages when not running in 'library' - if !slices.Contains(opts.VulnType, types.VulnTypeLibrary) { + if !slices.Contains(opts.PkgTypes, types.PkgTypeLibrary) { analyzers = append(analyzers, analyzer.TypeLanguages...) } @@ -473,7 +473,7 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan } scanOptions := types.ScanOptions{ - VulnType: opts.VulnType, + PkgTypes: opts.PkgTypes, Scanners: opts.Scanners, ImageConfigScanners: opts.ImageConfigScanners, // this is valid only for 'image' subcommand ScanRemovedPackages: opts.ScanRemovedPkgs, // this is valid only for 'image' subcommand @@ -488,7 +488,7 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan if opts.Scanners.Enabled(types.VulnerabilityScanner) { log.Info("Vulnerability scanning is enabled") - log.Debug("Vulnerability type", log.Any("type", scanOptions.VulnType)) + log.Debug("Package types", log.Any("types", scanOptions.PkgTypes)) } // ScannerOption is filled only when config scanning is enabled. diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go index ce833cc1b13e..b82cc13e706b 100644 --- a/pkg/flag/report_flags.go +++ b/pkg/flag/report_flags.go @@ -106,6 +106,20 @@ var ( ConfigName: "scan.show-suppressed", Usage: "[EXPERIMENTAL] show suppressed vulnerabilities", } + PkgTypesFlag = Flag[[]string]{ + Name: "pkg-types", + ConfigName: "pkg-types", + Default: types.PkgTypes, + Values: types.PkgTypes, + Usage: "comma-separated list of package types", + Aliases: []Alias{ + { + Name: "vuln-type", + ConfigName: "vulnerability.type", + Deprecated: true, // --vuln-type was renamed to --pkg-types + }, + }, + } ) // ReportFlagGroup composes common printer flag structs @@ -125,6 +139,7 @@ type ReportFlagGroup struct { Severity *Flag[[]string] Compliance *Flag[string] ShowSuppressed *Flag[bool] + PkgTypes *Flag[[]string] } type ReportOptions struct { @@ -142,6 +157,7 @@ type ReportOptions struct { Severities []dbTypes.Severity Compliance spec.ComplianceSpec ShowSuppressed bool + PkgTypes []string } func NewReportFlagGroup() *ReportFlagGroup { @@ -160,6 +176,7 @@ func NewReportFlagGroup() *ReportFlagGroup { Severity: SeverityFlag.Clone(), Compliance: ComplianceFlag.Clone(), ShowSuppressed: ShowSuppressedFlag.Clone(), + PkgTypes: PkgTypesFlag.Clone(), } } @@ -183,6 +200,7 @@ func (f *ReportFlagGroup) Flags() []Flagger { f.Severity, f.Compliance, f.ShowSuppressed, + f.PkgTypes, } } @@ -252,6 +270,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { Severities: toSeverity(f.Severity.Value()), Compliance: cs, ShowSuppressed: f.ShowSuppressed.Value(), + PkgTypes: f.PkgTypes.Value(), }, nil } diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go index 6e56904c4dcf..108d95e22a70 100644 --- a/pkg/flag/report_flags_test.go +++ b/pkg/flag/report_flags_test.go @@ -31,6 +31,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { severities string compliance string debug bool + pkgTypes string } tests := []struct { name string @@ -159,6 +160,28 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { Severities: []dbTypes.Severity{dbTypes.SeverityLow}, }, }, + { + name: "happy path for OS packages", + fields: fields{ + pkgTypes: "os", + }, + want: flag.ReportOptions{ + PkgTypes: []string{ + types.PkgTypeOS, + }, + }, + }, + { + name: "happy path for library packages", + fields: fields{ + pkgTypes: "library", + }, + want: flag.ReportOptions{ + PkgTypes: []string{ + types.PkgTypeLibrary, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -183,6 +206,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { setValue(flag.OutputPluginArgFlag.ConfigName, tt.fields.outputPluginArgs) setValue(flag.SeverityFlag.ConfigName, tt.fields.severities) setValue(flag.ComplianceFlag.ConfigName, tt.fields.compliance) + setValue(flag.PkgTypesFlag.ConfigName, tt.fields.pkgTypes) // Assert options f := &flag.ReportFlagGroup{ @@ -198,6 +222,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { OutputPluginArg: flag.OutputPluginArgFlag.Clone(), Severity: flag.SeverityFlag.Clone(), Compliance: flag.ComplianceFlag.Clone(), + PkgTypes: flag.PkgTypesFlag.Clone(), } got, err := f.ToOptions() diff --git a/pkg/flag/vulnerability_flags.go b/pkg/flag/vulnerability_flags.go index f0db9e7b70c3..0e9d8291d059 100644 --- a/pkg/flag/vulnerability_flags.go +++ b/pkg/flag/vulnerability_flags.go @@ -5,23 +5,9 @@ import ( dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/types" ) var ( - VulnTypeFlag = Flag[[]string]{ - Name: "vuln-type", - ConfigName: "vulnerability.type", - Default: []string{ - types.VulnTypeOS, - types.VulnTypeLibrary, - }, - Values: []string{ - types.VulnTypeOS, - types.VulnTypeLibrary, - }, - Usage: "comma-separated list of vulnerability types", - } IgnoreUnfixedFlag = Flag[bool]{ Name: "ignore-unfixed", ConfigName: "vulnerability.ignore-unfixed", @@ -42,21 +28,18 @@ var ( ) type VulnerabilityFlagGroup struct { - VulnType *Flag[[]string] IgnoreUnfixed *Flag[bool] IgnoreStatus *Flag[[]string] VEXPath *Flag[string] } type VulnerabilityOptions struct { - VulnType []string IgnoreStatuses []dbTypes.Status VEXPath string } func NewVulnerabilityFlagGroup() *VulnerabilityFlagGroup { return &VulnerabilityFlagGroup{ - VulnType: VulnTypeFlag.Clone(), IgnoreUnfixed: IgnoreUnfixedFlag.Clone(), IgnoreStatus: IgnoreStatusFlag.Clone(), VEXPath: VEXFlag.Clone(), @@ -69,7 +52,6 @@ func (f *VulnerabilityFlagGroup) Name() string { func (f *VulnerabilityFlagGroup) Flags() []Flagger { return []Flagger{ - f.VulnType, f.IgnoreUnfixed, f.IgnoreStatus, f.VEXPath, @@ -105,7 +87,6 @@ func (f *VulnerabilityFlagGroup) ToOptions() (VulnerabilityOptions, error) { log.Debug("Ignore statuses", log.Any("statuses", ignoreStatuses)) return VulnerabilityOptions{ - VulnType: f.VulnType.Value(), IgnoreStatuses: ignoreStatuses, VEXPath: f.VEXPath.Value(), }, nil diff --git a/pkg/flag/vulnerability_flags_test.go b/pkg/flag/vulnerability_flags_test.go deleted file mode 100644 index a1bcd90b3473..000000000000 --- a/pkg/flag/vulnerability_flags_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package flag_test - -import ( - "testing" - - "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" - "github.com/aquasecurity/trivy/pkg/types" -) - -func TestVulnerabilityFlagGroup_ToOptions(t *testing.T) { - type fields struct { - vulnType string - } - tests := []struct { - name string - args []string - fields fields - want flag.VulnerabilityOptions - wantLogs []string - }{ - { - name: "happy path for OS vulnerabilities", - args: []string{"alpine:latest"}, - fields: fields{ - vulnType: "os", - }, - want: flag.VulnerabilityOptions{ - VulnType: []string{types.VulnTypeOS}, - }, - }, - { - name: "happy path for library vulnerabilities", - args: []string{"alpine:latest"}, - fields: fields{ - vulnType: "library", - }, - want: flag.VulnerabilityOptions{ - VulnType: []string{types.VulnTypeLibrary}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - out := newLogger(log.LevelWarn) - - viper.Set(flag.VulnTypeFlag.ConfigName, tt.fields.vulnType) - - // Assert options - f := &flag.VulnerabilityFlagGroup{ - VulnType: flag.VulnTypeFlag.Clone(), - } - - got, err := f.ToOptions() - require.NoError(t, err) - assert.Equalf(t, tt.want, got, "ToOptions()") - - // Assert log messages - assert.Equal(t, tt.wantLogs, out.Messages(), tt.name) - }) - - } -} diff --git a/pkg/k8s/scanner/scanner.go b/pkg/k8s/scanner/scanner.go index 95761ad445a8..67d06b4c54bd 100644 --- a/pkg/k8s/scanner/scanner.go +++ b/pkg/k8s/scanner/scanner.go @@ -218,7 +218,7 @@ func (s *Scanner) scanK8sVulns(ctx context.Context, artifactsData []*artifacts.A k8sScanner := k8s.NewKubernetesScanner() scanOptions := types.ScanOptions{ Scanners: s.opts.Scanners, - VulnType: s.opts.VulnType, + PkgTypes: s.opts.PkgTypes, } for _, artifact := range artifactsData { switch artifact.Kind { diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index c2bcae2793a4..d1ac1d8d829f 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -82,7 +82,7 @@ func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys ArtifactId: artifactKey, BlobIds: blobKeys, Options: &rpc.ScanOptions{ - VulnType: opts.VulnType, + PkgTypes: opts.PkgTypes, Scanners: xstrings.ToStringSlice(opts.Scanners), LicenseCategories: licenseCategories, IncludeDevDeps: opts.IncludeDevDeps, diff --git a/pkg/rpc/client/client_test.go b/pkg/rpc/client/client_test.go index bf5036c6116d..639b6088b994 100644 --- a/pkg/rpc/client/client_test.go +++ b/pkg/rpc/client/client_test.go @@ -50,7 +50,7 @@ func TestScanner_Scan(t *testing.T) { imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - VulnType: []string{"os"}, + PkgTypes: []string{"os"}, }, }, expectation: &rpc.ScanResponse{ @@ -166,7 +166,7 @@ func TestScanner_Scan(t *testing.T) { imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - VulnType: []string{"os"}, + PkgTypes: []string{"os"}, }, }, wantErr: "failed to detect vulnerabilities via RPC", diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index eb29683942f5..25b43b2afd92 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -47,7 +47,7 @@ func (s *ScanServer) Scan(ctx context.Context, in *rpcScanner.ScanRequest) (*rpc return types.Scanner(s) }) options := types.ScanOptions{ - VulnType: in.Options.VulnType, + PkgTypes: in.Options.PkgTypes, Scanners: scanners, IncludeDevDeps: in.Options.IncludeDevDeps, } diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 18ba53eac7a0..7526ff48dd4c 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -160,7 +160,7 @@ func (s Scanner) scanVulnerabilities(ctx context.Context, target types.ScanTarge var eosl bool var results types.Results - if slices.Contains(options.VulnType, types.VulnTypeOS) { + if slices.Contains(options.PkgTypes, types.PkgTypeOS) { vuln, detectedEOSL, err := s.osPkgScanner.Scan(ctx, target, options) switch { case errors.Is(err, ospkgDetector.ErrUnsupportedOS): @@ -173,7 +173,7 @@ func (s Scanner) scanVulnerabilities(ctx context.Context, target types.ScanTarge } } - if slices.Contains(options.VulnType, types.VulnTypeLibrary) { + if slices.Contains(options.PkgTypes, types.PkgTypeLibrary) { vulns, err := s.langPkgScanner.Scan(ctx, target, options) if err != nil { return nil, false, xerrors.Errorf("failed to scan application libraries: %w", err) diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go index f0d154a3904d..a80a4bd281c8 100644 --- a/pkg/scanner/local/scan_test.go +++ b/pkg/scanner/local/scan_test.go @@ -94,9 +94,9 @@ func TestScanner_Scan(t *testing.T) { target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - VulnType: []string{ - types.VulnTypeOS, - types.VulnTypeLibrary, + PkgTypes: []string{ + types.PkgTypeOS, + types.PkgTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, @@ -294,9 +294,9 @@ func TestScanner_Scan(t *testing.T) { target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - VulnType: []string{ - types.VulnTypeOS, - types.VulnTypeLibrary, + PkgTypes: []string{ + types.PkgTypeOS, + types.PkgTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, @@ -377,7 +377,7 @@ func TestScanner_Scan(t *testing.T) { target: "./result.cdx", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - VulnType: []string{types.VulnTypeLibrary}, + PkgTypes: []string{types.PkgTypeLibrary}, Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, @@ -473,9 +473,9 @@ func TestScanner_Scan(t *testing.T) { target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - VulnType: []string{ - types.VulnTypeOS, - types.VulnTypeLibrary, + PkgTypes: []string{ + types.PkgTypeOS, + types.PkgTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, @@ -554,9 +554,9 @@ func TestScanner_Scan(t *testing.T) { target: "fedora:27", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - VulnType: []string{ - types.VulnTypeOS, - types.VulnTypeLibrary, + PkgTypes: []string{ + types.PkgTypeOS, + types.PkgTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, @@ -626,9 +626,9 @@ func TestScanner_Scan(t *testing.T) { target: "busybox:latest", layerIDs: []string{"sha256:a6d503001157aedc826853f9b67f26d35966221b158bff03849868ae4a821116"}, options: types.ScanOptions{ - VulnType: []string{ - types.VulnTypeOS, - types.VulnTypeLibrary, + PkgTypes: []string{ + types.PkgTypeOS, + types.PkgTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, @@ -650,7 +650,7 @@ func TestScanner_Scan(t *testing.T) { target: "alpine:latest", layerIDs: []string{"sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33"}, options: types.ScanOptions{ - VulnType: []string{types.VulnTypeLibrary}, + PkgTypes: []string{types.PkgTypeLibrary}, Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, @@ -896,9 +896,9 @@ func TestScanner_Scan(t *testing.T) { target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - VulnType: []string{ - types.VulnTypeOS, - types.VulnTypeLibrary, + PkgTypes: []string{ + types.PkgTypeOS, + types.PkgTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, @@ -920,7 +920,7 @@ func TestScanner_Scan(t *testing.T) { target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - VulnType: []string{types.VulnTypeLibrary}, + PkgTypes: []string{types.PkgTypeLibrary}, Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, diff --git a/pkg/scanner/scan_test.go b/pkg/scanner/scan_test.go index 287935ddd41f..6cfed72e43ea 100644 --- a/pkg/scanner/scan_test.go +++ b/pkg/scanner/scan_test.go @@ -30,7 +30,7 @@ func TestScanner_ScanArtifact(t *testing.T) { { name: "happy path", args: args{ - options: types.ScanOptions{VulnType: []string{"os"}}, + options: types.ScanOptions{PkgTypes: []string{"os"}}, }, inspectExpectation: artifact.ArtifactInspectExpectation{ Args: artifact.ArtifactInspectArgs{ @@ -57,7 +57,7 @@ func TestScanner_ScanArtifact(t *testing.T) { Target: "alpine:3.11", ImageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", LayerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - Options: types.ScanOptions{VulnType: []string{"os"}}, + Options: types.ScanOptions{PkgTypes: []string{"os"}}, }, Returns: DriverScanReturns{ Results: types.Results{ @@ -146,7 +146,7 @@ func TestScanner_ScanArtifact(t *testing.T) { { name: "sad path: AnalyzerAnalyze returns an error", args: args{ - options: types.ScanOptions{VulnType: []string{"os"}}, + options: types.ScanOptions{PkgTypes: []string{"os"}}, }, inspectExpectation: artifact.ArtifactInspectExpectation{ Args: artifact.ArtifactInspectArgs{ @@ -161,7 +161,7 @@ func TestScanner_ScanArtifact(t *testing.T) { { name: "sad path: Scan returns an error", args: args{ - options: types.ScanOptions{VulnType: []string{"os"}}, + options: types.ScanOptions{PkgTypes: []string{"os"}}, }, inspectExpectation: artifact.ArtifactInspectExpectation{ Args: artifact.ArtifactInspectArgs{ @@ -181,7 +181,7 @@ func TestScanner_ScanArtifact(t *testing.T) { Target: "alpine:3.11", ImageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", LayerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - Options: types.ScanOptions{VulnType: []string{"os"}}, + Options: types.ScanOptions{PkgTypes: []string{"os"}}, }, Returns: DriverScanReturns{ Err: errors.New("error"), diff --git a/pkg/types/scan.go b/pkg/types/scan.go index 04b6d6230230..2151cb9ccd24 100644 --- a/pkg/types/scan.go +++ b/pkg/types/scan.go @@ -22,7 +22,7 @@ type ScanTarget struct { // ScanOptions holds the attributes for scanning vulnerabilities type ScanOptions struct { - VulnType []string + PkgTypes []string Scanners Scanners ImageConfigScanners Scanners // Scanners for container image configuration ScanRemovedPackages bool diff --git a/pkg/types/target.go b/pkg/types/target.go index bb2dce90fc52..a8cccced3de7 100644 --- a/pkg/types/target.go +++ b/pkg/types/target.go @@ -4,8 +4,8 @@ import ( "slices" ) -// VulnType represents vulnerability type -type VulnType = string +// PkgType represents package type +type PkgType = string // Scanner represents the type of security scanning type Scanner string @@ -14,14 +14,14 @@ type Scanner string type Scanners []Scanner const ( - // VulnTypeUnknown is a vulnerability type of unknown - VulnTypeUnknown = VulnType("unknown") + // PkgTypeUnknown is a package type of unknown + PkgTypeUnknown = PkgType("unknown") - // VulnTypeOS is a vulnerability type of OS packages - VulnTypeOS = VulnType("os") + // PkgTypeOS is a package type of OS packages + PkgTypeOS = PkgType("os") - // VulnTypeLibrary is a vulnerability type of programming language dependencies - VulnTypeLibrary = VulnType("library") + // PkgTypeLibrary is a package type of programming language dependencies + PkgTypeLibrary = PkgType("library") // UnknownScanner is the scanner of unknown UnknownScanner = Scanner("unknown") @@ -49,9 +49,9 @@ const ( ) var ( - VulnTypes = []string{ - VulnTypeOS, - VulnTypeLibrary, + PkgTypes = []string{ + PkgTypeOS, + PkgTypeLibrary, } AllScanners = Scanners{ diff --git a/rpc/scanner/service.pb.go b/rpc/scanner/service.pb.go index 50d0ead38681..2f03d4727662 100644 --- a/rpc/scanner/service.pb.go +++ b/rpc/scanner/service.pb.go @@ -146,7 +146,7 @@ type ScanOptions struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - VulnType []string `protobuf:"bytes,1,rep,name=vuln_type,json=vulnType,proto3" json:"vuln_type,omitempty"` + PkgTypes []string `protobuf:"bytes,1,rep,name=pkg_types,json=pkgTypes,proto3" json:"pkg_types,omitempty"` Scanners []string `protobuf:"bytes,2,rep,name=scanners,proto3" json:"scanners,omitempty"` LicenseCategories map[string]*Licenses `protobuf:"bytes,4,rep,name=license_categories,json=licenseCategories,proto3" json:"license_categories,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` IncludeDevDeps bool `protobuf:"varint,5,opt,name=include_dev_deps,json=includeDevDeps,proto3" json:"include_dev_deps,omitempty"` @@ -184,9 +184,9 @@ func (*ScanOptions) Descriptor() ([]byte, []int) { return file_rpc_scanner_service_proto_rawDescGZIP(), []int{2} } -func (x *ScanOptions) GetVulnType() []string { +func (x *ScanOptions) GetPkgTypes() []string { if x != nil { - return x.VulnType + return x.PkgTypes } return nil } @@ -399,9 +399,9 @@ var file_rpc_scanner_service_proto_rawDesc = []byte{ 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x20, 0x0a, 0x08, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xbd, 0x02, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x4f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x75, 0x6c, 0x6e, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x76, 0x75, 0x6c, 0x6e, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x67, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6b, 0x67, 0x54, 0x79, + 0x70, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x63, 0x0a, 0x12, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x74, 0x72, diff --git a/rpc/scanner/service.proto b/rpc/scanner/service.proto index 312930adc89a..8fc1cdbd46ac 100644 --- a/rpc/scanner/service.proto +++ b/rpc/scanner/service.proto @@ -23,7 +23,7 @@ message Licenses { } message ScanOptions { - repeated string vuln_type = 1; + repeated string pkg_types = 1; repeated string scanners = 2; map license_categories = 4; bool include_dev_deps = 5; diff --git a/rpc/scanner/service.twirp.go b/rpc/scanner/service.twirp.go index 0a2fbe5759e7..c877fbe34523 100644 --- a/rpc/scanner/service.twirp.go +++ b/rpc/scanner/service.twirp.go @@ -1094,46 +1094,46 @@ func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) } var twirpFileDescriptor0 = []byte{ - // 648 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xdd, 0x4e, 0x1b, 0x3b, - 0x10, 0x56, 0x7e, 0x48, 0x36, 0x93, 0xa3, 0x43, 0xb0, 0xce, 0x41, 0x4b, 0x38, 0x9c, 0x46, 0xb9, - 0xa8, 0x72, 0x95, 0x94, 0xd0, 0xaa, 0x7f, 0x77, 0x05, 0x5a, 0x51, 0xb5, 0x02, 0x39, 0xa8, 0x17, - 0xbd, 0x49, 0x1d, 0xef, 0x90, 0x5a, 0x6c, 0x76, 0x17, 0x8f, 0x37, 0x52, 0x5e, 0xa5, 0xef, 0xd2, - 0xc7, 0xe8, 0xfb, 0x54, 0x6b, 0x7b, 0x11, 0x09, 0xd0, 0xab, 0xf5, 0xcc, 0x7c, 0xf3, 0xcd, 0x27, - 0xcf, 0xb7, 0x86, 0x3d, 0x9d, 0xc9, 0x11, 0x49, 0x91, 0x24, 0xa8, 0x47, 0x84, 0x7a, 0xa9, 0x24, - 0x0e, 0x33, 0x9d, 0x9a, 0x94, 0x75, 0x8c, 0x56, 0xcb, 0xd5, 0xd0, 0x17, 0x87, 0xcb, 0xc3, 0x6e, - 0x58, 0x80, 0x65, 0xba, 0x58, 0xa4, 0xc9, 0x3a, 0xb6, 0xff, 0xa3, 0x02, 0xed, 0x89, 0x14, 0x09, - 0xc7, 0x9b, 0x1c, 0xc9, 0xb0, 0x5d, 0x68, 0x18, 0xa1, 0xe7, 0x68, 0xc2, 0x4a, 0xaf, 0x32, 0x68, - 0x71, 0x1f, 0xb1, 0x27, 0xd0, 0x16, 0xda, 0xa8, 0x2b, 0x21, 0xcd, 0x54, 0x45, 0x61, 0xd5, 0x16, - 0xa1, 0x4c, 0x9d, 0x45, 0x6c, 0x0f, 0x82, 0x59, 0x9c, 0xce, 0xa6, 0x2a, 0xa2, 0xb0, 0xd6, 0xab, - 0x0d, 0x5a, 0xbc, 0x59, 0xc4, 0x67, 0x11, 0xb1, 0x97, 0xd0, 0x4c, 0x33, 0xa3, 0xd2, 0x84, 0xc2, - 0x7a, 0xaf, 0x32, 0x68, 0x8f, 0x0f, 0x86, 0x9b, 0x0a, 0x87, 0x85, 0x86, 0x73, 0x07, 0xe2, 0x25, - 0xba, 0xdf, 0x83, 0xe0, 0x93, 0x92, 0x98, 0x10, 0x12, 0xfb, 0x07, 0xb6, 0x12, 0xb1, 0x40, 0x0a, - 0x2b, 0x96, 0xdc, 0x05, 0xfd, 0x9f, 0x55, 0x27, 0xdf, 0xb7, 0xb2, 0x7d, 0x68, 0x2d, 0xf3, 0x38, - 0x99, 0x9a, 0x55, 0x86, 0x1e, 0x19, 0x14, 0x89, 0xcb, 0x55, 0x86, 0xac, 0x0b, 0x81, 0x9f, 0x48, - 0x61, 0xd5, 0xd5, 0xca, 0x98, 0x49, 0x60, 0xb1, 0x1b, 0x35, 0x95, 0xc2, 0xe0, 0x3c, 0xd5, 0x0a, - 0x0b, 0xb9, 0xb5, 0x41, 0x7b, 0xfc, 0xfc, 0x8f, 0x72, 0x87, 0x5e, 0xe2, 0xf1, 0x6d, 0xdb, 0x69, - 0x62, 0xf4, 0x8a, 0xef, 0xc4, 0x9b, 0x79, 0x36, 0x80, 0x8e, 0x4a, 0x64, 0x9c, 0x47, 0x38, 0x8d, - 0x70, 0x39, 0x8d, 0x30, 0xa3, 0x70, 0xab, 0x57, 0x19, 0x04, 0xfc, 0x6f, 0x9f, 0x3f, 0xc1, 0xe5, - 0x09, 0x66, 0xd4, 0xfd, 0x06, 0xbb, 0x0f, 0xd3, 0xb2, 0x0e, 0xd4, 0xae, 0x71, 0xe5, 0xb7, 0x53, - 0x1c, 0xd9, 0x33, 0xd8, 0x5a, 0x8a, 0x38, 0x47, 0xbb, 0x94, 0xf6, 0xb8, 0x7b, 0x5f, 0x6d, 0x79, - 0x89, 0xdc, 0x01, 0xdf, 0x54, 0x5f, 0x55, 0x3e, 0xd6, 0x83, 0x5a, 0xa7, 0xde, 0x8f, 0xe0, 0x2f, - 0xb7, 0x7d, 0xca, 0xd2, 0x84, 0x90, 0xf5, 0xa0, 0x9a, 0x92, 0x25, 0x6f, 0x8f, 0x3b, 0x9e, 0xc8, - 0xf9, 0x66, 0x78, 0x3e, 0xe1, 0xd5, 0x94, 0xd8, 0x18, 0x9a, 0x1a, 0x29, 0x8f, 0x8d, 0x5b, 0x73, - 0x7b, 0x1c, 0xde, 0x9f, 0xc7, 0x2d, 0x80, 0x97, 0xc0, 0xfe, 0xaf, 0x1a, 0x34, 0x5c, 0xee, 0x51, - 0x7f, 0x9d, 0xc2, 0x76, 0xb1, 0x27, 0xd4, 0x62, 0xa6, 0x62, 0x65, 0x8a, 0xcb, 0xaf, 0x5a, 0xfa, - 0xfd, 0x75, 0x15, 0x5f, 0xee, 0x80, 0x56, 0x7c, 0xb3, 0x87, 0x5d, 0xc2, 0xce, 0x42, 0x91, 0x4c, - 0x93, 0x2b, 0x35, 0xcf, 0xb5, 0x28, 0x4d, 0x57, 0x10, 0x3d, 0x5d, 0x27, 0x3a, 0x41, 0x83, 0xd2, - 0x60, 0xf4, 0x79, 0x03, 0xce, 0xef, 0x13, 0x14, 0xde, 0x93, 0xb1, 0x20, 0x0a, 0x1b, 0x56, 0xb3, - 0x0b, 0x18, 0x83, 0xba, 0xb5, 0x59, 0xcd, 0x26, 0xed, 0x99, 0x1d, 0x42, 0x90, 0x09, 0x79, 0x2d, - 0xe6, 0x58, 0x6c, 0xb6, 0x18, 0xfb, 0xef, 0xfa, 0xd8, 0x0b, 0x57, 0xe5, 0xb7, 0x30, 0xf6, 0x01, - 0x3a, 0x32, 0x27, 0x93, 0x2e, 0xa6, 0x1a, 0x29, 0xcd, 0xb5, 0x44, 0x0a, 0x9b, 0xb6, 0xf5, 0xbf, - 0xf5, 0xd6, 0x63, 0x8b, 0xe2, 0x1e, 0xc4, 0xb7, 0xe5, 0x5a, 0x4c, 0xec, 0x05, 0x34, 0x09, 0xa5, - 0x46, 0x43, 0x61, 0xf0, 0xd0, 0xd5, 0x4d, 0x6c, 0xf1, 0xbd, 0x4a, 0x22, 0x95, 0xcc, 0x79, 0x89, - 0x65, 0xaf, 0x21, 0xf0, 0x4e, 0xa5, 0xb0, 0x65, 0xfb, 0x0e, 0x1e, 0xbe, 0x29, 0xef, 0x22, 0x7e, - 0x0b, 0x1f, 0x5f, 0x40, 0x73, 0xe2, 0xb6, 0xce, 0x4e, 0xa1, 0x5e, 0x1c, 0xd9, 0x23, 0xbf, 0xb6, - 0x7f, 0x5e, 0xba, 0xff, 0x3f, 0x56, 0x76, 0xfe, 0x7b, 0x77, 0xf4, 0xf5, 0x70, 0xae, 0xcc, 0xf7, - 0x7c, 0x56, 0x0c, 0x1f, 0x89, 0x9b, 0x5c, 0x10, 0xca, 0x5c, 0x2b, 0xb3, 0x1a, 0xd9, 0xc6, 0xd1, - 0x9d, 0x57, 0xef, 0xad, 0xff, 0xce, 0x1a, 0xf6, 0x29, 0x3b, 0xfa, 0x1d, 0x00, 0x00, 0xff, 0xff, - 0x2e, 0x74, 0xc8, 0xad, 0x13, 0x05, 0x00, 0x00, + // 650 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xcd, 0x6e, 0xdb, 0x38, + 0x10, 0x86, 0x7f, 0x62, 0xcb, 0xe3, 0xc5, 0xc6, 0x21, 0x76, 0x03, 0xc5, 0xd9, 0x6c, 0x0d, 0x1f, + 0x0a, 0x9f, 0xec, 0xc6, 0x69, 0xd1, 0xbf, 0x5b, 0x93, 0xb4, 0x48, 0xd1, 0x22, 0x01, 0x1d, 0xf4, + 0xd0, 0x8b, 0x4b, 0x53, 0x13, 0x95, 0xb0, 0x2c, 0x29, 0x1c, 0xca, 0x80, 0x5f, 0xa5, 0xef, 0xd2, + 0xc7, 0xe8, 0xfb, 0x14, 0x22, 0x25, 0x23, 0x76, 0x92, 0x9e, 0xc4, 0x99, 0xf9, 0xe6, 0x9b, 0x0f, + 0x9c, 0x4f, 0x84, 0x03, 0x9d, 0xca, 0x11, 0x49, 0x11, 0xc7, 0xa8, 0x47, 0x84, 0x7a, 0xa9, 0x24, + 0x0e, 0x53, 0x9d, 0x98, 0x84, 0x75, 0x8c, 0x56, 0xcb, 0xd5, 0xb0, 0x28, 0x0e, 0x97, 0xc7, 0x5d, + 0x3f, 0x07, 0xcb, 0x64, 0xb1, 0x48, 0xe2, 0x4d, 0x6c, 0xff, 0x47, 0x05, 0xda, 0x13, 0x29, 0x62, + 0x8e, 0xb7, 0x19, 0x92, 0x61, 0xfb, 0xd0, 0x30, 0x42, 0x87, 0x68, 0xfc, 0x4a, 0xaf, 0x32, 0x68, + 0xf1, 0x22, 0x62, 0x4f, 0xa0, 0x2d, 0xb4, 0x51, 0x37, 0x42, 0x9a, 0xa9, 0x0a, 0xfc, 0xaa, 0x2d, + 0x42, 0x99, 0xba, 0x08, 0xd8, 0x01, 0x78, 0xb3, 0x28, 0x99, 0x4d, 0x55, 0x40, 0x7e, 0xad, 0x57, + 0x1b, 0xb4, 0x78, 0x33, 0x8f, 0x2f, 0x02, 0x62, 0x2f, 0xa1, 0x99, 0xa4, 0x46, 0x25, 0x31, 0xf9, + 0xf5, 0x5e, 0x65, 0xd0, 0x1e, 0x1f, 0x0d, 0xb7, 0x15, 0x0e, 0x73, 0x0d, 0x97, 0x0e, 0xc4, 0x4b, + 0x74, 0xbf, 0x07, 0xde, 0x27, 0x25, 0x31, 0x26, 0x24, 0xf6, 0x0f, 0xec, 0xc4, 0x62, 0x81, 0xe4, + 0x57, 0x2c, 0xb9, 0x0b, 0xfa, 0x3f, 0xab, 0x4e, 0x7e, 0xd1, 0xca, 0x0e, 0xa1, 0x95, 0xce, 0xc3, + 0xa9, 0x59, 0xa5, 0x6b, 0xa4, 0x97, 0xce, 0xc3, 0xeb, 0x3c, 0x66, 0x5d, 0xf0, 0x8a, 0x89, 0xe4, + 0x57, 0x5d, 0xad, 0x8c, 0x99, 0x04, 0x16, 0xb9, 0x51, 0x53, 0x29, 0x0c, 0x86, 0x89, 0x56, 0x98, + 0xcb, 0xad, 0x0d, 0xda, 0xe3, 0xe7, 0x7f, 0x94, 0x3b, 0x2c, 0x24, 0x9e, 0xae, 0xdb, 0xce, 0x63, + 0xa3, 0x57, 0x7c, 0x2f, 0xda, 0xce, 0xb3, 0x01, 0x74, 0x54, 0x2c, 0xa3, 0x2c, 0xc0, 0x69, 0x80, + 0xcb, 0x69, 0x80, 0x29, 0xf9, 0x3b, 0xbd, 0xca, 0xc0, 0xe3, 0x7f, 0x17, 0xf9, 0x33, 0x5c, 0x9e, + 0x61, 0x4a, 0xdd, 0x6f, 0xb0, 0xff, 0x30, 0x2d, 0xeb, 0x40, 0x6d, 0x8e, 0xab, 0x62, 0x3b, 0xf9, + 0x91, 0x3d, 0x83, 0x9d, 0xa5, 0x88, 0x32, 0xb4, 0x4b, 0x69, 0x8f, 0xbb, 0xf7, 0xd5, 0x96, 0x97, + 0xc8, 0x1d, 0xf0, 0x4d, 0xf5, 0x55, 0xe5, 0x63, 0xdd, 0xab, 0x75, 0xea, 0xfd, 0x00, 0xfe, 0x72, + 0xdb, 0xa7, 0x34, 0x89, 0x09, 0x59, 0x0f, 0xaa, 0x09, 0x59, 0xf2, 0xf6, 0xb8, 0x53, 0x10, 0x39, + 0xdf, 0x0c, 0x2f, 0x27, 0xbc, 0x9a, 0x10, 0x1b, 0x43, 0x53, 0x23, 0x65, 0x91, 0x71, 0x6b, 0x6e, + 0x8f, 0xfd, 0xfb, 0xf3, 0xb8, 0x05, 0xf0, 0x12, 0xd8, 0xff, 0x55, 0x83, 0x86, 0xcb, 0x3d, 0xea, + 0xaf, 0x73, 0xd8, 0x5d, 0x66, 0x51, 0x8c, 0x5a, 0xcc, 0x54, 0xa4, 0x4c, 0x7e, 0xf9, 0x55, 0x4b, + 0x7f, 0xb8, 0xa9, 0xe2, 0xcb, 0x1d, 0xd0, 0x8a, 0x6f, 0xf7, 0xb0, 0x6b, 0xd8, 0x5b, 0x28, 0x92, + 0x49, 0x7c, 0xa3, 0xc2, 0x4c, 0x8b, 0xd2, 0x74, 0x39, 0xd1, 0xd3, 0x4d, 0xa2, 0x33, 0x34, 0x28, + 0x0d, 0x06, 0x9f, 0xb7, 0xe0, 0xfc, 0x3e, 0x41, 0xee, 0x3d, 0x19, 0x09, 0x22, 0xbf, 0x61, 0x35, + 0xbb, 0x80, 0x31, 0xa8, 0xe7, 0x3e, 0xf3, 0x6b, 0x36, 0x69, 0xcf, 0xec, 0x18, 0xbc, 0x54, 0xc8, + 0xb9, 0x08, 0x31, 0xdf, 0x6c, 0x3e, 0xf6, 0xdf, 0xcd, 0xb1, 0x57, 0xae, 0xca, 0xd7, 0x30, 0xf6, + 0x01, 0x3a, 0x32, 0x23, 0x93, 0x2c, 0xa6, 0x1a, 0x29, 0xc9, 0xb4, 0x44, 0xf2, 0x9b, 0xb6, 0xf5, + 0xbf, 0xcd, 0xd6, 0x53, 0x8b, 0xe2, 0x05, 0x88, 0xef, 0xca, 0x8d, 0x98, 0xd8, 0x0b, 0x68, 0x12, + 0x4a, 0x8d, 0x86, 0x7c, 0xef, 0xa1, 0xab, 0x9b, 0xd8, 0xe2, 0x7b, 0x15, 0x07, 0x2a, 0x0e, 0x79, + 0x89, 0x65, 0xaf, 0xc1, 0x2b, 0x9c, 0x4a, 0x7e, 0xcb, 0xf6, 0x1d, 0x3d, 0x7c, 0x53, 0x85, 0x8b, + 0xf8, 0x1a, 0x3e, 0xbe, 0x82, 0xe6, 0xc4, 0x6d, 0x9d, 0x9d, 0x43, 0x3d, 0x3f, 0xb2, 0x47, 0x7e, + 0xed, 0xe2, 0x79, 0xe9, 0xfe, 0xff, 0x58, 0xd9, 0xf9, 0xef, 0xdd, 0xc9, 0xd7, 0xe3, 0x50, 0x99, + 0xef, 0xd9, 0x2c, 0x1f, 0x3e, 0x12, 0xb7, 0x99, 0x20, 0x94, 0x99, 0x56, 0x66, 0x35, 0xb2, 0x8d, + 0xa3, 0x3b, 0xaf, 0xde, 0xdb, 0xe2, 0x3b, 0x6b, 0xd8, 0xa7, 0xec, 0xe4, 0x77, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x50, 0x58, 0x22, 0xc7, 0x13, 0x05, 0x00, 0x00, } From f27c236d6e155cb366aeef619b6ea96d20fb93da Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 10 Jul 2024 10:02:40 +0700 Subject: [PATCH 225/352] fix(misconf): do not evaluate TF when a load error occurs (#7109) Signed-off-by: nikpivkin --- pkg/iac/scanners/terraform/parser/parser.go | 3 +++ .../scanners/terraform/parser/parser_test.go | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/pkg/iac/scanners/terraform/parser/parser.go b/pkg/iac/scanners/terraform/parser/parser.go index aec5ce0c31d7..fa511fed54c2 100644 --- a/pkg/iac/scanners/terraform/parser/parser.go +++ b/pkg/iac/scanners/terraform/parser/parser.go @@ -268,7 +268,10 @@ func (p *Parser) EvaluateAll(ctx context.Context) (terraform.Modules, cty.Value, e, err := p.Load(ctx) if errors.Is(err, ErrNoFiles) { return nil, cty.NilVal, nil + } else if err != nil { + return nil, cty.NilVal, err } + modules, fsMap := e.EvaluateAll(ctx) p.debug.Log("Finished parsing module '%s'.", p.moduleName) p.fsMap = fsMap diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index 3d25b5518b46..10232b007fd9 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -6,6 +6,7 @@ import ( "path/filepath" "sort" "testing" + "testing/fstest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -1725,3 +1726,22 @@ func Test_LoadLocalCachedModule(t *testing.T) { bucketName := buckets[0].GetAttribute("bucket").Value().AsString() assert.Equal(t, "my-s3-bucket", bucketName) } + +func TestTFVarsFileDoesNotExist(t *testing.T) { + fsys := fstest.MapFS{ + "main.tf": &fstest.MapFile{ + Data: []byte(``), + }, + } + + parser := New( + fsys, "", + OptionStopOnHCLError(true), + OptionWithDownloads(false), + OptionWithTFVarsPaths("main.tfvars"), + ) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + _, _, err := parser.EvaluateAll(context.TODO()) + assert.ErrorContains(t, err, "file does not exist") +} From d2f4da86a4d666f8d894149cfc74ea571dc78c6f Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 10 Jul 2024 10:21:17 +0400 Subject: [PATCH 226/352] chore: add VEX document and generator for Trivy (#7128) Signed-off-by: knqyf263 Co-authored-by: Nikita Pivkin --- .vex/trivy.openvex.json | 458 ++++++++++++++++++++++++++++++++++++++++ go.mod | 4 +- go.sum | 8 +- magefiles/magefile.go | 5 + magefiles/vex.go | 425 +++++++++++++++++++++++++++++++++++++ 5 files changed, 897 insertions(+), 3 deletions(-) create mode 100644 .vex/trivy.openvex.json create mode 100644 magefiles/vex.go diff --git a/.vex/trivy.openvex.json b/.vex/trivy.openvex.json new file mode 100644 index 000000000000..21af61db7d76 --- /dev/null +++ b/.vex/trivy.openvex.json @@ -0,0 +1,458 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "aquasecurity/trivy:613fd55abbc2857b5ca28b07a26f3cd4c8b0ddc4c8a97c57497a2d4c4880d7fc", + "author": "Aqua Security", + "timestamp": "2024-07-09T11:38:00.115697+04:00", + "version": 1, + "tooling": "https://github.com/aquasecurity/trivy/tree/main/magefiles/vex.go", + "statements": [ + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-2575", + "name": "GO-2024-2575", + "description": "Helm's Missing YAML Content Leads To Panic in helm.sh/helm/v3", + "aliases": [ + "CVE-2024-26147", + "GHSA-r53h-jv2g-vpx6" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/helm.sh/helm/v3", + "identifiers": { + "purl": "pkg:golang/helm.sh/helm/v3" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2023-1765", + "name": "GO-2023-1765", + "description": "Leaked shared secret and weak blinding in github.com/cloudflare/circl", + "aliases": [ + "CVE-2023-1732", + "GHSA-2q89-485c-9j2x" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/github.com/cloudflare/circl", + "identifiers": { + "purl": "pkg:golang/github.com/cloudflare/circl" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_present", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-2512", + "name": "GO-2024-2512", + "description": "Classic builder cache poisoning in github.com/docker/docker", + "aliases": [ + "CVE-2024-24557", + "GHSA-xw73-rw38-6vjc" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/github.com/docker/docker", + "identifiers": { + "purl": "pkg:golang/github.com/docker/docker" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_present", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-2453", + "name": "GO-2024-2453", + "description": "Timing side channel in github.com/cloudflare/circl", + "aliases": [ + "GHSA-9763-4f94-gfch" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/github.com/cloudflare/circl", + "identifiers": { + "purl": "pkg:golang/github.com/cloudflare/circl" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_present", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2023-2048", + "name": "GO-2023-2048", + "description": "Paths outside of the rootfs could be produced on Windows in github.com/cyphar/filepath-securejoin", + "aliases": [ + "GHSA-6xv5-86q9-7xr8" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/github.com/cyphar/filepath-securejoin", + "identifiers": { + "purl": "pkg:golang/github.com/cyphar/filepath-securejoin" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-2497", + "name": "GO-2024-2497", + "description": "Privilege escalation in github.com/moby/buildkit", + "aliases": [ + "CVE-2024-23653", + "GHSA-wr6v-9f75-vh2g" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/github.com/moby/buildkit", + "identifiers": { + "purl": "pkg:golang/github.com/moby/buildkit" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_present", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2023-2102", + "name": "GO-2023-2102", + "description": "HTTP/2 rapid reset can cause excessive work in net/http", + "aliases": [ + "CVE-2023-39325", + "GHSA-4374-p667-p6c8" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/golang.org/x/net", + "identifiers": { + "purl": "pkg:golang/golang.org/x/net" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-2493", + "name": "GO-2024-2493", + "description": "Host system file access in github.com/moby/buildkit", + "aliases": [ + "CVE-2024-23651", + "GHSA-m3r6-h7wv-7xxv" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/github.com/moby/buildkit", + "identifiers": { + "purl": "pkg:golang/github.com/moby/buildkit" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_present", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-2491", + "name": "GO-2024-2491", + "description": "Container breakout through process.cwd trickery and leaked fds in github.com/opencontainers/runc", + "aliases": [ + "CVE-2024-21626", + "GHSA-xr7r-f8xq-vfvv" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/github.com/opencontainers/runc", + "identifiers": { + "purl": "pkg:golang/github.com/opencontainers/runc" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_present", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-2494", + "name": "GO-2024-2494", + "description": "Host system modification in github.com/moby/buildkit", + "aliases": [ + "CVE-2024-23652", + "GHSA-4v98-7qmw-rqr8" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/github.com/moby/buildkit", + "identifiers": { + "purl": "pkg:golang/github.com/moby/buildkit" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_present", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2023-2412", + "name": "GO-2023-2412", + "description": "RAPL accessibility in github.com/containerd/containerd", + "aliases": [ + "GHSA-7ww5-4wqc-m92c" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/github.com/containerd/containerd", + "identifiers": { + "purl": "pkg:golang/github.com/containerd/containerd" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_present", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2023-1988", + "name": "GO-2023-1988", + "description": "Improper rendering of text nodes in golang.org/x/net/html", + "aliases": [ + "CVE-2023-3978", + "GHSA-2wrh-6pvc-2jm9" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/golang.org/x/net", + "identifiers": { + "purl": "pkg:golang/golang.org/x/net" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-2492", + "name": "GO-2024-2492", + "description": "Panic in github.com/moby/buildkit", + "aliases": [ + "CVE-2024-23650", + "GHSA-9p26-698r-w4hx" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/github.com/moby/buildkit", + "identifiers": { + "purl": "pkg:golang/github.com/moby/buildkit" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_present", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2022-0646", + "name": "GO-2022-0646", + "description": "Use of risky cryptographic algorithm in github.com/aws/aws-sdk-go", + "aliases": [ + "CVE-2020-8911", + "CVE-2020-8912", + "GHSA-7f33-f4f5-xwgw", + "GHSA-f5pg-7wfw-84q9" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/github.com/aws/aws-sdk-go", + "identifiers": { + "purl": "pkg:golang/github.com/aws/aws-sdk-go" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_present", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2023-2153", + "name": "GO-2023-2153", + "description": "Denial of service from HTTP/2 Rapid Reset in google.golang.org/grpc", + "aliases": [ + "GHSA-m425-mq94-257g" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/google.golang.org/grpc", + "identifiers": { + "purl": "pkg:golang/google.golang.org/grpc" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + } + ] +} diff --git a/go.mod b/go.mod index fb7cca2025db..222511f8eefd 100644 --- a/go.mod +++ b/go.mod @@ -122,6 +122,7 @@ require ( golang.org/x/sync v0.7.0 golang.org/x/term v0.21.0 golang.org/x/text v0.16.0 + golang.org/x/vuln v1.1.2 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 @@ -355,8 +356,9 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sys v0.21.0 // indirect + golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/tools v0.22.0 // indirect google.golang.org/api v0.172.0 // indirect google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect diff --git a/go.sum b/go.sum index ed9263ecb22c..0eedca04806a 100644 --- a/go.sum +++ b/go.sum @@ -2506,6 +2506,8 @@ golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 h1:FemxDzfMUcK2f3YY4H+05K9CDzbSVr2+q/JKN45pey0= +golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2624,8 +2626,10 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/vuln v1.1.2 h1:UkLxe+kAMcrNBpGrFbU0Mc5l7cX97P2nhy21wx5+Qbk= +golang.org/x/vuln v1.1.2/go.mod h1:2o3fRKD8Uz9AraAL3lwd/grWBv+t+SeJnPcqBUJrY24= 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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/magefiles/magefile.go b/magefiles/magefile.go index b23dde046e94..7ce148d885a0 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -469,3 +469,8 @@ type CloudActions mg.Namespace func (CloudActions) Generate() error { return sh.RunWith(ENV, "go", "run", "-tags=mage_cloudactions", "./magefiles") } + +// VEX generates a VEX document for Trivy +func VEX(_ context.Context, dir string) error { + return sh.RunWith(ENV, "go", "run", "-tags=mage_vex", "./magefiles/vex.go", "--dir", dir) +} diff --git a/magefiles/vex.go b/magefiles/vex.go new file mode 100644 index 000000000000..f5eb52e2efd7 --- /dev/null +++ b/magefiles/vex.go @@ -0,0 +1,425 @@ +//go:build mage_vex + +package main + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "os" + "os/exec" + "path" + "strings" + "time" + + "github.com/openvex/go-vex/pkg/vex" + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "golang.org/x/vuln/scan" + + "github.com/aquasecurity/go-version/pkg/version" + "github.com/aquasecurity/trivy/pkg/log" +) + +const ( + repoURL = "https://github.com/aquasecurity/trivy" + minVersion = "0.40.0" +) + +var ( + minVer, _ = version.Parse(minVersion) + + // Product ID for Trivy + productID = &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + // According to https://github.com/package-url/purl-spec/issues/63, + // It's probably better to leave namespace empty and put a module name into `name`. + Namespace: "", + Name: "github.com/aquasecurity/trivy", + } +) + +// VulnerabilityFinding is for parsing govulncheck JSON output +type VulnerabilityFinding struct { + Finding Finding `json:"finding"` +} + +type Finding struct { + OSV string `json:"osv"` + FixedVersion string `json:"fixed_version"` + Trace []Trace `json:"trace"` +} + +type Trace struct { + Module string `json:"module"` + Version string `json:"version"` + Package string `json:"package"` +} + +// UniqueKey is used to identify unique vulnerability-subcomponent pairs +type UniqueKey struct { + VulnerabilityID vex.VulnerabilityID + SubcomponentID string +} + +func main() { + if err := run(); err != nil { + log.Fatal("Fatal error", log.Err(err)) + } +} + +// run is the main entry point for the VEX generator +func run() error { + log.InitLogger(false, false) + + // Parse command-line flags + cloneDir := flag.String("dir", "trivy", "Directory to clone the repository") + output := flag.String("output", ".vex/trivy.openvex.json", "Output file") + flag.Parse() + + ctx := context.Background() + + // Clone or pull the Trivy repository + if _, err := cloneOrPullRepo(ctx, *cloneDir); err != nil { + return err + } + + defer func() { + // Ensure we are on the main branch after processing + _, _ = checkoutMain(ctx, *cloneDir) + }() + + // Save the current working directory + wd, err := os.Getwd() + if err != nil { + return fmt.Errorf("failed to get current working directory: %w", err) + } + + // Change to the target directory as govulncheck doesn't support Dir + // cf. https://github.com/golang/go/blob/6d89b38ed86e0bfa0ddaba08dc4071e6bb300eea/src/os/exec/exec.go#L171-L174 + if err = os.Chdir(*cloneDir); err != nil { + return fmt.Errorf("failed to change to directory %s: %w", *cloneDir, err) + } + + // Get the latest tags from the repository + tags, err := getLatestTags(ctx) + if err != nil { + return err + } + log.Info("Latest tags", log.Any("tags", tags)) + + // Maps to store "not_affected" statements across Trivy versions + notAffectedVulns := make(map[UniqueKey][]vex.Statement) + + // Indicate one or more Trivy versions are affected by the vulnerability. + // This means that the version cannot be omitted later. + affectedVulns := make(map[UniqueKey]struct{}) + + // Process each tag + for _, tag := range tags { + notAffected, affected, err := processTag(ctx, tag) + if err != nil { + return err + } + log.Info("Processed tag", log.String("tag", tag), + log.Int("not_affected", len(notAffected)), log.Int("affected", len(affected))) + lo.Assign(affectedVulns, affected) + for k, v := range notAffected { + notAffectedVulns[k] = append(notAffectedVulns[k], v) + } + } + + // Change back to the original directory + if err = os.Chdir(wd); err != nil { + return fmt.Errorf("failed to change back to original directory: %w", err) + } + + // Generate the final VEX document + if err = updateVEX(*output, combineDocs(notAffectedVulns, affectedVulns)); err != nil { + return err + } + + return nil +} + +// cloneOrPullRepo clones the Trivy repository or pulls updates if it already exists +func cloneOrPullRepo(ctx context.Context, dir string) ([]byte, error) { + if _, err := os.Stat(dir); os.IsNotExist(err) { + return runCommandWithTimeout(ctx, 20*time.Minute, "git", "clone", repoURL, dir) + } + + if _, err := checkoutMain(ctx, dir); err != nil { + return nil, fmt.Errorf("failed to checkout main: %w", err) + } + return runCommandWithTimeout(ctx, 2*time.Minute, "git", "-C", dir, "pull", "--tags") +} + +// checkoutMain checks out the main branch of the repository +func checkoutMain(ctx context.Context, dir string) ([]byte, error) { + return runCommandWithTimeout(ctx, 1*time.Minute, "git", "-C", dir, "checkout", "main") +} + +// getLatestTags retrieves and sorts the latest tags from the repository +func getLatestTags(ctx context.Context) ([]string, error) { + output, err := runCommandWithTimeout(ctx, 1*time.Minute, "git", "tag") + if err != nil { + return nil, fmt.Errorf("failed to get tags: %w", err) + } + + tags := strings.Split(strings.TrimSpace(string(output)), "\n") + versions := make([]string, 0, len(tags)) + + for _, tag := range tags { + v, err := version.Parse(tag) + if err != nil { + continue + } + if v.GreaterThanOrEqual(minVer) { + versions = append(versions, tag) + } + } + + return versions, nil +} + +// processTag processes a single tag, running govulncheck and generating VEX statements +func processTag(ctx context.Context, tag string) (map[UniqueKey]vex.Statement, map[UniqueKey]struct{}, error) { + log.Info("Processing tag...", log.String("tag", tag)) + if _, err := runCommandWithTimeout(ctx, 1*time.Minute, "git", "checkout", tag); err != nil { + return nil, nil, fmt.Errorf("failed to checkout tag %s: %w", tag, err) + } + + // Run govulncheck and generate VEX document + vexDoc, err := generateVEX(ctx) + if err != nil { + return nil, nil, fmt.Errorf("failed to run govulncheck: %w", err) + } + + // Run govulncheck and generate JSON result + // Need to generate JSON as well as OpenVEX for the following reasons: + // - Subcomponent + // - OpenVEX from govulncheck doesn't fill in subcomponents. + // - Status + // - govulncheck uses "not_affected" for all vulnerabilities. Need to determine "fixed" vulnerabilities. + // cf. https://github.com/golang/go/issues/68338 + findings, err := generateJSON(ctx) + if err != nil { + return nil, nil, fmt.Errorf("failed to run govulncheck: %w", err) + } + + product := *productID // Clone Trivy PURL + product.Version = tag + notAffected := make(map[UniqueKey]vex.Statement) + affected := make(map[UniqueKey]struct{}) + + // Update VEX document generated by govulncheck + for _, stmt := range vexDoc.Statements { + finding, ok := findings[stmt.Vulnerability.Name] + if !ok { + // Considered as "fixed" vulnerabilities + // cf. https://github.com/golang/go/issues/68338 + continue + } else if len(finding.Finding.Trace) == 0 { + continue + } + + namespace, name := path.Split(finding.Finding.Trace[0].Module) + subcomponent := &packageurl.PackageURL{ + Type: packageurl.TypeGolang, + Namespace: namespace, + Name: name, + } + + key := UniqueKey{ + VulnerabilityID: stmt.Vulnerability.Name, + SubcomponentID: subcomponent.String(), + } + + if stmt.Status == vex.StatusAffected { + affected[key] = struct{}{} + continue + } else if stmt.Status != vex.StatusNotAffected { + continue + } + + // Update the statement with product and subcomponent information + stmt.Products = []vex.Product{ + { + // Fill in components manually + // cf. https://github.com/golang/go/issues/68152 + Component: vex.Component{ + ID: product.String(), + Identifiers: map[vex.IdentifierType]string{ + vex.PURL: product.String(), + }, + }, + Subcomponents: []vex.Subcomponent{ + { + Component: vex.Component{ + ID: key.SubcomponentID, + Identifiers: map[vex.IdentifierType]string{ + vex.PURL: key.SubcomponentID, + }, + }, + }, + }, + }, + } + notAffected[key] = stmt + } + + return notAffected, affected, nil +} + +// generateVEX runs govulncheck with OpenVEX format and parses the output +func generateVEX(ctx context.Context) (*vex.VEX, error) { + buf, err := runGovulncheck(ctx, "openvex") + if err != nil { + return nil, fmt.Errorf("failed to run govulncheck: %w", err) + } + + vexDoc, err := vex.Parse(buf.Bytes()) + if err != nil { + return nil, fmt.Errorf("failed to parse govulncheck output: %w", err) + } + return vexDoc, nil +} + +// generateJSON runs govulncheck with JSON format and parses the output +func generateJSON(ctx context.Context) (map[vex.VulnerabilityID]VulnerabilityFinding, error) { + buf, err := runGovulncheck(ctx, "json") + if err != nil { + return nil, fmt.Errorf("failed to run govulncheck: %w", err) + } + + decoder := json.NewDecoder(buf) + findings := make(map[vex.VulnerabilityID]VulnerabilityFinding) + for { + var finding VulnerabilityFinding + if err := decoder.Decode(&finding); err == io.EOF { + break + } else if err != nil { + return nil, fmt.Errorf("failed to decode govulncheck output: %w", err) + } + findings[vex.VulnerabilityID(finding.Finding.OSV)] = finding + } + return findings, nil +} + +// runGovulncheck executes the govulncheck command with the specified format +func runGovulncheck(ctx context.Context, format string) (*bytes.Buffer, error) { + var buf bytes.Buffer + cmd := scan.Command(ctx, "-format", format, "./...") + cmd.Stdout = &buf + + log.Info("Running govulncheck", log.String("format", format)) + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("failed to start govulncheck: %w", err) + } + + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("failed to run govulncheck: %w", err) + } + return &buf, nil +} + +// combineDocs merges the VEX statements from all processed tags +func combineDocs(notAffected map[UniqueKey][]vex.Statement, affected map[UniqueKey]struct{}) []vex.Statement { + log.Info("Combining VEX documents") + statements := make(map[UniqueKey]vex.Statement) + for key, stmts := range notAffected { + for _, stmt := range stmts { + if _, ok := affected[key]; !ok { + // All versions are "not_affected" or "fixed" by the vulnerability, omitting a version in PURL + // => pkg:golang/github.com/aquasecurity/trivy + stmt.Products[0].ID = productID.String() + stmt.Products[0].Identifiers[vex.PURL] = productID.String() + statements[key] = stmt + break + } + + // At least one version is "affected" by the vulnerability, so we need to include the version in PURL. + // => pkg:golang/github.com/aquasecurity/trivy@0.52.0 + if s, ok := statements[key]; ok { + s.Products = append(s.Products, stmt.Products...) + statements[key] = s + } else { + statements[key] = stmt + } + } + } + return lo.Values(statements) +} + +// runCommandWithTimeout executes a command with a specified timeout +func runCommandWithTimeout(ctx context.Context, timeout time.Duration, name string, args ...string) ([]byte, error) { + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + cmd := exec.CommandContext(ctx, name, args...) + log.Info("Executing command", log.String("cmd", cmd.String())) + + output, err := cmd.CombinedOutput() + if err != nil { + return output, fmt.Errorf("%w, output: %s", err, string(output)) + } + + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return nil, fmt.Errorf("command timed out after %v", timeout) + } + + return output, nil +} + +// updateVEX updates the final VEX document with the combined statements +func updateVEX(output string, statements []vex.Statement) error { + doc, err := vex.Load(output) + if errors.Is(err, os.ErrNotExist) { + doc = &vex.VEX{} + } else if err != nil { + return err + } + + vex.SortStatements(statements, time.Now()) + d := &vex.VEX{ + Metadata: vex.Metadata{ + Context: "https://openvex.dev/ns/v0.2.0", + Author: "Aqua Security", + Timestamp: lo.ToPtr(time.Now()), + Version: doc.Version + 1, + Tooling: "https://github.com/aquasecurity/trivy/tree/main/magefiles/vex.go", + }, + Statements: statements, + } + h, err := hashVEX(d) + if err != nil { + return err + } + d.ID = "aquasecurity/trivy:" + h + + f, err := os.Create(output) + if err != nil { + return err + } + defer f.Close() + + e := json.NewEncoder(f) + e.SetIndent("", " ") + if err = e.Encode(d); err != nil { + return err + } + return err +} + +func hashVEX(d *vex.VEX) (string, error) { + out, err := json.Marshal(d) + if err != nil { + return "", err + } + return fmt.Sprintf("%x", sha256.Sum256(out)), nil +} From e674c9347010abe5a73dbd7496c9eaadce93ac70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 12:39:17 +0400 Subject: [PATCH 227/352] chore(deps): bump the common group across 1 directory with 7 updates (#7125) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: DmitriyLewen --- go.mod | 20 +++++----- go.sum | 40 +++++++++++--------- integration/testdata/alpine-310.sarif.golden | 2 +- pkg/fanal/artifact/image/remote_sbom_test.go | 2 + pkg/report/sarif_test.go | 12 +++--- 5 files changed, 42 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index 222511f8eefd..6ff9ddf6e50a 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cenkalti/backoff/v4 v4.3.0 github.com/cheggaaa/pb/v3 v3.1.5 - github.com/containerd/containerd v1.7.18 + github.com/containerd/containerd v1.7.19 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 github.com/docker/docker v27.0.3+incompatible github.com/docker/go-connections v0.5.0 @@ -51,7 +51,7 @@ require ( github.com/go-openapi/strfmt v0.23.0 github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/go-containerregistry v0.19.2 + github.com/google/go-containerregistry v0.20.0 github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/uuid v1.6.0 github.com/google/wire v0.6.0 @@ -91,7 +91,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/openvex/go-vex v0.2.5 - github.com/owenrumney/go-sarif/v2 v2.3.1 + github.com/owenrumney/go-sarif/v2 v2.3.2 github.com/owenrumney/squealer v1.2.2 github.com/package-url/packageurl-go v0.1.3 github.com/quasilyte/go-ruleguard/dsl v0.3.22 @@ -115,12 +115,12 @@ require ( github.com/zclconf/go-cty v1.14.4 github.com/zclconf/go-cty-yaml v1.0.3 go.etcd.io/bbolt v1.3.10 - golang.org/x/crypto v0.24.0 + golang.org/x/crypto v0.25.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect - golang.org/x/mod v0.18.0 - golang.org/x/net v0.26.0 + golang.org/x/mod v0.19.0 + golang.org/x/net v0.27.0 golang.org/x/sync v0.7.0 - golang.org/x/term v0.21.0 + golang.org/x/term v0.22.0 golang.org/x/text v0.16.0 golang.org/x/vuln v1.1.2 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 @@ -185,12 +185,14 @@ require ( github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/cgroups/v3 v3.0.2 // indirect + github.com/containerd/containerd/api v1.7.19 // indirect github.com/containerd/continuity v0.4.3 // indirect github.com/containerd/errdefs v0.1.0 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect - github.com/containerd/ttrpc v1.2.4 // indirect + github.com/containerd/ttrpc v1.2.5 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect @@ -355,7 +357,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect diff --git a/go.sum b/go.sum index 0eedca04806a..8dabdd85f5e4 100644 --- a/go.sum +++ b/go.sum @@ -934,8 +934,10 @@ github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= -github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +github.com/containerd/containerd v1.7.19 h1:/xQ4XRJ0tamDkdzrrBAUy/LE5nCcxFKdBm4EcPrSMEE= +github.com/containerd/containerd v1.7.19/go.mod h1:h4FtNYUUMB4Phr6v+xG89RYKj9XccvbNSCKjdufCrkc= +github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA= +github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -971,6 +973,8 @@ github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3 github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= @@ -979,8 +983,8 @@ github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDG github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.2.4 h1:eQCQK4h9dxDmpOb9QOOMh2NHTfzroH1IkmHiKZi05Oo= -github.com/containerd/ttrpc v1.2.4/go.mod h1:ojvb8SJBSch0XkqNO0L0YX/5NxR3UnVk2LzFKBK0upc= +github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU= +github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= @@ -1351,8 +1355,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= -github.com/google/go-containerregistry v0.19.2 h1:TannFKE1QSajsP6hPWb5oJNgKe1IKjHukIKDUmvsV6w= -github.com/google/go-containerregistry v0.19.2/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/go-containerregistry v0.20.0 h1:wRqHpOeVh3DnenOrPy9xDOLdnLatiGuuNRVelR2gSbg= +github.com/google/go-containerregistry v0.20.0/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -1795,8 +1799,8 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ= github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88IO/0Wo= github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= -github.com/owenrumney/go-sarif/v2 v2.3.1 h1:77opmuqxQZE1UF6TylFz5XllVEI72WijgwpwNw4JTmY= -github.com/owenrumney/go-sarif/v2 v2.3.1/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= +github.com/owenrumney/go-sarif/v2 v2.3.2 h1:yptG4K76SnLydTFHUecZotPR9uhBvnJLjE7cPltvROU= +github.com/owenrumney/go-sarif/v2 v2.3.2/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= github.com/owenrumney/squealer v1.2.2 h1:zsnZSwkWi8Y2lgwmg77b565vlHQovlvBrSBzmAs3oiE= github.com/owenrumney/squealer v1.2.2/go.mod h1:pDCW33bWJ2kDOuz7+2BSXDgY38qusVX0MtjPCSFtdSo= github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= @@ -2176,8 +2180,8 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2240,8 +2244,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2316,8 +2320,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2504,8 +2508,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 h1:FemxDzfMUcK2f3YY4H+05K9CDzbSVr2+q/JKN45pey0= golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2522,8 +2526,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/integration/testdata/alpine-310.sarif.golden b/integration/testdata/alpine-310.sarif.golden index a875ba35fecf..66265d172b3f 100644 --- a/integration/testdata/alpine-310.sarif.golden +++ b/integration/testdata/alpine-310.sarif.golden @@ -1,6 +1,6 @@ { "version": "2.1.0", - "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", "runs": [ { "tool": { diff --git a/pkg/fanal/artifact/image/remote_sbom_test.go b/pkg/fanal/artifact/image/remote_sbom_test.go index b6c144129929..bdd2562101f1 100644 --- a/pkg/fanal/artifact/image/remote_sbom_test.go +++ b/pkg/fanal/artifact/image/remote_sbom_test.go @@ -10,6 +10,7 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" fakei "github.com/google/go-containerregistry/pkg/v1/fake" + typesv1 "github.com/google/go-containerregistry/pkg/v1/types" "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -183,6 +184,7 @@ func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { _, err := w.Write([]byte("ok")) assert.NoError(t, err) case "/v2/test/image/referrers/sha256:782143e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02": + w.Header().Set("Content-Type", string(typesv1.OCIImageIndex)) http.ServeFile(w, r, "testdata/index.json") case "/v2/test/image/manifests/sha256:37c89af4907fa0af078aeba12d6f18dc0c63937c010030baaaa88e958f0719a5": http.ServeFile(w, r, "testdata/manifest.json") diff --git a/pkg/report/sarif_test.go b/pkg/report/sarif_test.go index 9ce3363cc321..cf4bfea8eb0b 100644 --- a/pkg/report/sarif_test.go +++ b/pkg/report/sarif_test.go @@ -93,7 +93,7 @@ func TestReportWriter_Sarif(t *testing.T) { }, want: &sarif.Report{ Version: "2.1.0", - Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", Runs: []*sarif.Run{ { Tool: sarif.Tool{ @@ -219,7 +219,7 @@ func TestReportWriter_Sarif(t *testing.T) { }, want: &sarif.Report{ Version: "2.1.0", - Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", Runs: []*sarif.Run{ { Tool: sarif.Tool{ @@ -359,7 +359,7 @@ func TestReportWriter_Sarif(t *testing.T) { }, want: &sarif.Report{ Version: "2.1.0", - Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", Runs: []*sarif.Run{ { Tool: sarif.Tool{ @@ -453,7 +453,7 @@ func TestReportWriter_Sarif(t *testing.T) { }, want: &sarif.Report{ Version: "2.1.0", - Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", Runs: []*sarif.Run{ { Tool: sarif.Tool{ @@ -523,7 +523,7 @@ func TestReportWriter_Sarif(t *testing.T) { name: "no vulns", want: &sarif.Report{ Version: "2.1.0", - Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", Runs: []*sarif.Run{ { Tool: sarif.Tool{ @@ -592,7 +592,7 @@ func TestReportWriter_Sarif(t *testing.T) { }, want: &sarif.Report{ Version: "2.1.0", - Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", Runs: []*sarif.Run{ { Tool: *sarif.NewTool( From a3a6de27c4934381e088c99608c89352754fa65b Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 10 Jul 2024 12:55:57 +0400 Subject: [PATCH 228/352] chore: add VEX for Trivy images (#7140) Signed-off-by: knqyf263 --- .vex/oci.openvex.json | 134 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 .vex/oci.openvex.json diff --git a/.vex/oci.openvex.json b/.vex/oci.openvex.json new file mode 100644 index 000000000000..6a58df8cdf41 --- /dev/null +++ b/.vex/oci.openvex.json @@ -0,0 +1,134 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "https://openvex.dev/docs/public/vex-8e30ed756ae8e4196af93bf43edf68360f396a98c0268787453a3443b26e7d6c", + "author": "Aqua Security", + "timestamp": "2024-07-10T12:17:44.60495+04:00", + "version": 1, + "statements": [ + { + "vulnerability": { + "name": "CVE-2023-42363" + }, + "products": [ + { + "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"} + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_cannot_be_controlled_by_adversary", + "impact_statement": "awk is not used" + }, + { + "vulnerability": { + "name": "CVE-2023-42364" + }, + "products": [ + { + "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/busybox" + } + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"} + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_cannot_be_controlled_by_adversary", + "impact_statement": "awk is not used" + }, + { + "vulnerability": { + "name": "CVE-2023-42365" + }, + "products": [ + { + "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"} + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_cannot_be_controlled_by_adversary", + "impact_statement": "awk is not used" + }, + { + "vulnerability": { + "name": "CVE-2023-42366" + }, + "products": [ + { + "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"} + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_cannot_be_controlled_by_adversary", + "impact_statement": "awk is not used" + } + ] +} From d1f89672d92d04504808f9fa02845282c5e81b26 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 11 Jul 2024 05:22:17 +0700 Subject: [PATCH 229/352] docs(misconf): add info about limitations for terraform plan json (#7143) --- docs/docs/coverage/iac/terraform.md | 31 ++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/docs/coverage/iac/terraform.md b/docs/docs/coverage/iac/terraform.md index 843126f54d3a..e190c901cf05 100644 --- a/docs/docs/coverage/iac/terraform.md +++ b/docs/docs/coverage/iac/terraform.md @@ -47,4 +47,33 @@ trivy conf --tf-exclude-downloaded-modules ./configs ``` ## Secret -The secret scan is performed on plain text files, with no special treatment for Terraform. \ No newline at end of file +The secret scan is performed on plain text files, with no special treatment for Terraform. + +## Limitations + +### Terraform Plan JSON + +#### For each and count objects in expression + +The plan created by Terraform does not provide complete information about references in expressions that use `each` or `count` objects. For this reason, in some situations it is not possible to establish references between resources that are needed for checks when detecting misconfigurations. An example of such a configuration is: + +```hcl +locals { + buckets = toset(["test"]) +} + +resource "aws_s3_bucket" "this" { + for_each = local.buckets + bucket = each.key +} + +resource "aws_s3_bucket_acl" "this" { + for_each = local.buckets + bucket = aws_s3_bucket.this[each.key].id + acl = "private" +} +``` + +With this configuration, the plan will not contain information about which attribute of the `aws_s3_bucket` resource is referenced by the `aws_s3_bucket_acl` resource. + +See more [here](https://github.com/hashicorp/terraform/issues/30826). \ No newline at end of file From 4308a0a5e3736bb181f35e5f09fa3e46d6e7ecce Mon Sep 17 00:00:00 2001 From: Pierre Baumard Date: Fri, 12 Jul 2024 08:49:33 +0200 Subject: [PATCH 230/352] docs: Fix PR documentation to use GitHub Discussions, not Issues (#7141) --- docs/community/contribute/pr.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/community/contribute/pr.md b/docs/community/contribute/pr.md index 0324bb6f8059..584f502b9fb9 100644 --- a/docs/community/contribute/pr.md +++ b/docs/community/contribute/pr.md @@ -1,7 +1,6 @@ Thank you for taking interest in contributing to Trivy! -1. Every Pull Request should have an associated bug or feature issue unless you are fixing a trivial documentation issue. -1. Please add the associated Issue link in the PR description. +1. Every Pull Request should have an associated GitHub issue link in the PR description. Note that issues are created by Trivy maintainers based on feedback provided in a GitHub discussion. Please refer to the [issue](./issue.md) and [discussion](./discussion.md) pages for explanation about this process. If you think your change is trivial enough, you can skip the issue and instead add justification and explanation in the PR description. 1. Your PR is more likely to be accepted if it focuses on just one change. 1. There's no need to add or tag reviewers. 1. If a reviewer commented on your code or asked for changes, please remember to respond with comment. Do not mark discussion as resolved. It's up to reviewer to mark it resolved (in case if suggested fix addresses problem properly). PRs with unresolved issues should not be merged (even if the comment is unclear or requires no action from your side). From 2a577a7bae37e5731dceaea8740683573b6b70a5 Mon Sep 17 00:00:00 2001 From: guoguangwu Date: Mon, 15 Jul 2024 21:05:42 +0800 Subject: [PATCH 231/352] fix: close file when failed to open gzip (#7164) Signed-off-by: guoguangwu --- pkg/fanal/image/docker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/fanal/image/docker.go b/pkg/fanal/image/docker.go index 54096099761f..ea1549b0f910 100644 --- a/pkg/fanal/image/docker.go +++ b/pkg/fanal/image/docker.go @@ -35,6 +35,7 @@ func fileOpener(fileName string) func() (io.ReadCloser, error) { if utils.IsGzip(br) { r, err = gzip.NewReader(br) if err != nil { + _ = f.Close() return nil, xerrors.Errorf("failed to open gzip: %w", err) } } From d1ec89d1db4b039f0e31076ccd1ca969fb15628e Mon Sep 17 00:00:00 2001 From: Adam Bloom Date: Mon, 15 Jul 2024 20:05:34 -0700 Subject: [PATCH 232/352] feat(misconf): enabled China configuration for ACRs (#7156) --- pkg/fanal/image/registry/azure/azure.go | 33 ++++++++++++++------ pkg/fanal/image/registry/azure/azure_test.go | 4 +++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/pkg/fanal/image/registry/azure/azure.go b/pkg/fanal/image/registry/azure/azure.go index fe348eaac9f5..3203829f3d3d 100644 --- a/pkg/fanal/image/registry/azure/azure.go +++ b/pkg/fanal/image/registry/azure/azure.go @@ -8,6 +8,8 @@ import ( "strings" "github.com/Azure/azure-sdk-for-go/profiles/preview/preview/containerregistry/runtime/containerregistry" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "golang.org/x/xerrors" @@ -17,28 +19,41 @@ import ( type Registry struct { domain string + scope string + cloud cloud.Configuration } const ( - azureURL = ".azurecr.io" - scope = "https://management.azure.com/.default" - scheme = "https" + azureURL = ".azurecr.io" + chinaAzureURL = ".azurecr.cn" + scope = "https://management.azure.com/.default" + chinaScope = "https://management.chinacloudapi.cn/.default" + scheme = "https" ) func (r *Registry) CheckOptions(domain string, _ types.RegistryOptions) error { - if !strings.HasSuffix(domain, azureURL) { - return xerrors.Errorf("Azure registry: %w", types.InvalidURLPattern) + if strings.HasSuffix(domain, azureURL) { + r.domain = domain + r.scope = scope + r.cloud = cloud.AzurePublic + return nil + } else if strings.HasSuffix(domain, chinaAzureURL) { + r.domain = domain + r.scope = chinaScope + r.cloud = cloud.AzureChina + return nil } - r.domain = domain - return nil + + return xerrors.Errorf("Azure registry: %w", types.InvalidURLPattern) } func (r *Registry) GetCredential(ctx context.Context) (string, string, error) { - cred, err := azidentity.NewDefaultAzureCredential(nil) + opts := azcore.ClientOptions{Cloud: r.cloud} + cred, err := azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ClientOptions: opts}) if err != nil { return "", "", xerrors.Errorf("unable to generate acr credential error: %w", err) } - aadToken, err := cred.GetToken(ctx, policy.TokenRequestOptions{Scopes: []string{scope}}) + aadToken, err := cred.GetToken(ctx, policy.TokenRequestOptions{Scopes: []string{r.scope}}) if err != nil { return "", "", xerrors.Errorf("unable to get an access token: %w", err) } diff --git a/pkg/fanal/image/registry/azure/azure_test.go b/pkg/fanal/image/registry/azure/azure_test.go index c8c48574b61b..0fb4839e8fee 100644 --- a/pkg/fanal/image/registry/azure/azure_test.go +++ b/pkg/fanal/image/registry/azure/azure_test.go @@ -20,6 +20,10 @@ func TestRegistry_CheckOptions(t *testing.T) { name: "happy path", domain: "test.azurecr.io", }, + { + name: "china happy path", + domain: "test.azurecr.cn", + }, { name: "invalidURL", domain: "not-azurecr.io", From 7066f5e674e469d09b81a4e3381c19cdd240ae81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 07:15:08 +0400 Subject: [PATCH 233/352] chore(deps): bump the aws group with 6 updates (#7166) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 26 +++++++++++++------------- go.sum | 52 ++++++++++++++++++++++++++-------------------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index 6ff9ddf6e50a..98dab8d4fff2 100644 --- a/go.mod +++ b/go.mod @@ -29,13 +29,13 @@ require ( github.com/aquasecurity/trivy-db v0.0.0-20240701103400-8e907467e9ab github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b - github.com/aws/aws-sdk-go-v2 v1.30.1 - github.com/aws/aws-sdk-go-v2/config v1.27.24 - github.com/aws/aws-sdk-go-v2/credentials v1.17.24 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.168.0 - github.com/aws/aws-sdk-go-v2/service/ecr v1.30.1 - github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.30.3 + github.com/aws/aws-sdk-go-v2/config v1.27.26 + github.com/aws/aws-sdk-go-v2/credentials v1.17.26 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.170.0 + github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 + github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect github.com/aws/smithy-go v1.20.3 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 @@ -169,15 +169,15 @@ require ( github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.54.6 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/briandowns/spinner v1.23.0 // indirect diff --git a/go.sum b/go.sum index 8dabdd85f5e4..114f8585b370 100644 --- a/go.sum +++ b/go.sum @@ -789,38 +789,38 @@ github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g= github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o= -github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= -github.com/aws/aws-sdk-go-v2/config v1.27.24 h1:NM9XicZ5o1CBU/MZaHwFtimRpWx9ohAUAqkG6AqSqPo= -github.com/aws/aws-sdk-go-v2/config v1.27.24/go.mod h1:aXzi6QJTuQRVVusAO8/NxpdTeTyr/wRcybdDtfUwJSs= -github.com/aws/aws-sdk-go-v2/credentials v1.17.24 h1:YclAsrnb1/GTQNt2nzv+756Iw4mF8AOzcDfweWwwm/M= -github.com/aws/aws-sdk-go-v2/credentials v1.17.24/go.mod h1:Hld7tmnAkoBQdTMNYZGzztzKRdA4fCdn9L83LOoigac= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8= +github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= +github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/config v1.27.26 h1:T1kAefbKuNum/AbShMsZEro6eRkeOT8YILfE9wyjAYQ= +github.com/aws/aws-sdk-go-v2/config v1.27.26/go.mod h1:ivWHkAWFrw/nxty5Fku7soTIVdqZaZ7dw+tc5iGW3GA= +github.com/aws/aws-sdk-go-v2/credentials v1.17.26 h1:tsm8g/nJxi8+/7XyJJcP2dLrnK/5rkFp6+i2nhmz5fk= +github.com/aws/aws-sdk-go-v2/credentials v1.17.26/go.mod h1:3vAM49zkIa3q8WT6o9Ve5Z0vdByDMwmdScO0zvThTgI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.168.0 h1:xOPq0agGC1WMZvFpSZCKEjDVAQnLPZJZGvjuPVF2t9M= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.168.0/go.mod h1:CtLD6CPq9z9dyMxV+H6/M5d9+/ea3dO80um029GXqV0= -github.com/aws/aws-sdk-go-v2/service/ecr v1.30.1 h1:zV3FlyuyPzfyFOXKu6mJW9JBGzdtOgpdlj3va+naOD8= -github.com/aws/aws-sdk-go-v2/service/ecr v1.30.1/go.mod h1:l0zC7cSb2vAH1fr8+BRlolWT9cwlKpbRC8PjW6tyyIU= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.170.0 h1:zPwhEYn3Y83mnnr9QG+i6NTiAbVbcJe6RpCSJKHIQNE= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.170.0/go.mod h1:9KdiRVKTZyPRTlbX3i41FxTV+5OatZ7xOJCN4lleX7g= +github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3 h1:+v2hv29pWaVDASIScHuUhDC93nqJGVlGf6cujrJMHZE= +github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3/go.mod h1:RhaP7Wil0+uuuhiE4FzOOEFZwkmFAk1ZflXzK+O3ptU= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0 h1:4rhV0Hn+bf8IAIUphRX1moBcEvKJipCPmswMCl6Q5mw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0/go.mod h1:hdV0NTYd0RwV4FvNKhKUNbPLZoq9CTr/lke+3I7aCAI= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 h1:p1GahKIjyMDZtiKoIn0/jAj/TkMzfzndDv5+zi2Mhgc= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 h1:ORnrOK0C4WmYV/uYt3koHEWBLYsRDwk2Np+eEoyV4Z0= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALwfMWpd64tONS/NE= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 h1:Fv1vD2L65Jnp5QRsdiM64JvUM4Xe+E0JyVsRQKv6IeA= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.3/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= From c8a7abd3b508975fcf10c254d13d1a2cd42da657 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 16 Jul 2024 12:20:13 +0700 Subject: [PATCH 234/352] fix: add missing platform and type to spec (#7149) Signed-off-by: nikpivkin --- pkg/iac/types/compliance.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/iac/types/compliance.go b/pkg/iac/types/compliance.go index 42636fffe544..5bb0fa346975 100644 --- a/pkg/iac/types/compliance.go +++ b/pkg/iac/types/compliance.go @@ -21,7 +21,9 @@ type Spec struct { Title string `yaml:"title"` Description string `yaml:"description"` Version string `yaml:"version"` - RelatedResources []string `yaml:"relatedResources"` + Platform string `yaml:"platform"` + Type string `yaml:"type"` + RelatedResources []string `yaml:"relatedResources,omitempty"` Controls []Control `yaml:"controls"` } @@ -30,8 +32,8 @@ type Control struct { ID string `yaml:"id"` Name string `yaml:"name"` Description string `yaml:"description,omitempty"` - Checks []SpecCheck `yaml:"checks"` - Commands []Command `yaml:"commands"` + Checks []SpecCheck `yaml:"checks,omitempty"` + Commands []Command `yaml:"commands,omitempty"` Severity Severity `yaml:"severity"` DefaultStatus ControlStatus `yaml:"defaultStatus,omitempty"` } From b76a7250912cfc028cfef743f0f98cd81b39f8aa Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 16 Jul 2024 13:44:10 +0600 Subject: [PATCH 235/352] chore(deps): bump goreleaser from `v2.0.0` to `v2.1.0` (#7162) --- .github/workflows/reusable-release.yaml | 2 +- .github/workflows/test.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-release.yaml b/.github/workflows/reusable-release.yaml index e308aba0a2db..0f7064ee18e1 100644 --- a/.github/workflows/reusable-release.yaml +++ b/.github/workflows/reusable-release.yaml @@ -91,7 +91,7 @@ jobs: - name: GoReleaser uses: goreleaser/goreleaser-action@v6 with: - version: v2.0.0 + version: v2.1.0 args: release -f=${{ inputs.goreleaser_config}} ${{ inputs.goreleaser_options}} env: GITHUB_TOKEN: ${{ secrets.ORG_REPO_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 441d37e735ab..2ff471be1c7d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -178,5 +178,5 @@ jobs: - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 with: - version: v2.0.0 + version: v2.1.0 args: build --snapshot --clean --timeout 90m ${{ steps.goreleaser_id.outputs.id }} From 5bc662be9a8f072599f90abfd3b400c8ab055ed6 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:44:44 +0500 Subject: [PATCH 236/352] fix(dotnet): don't include non-runtime libraries into report for `*.deps.json` files (#7039) --- docs/docs/coverage/language/dotnet.md | 3 + integration/testdata/dotnet.json.golden | 6 +- pkg/dependency/id.go | 3 +- .../parser/dotnet/core_deps/parse.go | 68 ++++++++-- .../parser/dotnet/core_deps/parse_test.go | 68 ++++++++-- ...{ExampleApp1.deps.json => happy.deps.json} | 0 ...nvalidJson.deps.json => invalid.deps.json} | 0 ...aries.deps.json => no-libraries.deps.json} | 0 .../testdata/without-runtime.deps.json | 116 ++++++++++++++++++ .../language/dotnet/deps/deps_test.go | 1 + 10 files changed, 244 insertions(+), 21 deletions(-) rename pkg/dependency/parser/dotnet/core_deps/testdata/{ExampleApp1.deps.json => happy.deps.json} (100%) rename pkg/dependency/parser/dotnet/core_deps/testdata/{InvalidJson.deps.json => invalid.deps.json} (100%) rename pkg/dependency/parser/dotnet/core_deps/testdata/{NoLibraries.deps.json => no-libraries.deps.json} (100%) create mode 100644 pkg/dependency/parser/dotnet/core_deps/testdata/without-runtime.deps.json diff --git a/docs/docs/coverage/language/dotnet.md b/docs/docs/coverage/language/dotnet.md index 0a05454365e9..311e5c010b2d 100644 --- a/docs/docs/coverage/language/dotnet.md +++ b/docs/docs/coverage/language/dotnet.md @@ -21,6 +21,9 @@ The following table provides an outline of the features Trivy offers. ## *.deps.json Trivy parses `*.deps.json` files. Trivy currently excludes dev dependencies from the report. +!!! note + Trivy only includes runtime dependencies in the report. + ## packages.config Trivy only finds dependency names and versions from `packages.config` files. To build dependency graph, it is better to use `packages.lock.json` files. diff --git a/integration/testdata/dotnet.json.golden b/integration/testdata/dotnet.json.golden index 778b1270fcf2..4c76a08fc300 100644 --- a/integration/testdata/dotnet.json.golden +++ b/integration/testdata/dotnet.json.golden @@ -22,10 +22,11 @@ "Type": "dotnet-core", "Packages": [ { + "ID": "Newtonsoft.Json/9.0.1", "Name": "Newtonsoft.Json", "Identifier": { "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1", - "UID": "19955f480b8a6340" + "UID": "e678401f5d07418a" }, "Version": "9.0.1", "Layer": {}, @@ -40,10 +41,11 @@ "Vulnerabilities": [ { "VulnerabilityID": "GHSA-5crp-9r3c-p9vr", + "PkgID": "Newtonsoft.Json/9.0.1", "PkgName": "Newtonsoft.Json", "PkgIdentifier": { "PURL": "pkg:nuget/Newtonsoft.Json@9.0.1", - "UID": "19955f480b8a6340" + "UID": "e678401f5d07418a" }, "InstalledVersion": "9.0.1", "FixedVersion": "13.0.1", diff --git a/pkg/dependency/id.go b/pkg/dependency/id.go index 577ed5d0ac41..77dd85bed3e0 100644 --- a/pkg/dependency/id.go +++ b/pkg/dependency/id.go @@ -20,7 +20,8 @@ func ID(ltype types.LangType, name, version string) string { sep := "@" switch ltype { - case types.Conan: + // cf. https://github.com/dotnet/sdk/blob/529132850841a6bcfce96799262ce688e3851875/documentation/specs/runtime-configuration-file.md#targets-section-depsjson + case types.Conan, types.DotNetCore: sep = "/" case types.GoModule, types.GoBinary: // Return a module ID according the Go way. diff --git a/pkg/dependency/parser/dotnet/core_deps/parse.go b/pkg/dependency/parser/dotnet/core_deps/parse.go index 4314e9af9b3d..7fc8d3df5d5e 100644 --- a/pkg/dependency/parser/dotnet/core_deps/parse.go +++ b/pkg/dependency/parser/dotnet/core_deps/parse.go @@ -2,23 +2,51 @@ package core_deps import ( "io" + "sort" "strings" + "sync" "github.com/liamg/jfather" + "github.com/samber/lo" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/dependency" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) +type dotNetDependencies struct { + Libraries map[string]dotNetLibrary `json:"libraries"` + RuntimeTarget RuntimeTarget `json:"runtimeTarget"` + Targets map[string]map[string]TargetLib `json:"targets"` +} + +type dotNetLibrary struct { + Type string `json:"type"` + StartLine int + EndLine int +} + +type RuntimeTarget struct { + Name string `json:"name"` +} + +type TargetLib struct { + Runtime any `json:"runtime"` + RuntimeTargets any `json:"runtimeTargets"` + Native any `json:"native"` +} + type Parser struct { logger *log.Logger + once sync.Once } func NewParser() *Parser { return &Parser{ logger: log.WithPrefix("dotnet"), + once: sync.Once{}, } } @@ -29,11 +57,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc if err != nil { return nil, nil, xerrors.Errorf("read error: %w", err) } - if err := jfather.Unmarshal(input, &depsFile); err != nil { + if err = jfather.Unmarshal(input, &depsFile); err != nil { return nil, nil, xerrors.Errorf("failed to decode .deps.json file: %w", err) } - var pkgs []ftypes.Package + var pkgs ftypes.Packages for nameVer, lib := range depsFile.Libraries { if !strings.EqualFold(lib.Type, "package") { continue @@ -46,7 +74,20 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc continue } + // Take target libraries for RuntimeTarget + if targetLibs, ok := depsFile.Targets[depsFile.RuntimeTarget.Name]; !ok { + // If the target is not found, take all dependencies + p.once.Do(func() { + p.logger.Debug("Unable to find `Target` for Runtime Target Name. All dependencies from `libraries` section will be included in the report", log.String("Runtime Target Name", depsFile.RuntimeTarget.Name)) + }) + } else if !p.isRuntimeLibrary(targetLibs, nameVer) { + // Skip non-runtime libraries + // cf. https://github.com/aquasecurity/trivy/pull/7039#discussion_r1674566823 + continue + } + pkgs = append(pkgs, ftypes.Package{ + ID: dependency.ID(ftypes.DotNetCore, split[0], split[1]), Name: split[0], Version: split[1], Locations: []ftypes.Location{ @@ -58,17 +99,24 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc }) } + sort.Sort(pkgs) return pkgs, nil, nil } -type dotNetDependencies struct { - Libraries map[string]dotNetLibrary `json:"libraries"` -} - -type dotNetLibrary struct { - Type string `json:"type"` - StartLine int - EndLine int +// isRuntimeLibrary returns true if library contains `runtime`, `runtimeTarget` or `native` sections, or if the library is missing from `targetLibs`. +// See https://github.com/aquasecurity/trivy/discussions/4282#discussioncomment-8830365 for more details. +func (p *Parser) isRuntimeLibrary(targetLibs map[string]TargetLib, library string) bool { + lib, ok := targetLibs[library] + // Selected target doesn't contain library + // Mark these libraries as runtime to avoid mistaken omission + if !ok { + p.once.Do(func() { + p.logger.Debug("Unable to determine that this is runtime library. Library not found in `Target` section.", log.String("Library", library)) + }) + return true + } + // Check that `runtime`, `runtimeTarget` and `native` sections are not empty + return !lo.IsEmpty(lib) } // UnmarshalJSONWithMetadata needed to detect start and end lines of deps diff --git a/pkg/dependency/parser/dotnet/core_deps/parse_test.go b/pkg/dependency/parser/dotnet/core_deps/parse_test.go index a495fe0d61fe..82bf0e0a1d47 100644 --- a/pkg/dependency/parser/dotnet/core_deps/parse_test.go +++ b/pkg/dependency/parser/dotnet/core_deps/parse_test.go @@ -2,7 +2,6 @@ package core_deps import ( "os" - "path" "sort" "testing" @@ -13,29 +12,82 @@ import ( ) func TestParse(t *testing.T) { - vectors := []struct { + tests := []struct { + name string file string // Test input file want []ftypes.Package wantErr string }{ { - file: "testdata/ExampleApp1.deps.json", + name: "happy path", + file: "testdata/happy.deps.json", want: []ftypes.Package{ - {Name: "Newtonsoft.Json", Version: "13.0.1", Locations: []ftypes.Location{{StartLine: 33, EndLine: 39}}}, + { + ID: "Newtonsoft.Json/13.0.1", + Name: "Newtonsoft.Json", + Version: "13.0.1", + Locations: []ftypes.Location{ + { + StartLine: 33, + EndLine: 39, + }, + }, + }, }, }, { - file: "testdata/NoLibraries.deps.json", + name: "happy path with skipped libs", + file: "testdata/without-runtime.deps.json", + want: []ftypes.Package{ + { + ID: "JsonDiffPatch/2.0.61", + Name: "JsonDiffPatch", + Version: "2.0.61", + Locations: []ftypes.Location{ + { + StartLine: 66, + EndLine: 72, + }, + }, + }, + { + ID: "Libuv/1.9.1", + Name: "Libuv", + Version: "1.9.1", + Locations: []ftypes.Location{ + { + StartLine: 73, + EndLine: 79, + }, + }, + }, + { + ID: "System.Collections.Immutable/1.3.0", + Name: "System.Collections.Immutable", + Version: "1.3.0", + Locations: []ftypes.Location{ + { + StartLine: 101, + EndLine: 107, + }, + }, + }, + }, + }, + { + name: "happy path without libs", + file: "testdata/no-libraries.deps.json", want: nil, }, { - file: "testdata/InvalidJson.deps.json", + name: "sad path", + file: "testdata/invalid.deps.json", wantErr: "failed to decode .deps.json file: EOF", }, } - for _, tt := range vectors { - t.Run(path.Base(tt.file), func(t *testing.T) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { f, err := os.Open(tt.file) require.NoError(t, err) diff --git a/pkg/dependency/parser/dotnet/core_deps/testdata/ExampleApp1.deps.json b/pkg/dependency/parser/dotnet/core_deps/testdata/happy.deps.json similarity index 100% rename from pkg/dependency/parser/dotnet/core_deps/testdata/ExampleApp1.deps.json rename to pkg/dependency/parser/dotnet/core_deps/testdata/happy.deps.json diff --git a/pkg/dependency/parser/dotnet/core_deps/testdata/InvalidJson.deps.json b/pkg/dependency/parser/dotnet/core_deps/testdata/invalid.deps.json similarity index 100% rename from pkg/dependency/parser/dotnet/core_deps/testdata/InvalidJson.deps.json rename to pkg/dependency/parser/dotnet/core_deps/testdata/invalid.deps.json diff --git a/pkg/dependency/parser/dotnet/core_deps/testdata/NoLibraries.deps.json b/pkg/dependency/parser/dotnet/core_deps/testdata/no-libraries.deps.json similarity index 100% rename from pkg/dependency/parser/dotnet/core_deps/testdata/NoLibraries.deps.json rename to pkg/dependency/parser/dotnet/core_deps/testdata/no-libraries.deps.json diff --git a/pkg/dependency/parser/dotnet/core_deps/testdata/without-runtime.deps.json b/pkg/dependency/parser/dotnet/core_deps/testdata/without-runtime.deps.json new file mode 100644 index 000000000000..ba882c61cb5f --- /dev/null +++ b/pkg/dependency/parser/dotnet/core_deps/testdata/without-runtime.deps.json @@ -0,0 +1,116 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v6.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v6.0": { + "hello2/1.0.0": { + "dependencies": { + "JsonDiffPatch": "2.0.61" + }, + "runtime": { + "hello2.dll": {} + } + }, + "JsonDiffPatch/2.0.61": { + "dependencies": { + "Microsoft.NETCore.App": "1.1.2" + }, + "runtime": { + "lib/netcoreapp1.1/JsonDiffPatch.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "Libuv/1.9.1": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0" + }, + "runtimeTargets": { + "runtimes/debian-x64/native/libuv.so": { + "rid": "debian-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + }, + "runtimes/fedora-x64/native/libuv.so": { + "rid": "fedora-x64", + "assetType": "native", + "fileVersion": "0.0.0.0" + } + } + }, + "Microsoft.NETCore.App/1.1.2": { + "dependencies": { + "Libuv": "1.9.1", + "System.Collections.Immutable": "1.3.0" + } + }, + "Microsoft.NETCore.Platforms/1.1.0": {}, + "NETStandard.Library/1.6.0": { + "dependencies": { + "System.Net.Http": "4.1.0" + } + }, + "System.Net.Http/4.1.0": {} + } + }, + "libraries": { + "hello2/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "JsonDiffPatch/2.0.61": { + "type": "package", + "serviceable": true, + "sha512": "sha512-nZ4QtcU3jR+CBT69qcJBvCcWi5uKgPRrrvSMm4V8Z76ljJ/MFo1P55qXk/nQY0q0WC4v94m5qH4SDhovFfci+Q==", + "path": "jsondiffpatch/2.0.61", + "hashPath": "jsondiffpatch.2.0.61.nupkg.sha512" + }, + "Libuv/1.9.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-uqX2Frwf9PW8MaY7PRNY6HM5BpW1D8oj1EdqzrmbEFD5nH63Yat3aEjN/tws6Tw6Fk7LwmLBvtUh32tTeTaHiA==", + "path": "libuv/1.9.1", + "hashPath": "libuv.1.9.1.nupkg.sha512" + }, + "Microsoft.NETCore.App/1.1.2": { + "type": "package", + "serviceable": true, + "sha512": "sha512-fcN0Ob6rjY7Zu0770cA5l9wRJvj7+ltJPPdryUidejkkhao+y2AYrtezBTlP9nCSFXLmYR9BtaknORT17x8reA==", + "path": "microsoft.netcore.app/1.1.2", + "hashPath": "microsoft.netcore.app.1.1.2.nupkg.sha512" + }, + "Microsoft.NETCore.Platforms/1.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", + "path": "microsoft.netcore.platforms/1.1.0", + "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" + }, + "NETStandard.Library/1.6.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ypsCvIdCZ4IoYASJHt6tF2fMo7N30NLgV1EbmC+snO490OMl9FvVxmumw14rhReWU3j3g7BYudG6YCrchwHJlA==", + "path": "netstandard.library/1.6.0", + "hashPath": "netstandard.library.1.6.0.nupkg.sha512" + }, + "System.Collections.Immutable/1.3.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-zukBRPUuNxwy9m4TGWLxKAnoiMc9+B+8VXeXVyPiBPvOd7yLgAlZ1DlsRWJjMx4VsvhhF2+6q6kO2GRbPja6hA==", + "path": "system.collections.immutable/1.3.0", + "hashPath": "system.collections.immutable.1.3.0.nupkg.sha512" + }, + "System.Net.Http/4.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ULq9g3SOPVuupt+Y3U+A37coXzdNisB1neFCSKzBwo182u0RDddKJF8I5+HfyXqK6OhJPgeoAwWXrbiUXuRDsg==", + "path": "system.net.http/4.1.0", + "hashPath": "system.net.http.4.1.0.nupkg.sha512" + } + } +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go b/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go index c91d4467320e..d6a86c78084e 100644 --- a/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go +++ b/pkg/fanal/analyzer/language/dotnet/deps/deps_test.go @@ -29,6 +29,7 @@ func Test_depsLibraryAnalyzer_Analyze(t *testing.T) { FilePath: "testdata/datacollector.deps.json", Packages: types.Packages{ { + ID: "Newtonsoft.Json/9.0.1", Name: "Newtonsoft.Json", Version: "9.0.1", Locations: []types.Location{ From 5f78ea4aee9631e08a12492be91a9fd4ef8b384e Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Mon, 22 Jul 2024 13:57:28 +0700 Subject: [PATCH 237/352] refactor(fs): remove unused field for CompositeFS (#7195) Signed-off-by: nikpivkin --- pkg/fanal/analyzer/analyzer.go | 2 +- pkg/fanal/analyzer/analyzer_test.go | 2 +- pkg/fanal/analyzer/fs.go | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go index 119a589190a2..d2d07ffcc733 100644 --- a/pkg/fanal/analyzer/analyzer.go +++ b/pkg/fanal/analyzer/analyzer.go @@ -504,7 +504,7 @@ func (ag AnalyzerGroup) PostAnalyze(ctx context.Context, compositeFS *CompositeF // PostAnalyzerFS returns a composite filesystem that contains multiple filesystems for each post-analyzer func (ag AnalyzerGroup) PostAnalyzerFS() (*CompositeFS, error) { - return NewCompositeFS(ag) + return NewCompositeFS() } func (ag AnalyzerGroup) filePatternMatch(analyzerType Type, filePath string) bool { diff --git a/pkg/fanal/analyzer/analyzer_test.go b/pkg/fanal/analyzer/analyzer_test.go index 671c1050837f..313fccda7e94 100644 --- a/pkg/fanal/analyzer/analyzer_test.go +++ b/pkg/fanal/analyzer/analyzer_test.go @@ -614,7 +614,7 @@ func TestAnalyzerGroup_PostAnalyze(t *testing.T) { require.NoError(t, err) // Create a virtual filesystem - composite, err := analyzer.NewCompositeFS(analyzer.AnalyzerGroup{}) + composite, err := analyzer.NewCompositeFS() require.NoError(t, err) mfs := mapfs.New() diff --git a/pkg/fanal/analyzer/fs.go b/pkg/fanal/analyzer/fs.go index 28880b6b0339..4fec721e3cf6 100644 --- a/pkg/fanal/analyzer/fs.go +++ b/pkg/fanal/analyzer/fs.go @@ -15,19 +15,17 @@ import ( // CompositeFS contains multiple filesystems for post-analyzers type CompositeFS struct { - group AnalyzerGroup dir string files *sync.Map[Type, *mapfs.FS] } -func NewCompositeFS(group AnalyzerGroup) (*CompositeFS, error) { +func NewCompositeFS() (*CompositeFS, error) { tmpDir, err := os.MkdirTemp("", "analyzer-fs-*") if err != nil { return nil, xerrors.Errorf("unable to create temporary directory: %w", err) } return &CompositeFS{ - group: group, dir: tmpDir, files: new(sync.Map[Type, *mapfs.FS]), }, nil From 5f780450ff19156079172e625dd850c3b03a7280 Mon Sep 17 00:00:00 2001 From: oliverrr <32515201+this-oliver@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:57:50 +0200 Subject: [PATCH 238/352] docs: updates config file (#7188) --- docs/docs/references/configuration/config-file.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index f3c0006bfe07..d90ae7b26384 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -352,9 +352,9 @@ rego: # Default is false trace: false - # Same as '--skip-policy-update' + # Same as '--skip-check-update' # Default is false - skip-policy-update: false + skip-check-update: false # Same as '--config-policy' # Default is empty From 5cbc452a09822d1bf300ead88f0d613d4cf0349a Mon Sep 17 00:00:00 2001 From: Tom Fay Date: Mon, 22 Jul 2024 07:58:53 +0100 Subject: [PATCH 239/352] feat(mariner): Add support for Azure Linux (#7186) Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Co-authored-by: DmitriyLewen --- docs/community/contribute/pr.md | 2 +- .../coverage/os/{cbl-mariner.md => azure.md} | 23 ++++--- docs/docs/coverage/os/index.md | 38 +++++------ docs/docs/scanner/vulnerability.md | 34 +++++----- go.mod | 2 +- go.sum | 4 +- integration/testdata/mariner-1.0.json.golden | 8 +-- mkdocs.yml | 2 +- .../{mariner/mariner.go => azure/azure.go} | 22 ++++-- .../mariner_test.go => azure/azure_test.go} | 60 +++++++++++++++-- .../testdata/fixtures/azure.yaml} | 8 +++ .../azure/testdata/fixtures/data-source.yaml | 21 ++++++ .../testdata/fixtures/invalid.yaml | 0 pkg/detector/ospkg/detect.go | 5 +- .../testdata/fixtures/data-source.yaml | 14 ---- pkg/fanal/analyzer/all/import.go | 1 - pkg/fanal/analyzer/const.go | 1 + pkg/fanal/analyzer/os/mariner/mariner.go | 67 ------------------- pkg/fanal/analyzer/os/mariner/mariner_test.go | 60 ----------------- .../os/mariner/testdata/1.0/mariner-release | 2 - .../os/mariner/testdata/sad/mariner-release | 1 - pkg/fanal/analyzer/os/release/release.go | 4 ++ pkg/fanal/analyzer/os/release/release_test.go | 30 +++++++++ .../os/release/testdata/azurelinux-3.0 | 9 +++ .../analyzer/os/release/testdata/mariner-1.0 | 9 +++ .../analyzer/os/release/testdata/mariner-2.0 | 9 +++ pkg/fanal/types/const.go | 1 + pkg/purl/purl.go | 2 +- 28 files changed, 224 insertions(+), 215 deletions(-) rename docs/docs/coverage/os/{cbl-mariner.md => azure.md} (70%) rename pkg/detector/ospkg/{mariner/mariner.go => azure/azure.go} (81%) rename pkg/detector/ospkg/{mariner/mariner_test.go => azure/azure_test.go} (69%) rename pkg/detector/ospkg/{mariner/testdata/fixtures/mariner.yaml => azure/testdata/fixtures/azure.yaml} (68%) create mode 100644 pkg/detector/ospkg/azure/testdata/fixtures/data-source.yaml rename pkg/detector/ospkg/{mariner => azure}/testdata/fixtures/invalid.yaml (100%) delete mode 100644 pkg/detector/ospkg/mariner/testdata/fixtures/data-source.yaml delete mode 100644 pkg/fanal/analyzer/os/mariner/mariner.go delete mode 100644 pkg/fanal/analyzer/os/mariner/mariner_test.go delete mode 100644 pkg/fanal/analyzer/os/mariner/testdata/1.0/mariner-release delete mode 100644 pkg/fanal/analyzer/os/mariner/testdata/sad/mariner-release create mode 100644 pkg/fanal/analyzer/os/release/testdata/azurelinux-3.0 create mode 100644 pkg/fanal/analyzer/os/release/testdata/mariner-1.0 create mode 100644 pkg/fanal/analyzer/os/release/testdata/mariner-2.0 diff --git a/docs/community/contribute/pr.md b/docs/community/contribute/pr.md index 584f502b9fb9..e60b3d987c08 100644 --- a/docs/community/contribute/pr.md +++ b/docs/community/contribute/pr.md @@ -121,7 +121,7 @@ os: - redhat - alma - rocky -- mariner +- azure - oracle - debian - ubuntu diff --git a/docs/docs/coverage/os/cbl-mariner.md b/docs/docs/coverage/os/azure.md similarity index 70% rename from docs/docs/coverage/os/cbl-mariner.md rename to docs/docs/coverage/os/azure.md index 0ca42bbb9993..9b4151a0be3c 100644 --- a/docs/docs/coverage/os/cbl-mariner.md +++ b/docs/docs/coverage/os/azure.md @@ -1,4 +1,7 @@ -# CBL-Mariner +# Azure Linux (CBL-Mariner) + +*CBL-Mariner was rebranded to Azure Linux for version 3.0 onwards.* + Trivy supports the following scanners for OS packages. | Version | SBOM | Vulnerability | License | @@ -7,6 +10,8 @@ Trivy supports the following scanners for OS packages. | 1.0 (Distroless) | ✔ | ✔ | | | 2.0 | ✔ | ✔ | ✔ | | 2.0 (Distroless) | ✔ | ✔ | | +| 3.0 | ✔ | ✔ | ✔ | +| 3.0 (Distroless) | ✔ | ✔ | | The following table provides an outline of the targets Trivy supports. @@ -15,6 +20,7 @@ The following table provides an outline of the targets Trivy supports. | ------- | :-------------: | :-------------: | :----------: | | 1.0 | ✔ | ✔ | amd64, arm64 | | 2.0 | ✔ | ✔ | amd64, arm64 | +| 3.0 | ✔ | ✔ | amd64, arm64 | The table below outlines the features offered by Trivy. @@ -24,22 +30,22 @@ The table below outlines the features offered by Trivy. | [Dependency graph][dependency-graph] | ✓ | ## SBOM -Trivy detects packages that have been installed through package managers such as `dnf` and `yum`. +Trivy detects packages that have been installed through package managers such as `tdnf`, `dnf` and `yum`. ## Vulnerability -CBL-Mariner offers its own security advisories, and these are utilized when scanning CBL-Mariner for vulnerabilities. +Azure Linux offers its own security advisories, and these are utilized when scanning Azure Linux for vulnerabilities. ### Data Source See [here](../../scanner/vulnerability.md#data-sources). ### Fixed Version -Trivy takes fixed versions from [CBL-Mariner OVAL][oval]. +Trivy takes fixed versions from [Azure Linux OVAL][oval]. ### Severity -Trivy calculates the severity of an issue based on the severity provided in [CBL-Mariner OVAL][oval]. +Trivy calculates the severity of an issue based on the severity provided in [Azure Linux OVAL][oval]. ### Status -Trivy supports the following [vulnerability statuses] for CBL-Mariner. +Trivy supports the following [vulnerability statuses] for Azure Linux. | Status | Supported | | :-----------------: | :-------: | @@ -55,12 +61,11 @@ Trivy supports the following [vulnerability statuses] for CBL-Mariner. Trivy identifies licenses by examining the metadata of RPM packages. !!! note - License detection is not supported for CBL-Mariner Distroless. + License detection is not supported for Azure Linux Distroless images. [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies -[cbl-mariner]: https://github.com/microsoft/CBL-Mariner -[oval]: https://github.com/microsoft/CBL-MarinerVulnerabilityData/ +[oval]: https://github.com/microsoft/AzureLinuxVulnerabilityData/ [vulnerability statuses]: ../../configuration/filtering.md#by-status diff --git a/docs/docs/coverage/os/index.md b/docs/docs/coverage/os/index.md index 49982b1b2d69..a28e113f07c9 100644 --- a/docs/docs/coverage/os/index.md +++ b/docs/docs/coverage/os/index.md @@ -9,25 +9,25 @@ Trivy supports operating systems for ## Supported OS -| OS | Supported Versions | Package Managers | -|--------------------------------------|-------------------------------------|------------------| -| [Alpine Linux](alpine.md) | 2.2 - 2.7, 3.0 - 3.20, edge | apk | -| [Wolfi Linux](wolfi.md) | (n/a) | apk | -| [Chainguard](chainguard.md) | (n/a) | apk | -| [Red Hat Enterprise Linux](rhel.md) | 6, 7, 8 | dnf/yum/rpm | -| [CentOS](centos.md)[^1] | 6, 7, 8 | dnf/yum/rpm | -| [AlmaLinux](alma.md) | 8, 9 | dnf/yum/rpm | -| [Rocky Linux](rocky.md) | 8, 9 | dnf/yum/rpm | -| [Oracle Linux](oracle.md) | 5, 6, 7, 8 | dnf/yum/rpm | -| [CBL-Mariner](cbl-mariner.md) | 1.0, 2.0 | dnf/yum/rpm | -| [Amazon Linux](amazon.md) | 1, 2, 2023 | dnf/yum/rpm | -| [openSUSE Leap](suse.md) | 42, 15 | zypper/rpm | -| [openSUSE Tumbleweed](suse.md) | (n/a) | zypper/rpm | -| [SUSE Enterprise Linux](suse.md) | 11, 12, 15 | zypper/rpm | -| [Photon OS](photon.md) | 1.0, 2.0, 3.0, 4.0 | tndf/yum/rpm | -| [Debian GNU/Linux](debian.md) | 7, 8, 9, 10, 11, 12 | apt/dpkg | -| [Ubuntu](ubuntu.md) | All versions supported by Canonical | apt/dpkg | -| [OSs with installed Conda](conda.md) | - | conda | +| OS | Supported Versions | Package Managers | +|---------------------------------------|-------------------------------------|------------------| +| [Alpine Linux](alpine.md) | 2.2 - 2.7, 3.0 - 3.20, edge | apk | +| [Wolfi Linux](wolfi.md) | (n/a) | apk | +| [Chainguard](chainguard.md) | (n/a) | apk | +| [Red Hat Enterprise Linux](rhel.md) | 6, 7, 8 | dnf/yum/rpm | +| [CentOS](centos.md)[^1] | 6, 7, 8 | dnf/yum/rpm | +| [AlmaLinux](alma.md) | 8, 9 | dnf/yum/rpm | +| [Rocky Linux](rocky.md) | 8, 9 | dnf/yum/rpm | +| [Oracle Linux](oracle.md) | 5, 6, 7, 8 | dnf/yum/rpm | +| [Azure Linux (CBL-Mariner)](azure.md) | 1.0, 2.0, 3.0 | tdnf/dnf/yum/rpm | +| [Amazon Linux](amazon.md) | 1, 2, 2023 | dnf/yum/rpm | +| [openSUSE Leap](suse.md) | 42, 15 | zypper/rpm | +| [openSUSE Tumbleweed](suse.md) | (n/a) | zypper/rpm | +| [SUSE Enterprise Linux](suse.md) | 11, 12, 15 | zypper/rpm | +| [Photon OS](photon.md) | 1.0, 2.0, 3.0, 4.0 | tndf/yum/rpm | +| [Debian GNU/Linux](debian.md) | 7, 8, 9, 10, 11, 12 | apt/dpkg | +| [Ubuntu](ubuntu.md) | All versions supported by Canonical | apt/dpkg | +| [OSs with installed Conda](conda.md) | - | conda | ## Supported container images diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md index ef233b4db4da..ba612ee06b28 100644 --- a/docs/docs/scanner/vulnerability.md +++ b/docs/docs/scanner/vulnerability.md @@ -19,22 +19,22 @@ See [here](../coverage/os/index.md#supported-os) for the supported OSes. ### Data Sources -| OS | Source | -| ------------- | ------------------------------------------------------------ | -| Arch Linux | [Vulnerable Issues][arch] | -| Alpine Linux | [secdb][alpine] | -| Wolfi Linux | [secdb][wolfi] | -| Chainguard | [secdb][chainguard] | -| Amazon Linux | [Amazon Linux Security Center][amazon] | -| Debian | [Security Bug Tracker][debian-tracker] / [OVAL][debian-oval] | -| Ubuntu | [Ubuntu CVE Tracker][ubuntu] | -| RHEL/CentOS | [OVAL][rhel-oval] / [Security Data][rhel-api] | -| AlmaLinux | [AlmaLinux Product Errata][alma] | -| Rocky Linux | [Rocky Linux UpdateInfo][rocky] | -| Oracle Linux | [OVAL][oracle] | -| CBL-Mariner | [OVAL][mariner] | -| OpenSUSE/SLES | [CVRF][suse] | -| Photon OS | [Photon Security Advisory][photon] | +| OS | Source | +|---------------------------|--------------------------------------------------------------| +| Arch Linux | [Vulnerable Issues][arch] | +| Alpine Linux | [secdb][alpine] | +| Wolfi Linux | [secdb][wolfi] | +| Chainguard | [secdb][chainguard] | +| Amazon Linux | [Amazon Linux Security Center][amazon] | +| Debian | [Security Bug Tracker][debian-tracker] / [OVAL][debian-oval] | +| Ubuntu | [Ubuntu CVE Tracker][ubuntu] | +| RHEL/CentOS | [OVAL][rhel-oval] / [Security Data][rhel-api] | +| AlmaLinux | [AlmaLinux Product Errata][alma] | +| Rocky Linux | [Rocky Linux UpdateInfo][rocky] | +| Oracle Linux | [OVAL][oracle] | +| Azure Linux (CBL-Mariner) | [OVAL][azure] | +| OpenSUSE/SLES | [CVRF][suse] | +| Photon OS | [Photon Security Advisory][photon] | #### Data Source Selection Trivy **only** consumes security advisories from the sources listed in the above table. @@ -288,7 +288,7 @@ Total: 7 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 3, CRITICAL: 2) [oracle]: https://linux.oracle.com/security/oval/ [suse]: http://ftp.suse.com/pub/projects/security/cvrf/ [photon]: https://packages.vmware.com/photon/photon_cve_metadata/ -[mariner]: https://github.com/microsoft/CBL-MarinerVulnerabilityData/ +[azure]: https://github.com/microsoft/AzureLinuxVulnerabilityData/ [php-ghsa]: https://github.com/advisories?query=ecosystem%3Acomposer [python-ghsa]: https://github.com/advisories?query=ecosystem%3Apip diff --git a/go.mod b/go.mod index 98dab8d4fff2..b01cd7c01e22 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac github.com/aquasecurity/tml v0.6.1 github.com/aquasecurity/trivy-checks v0.13.0 - github.com/aquasecurity/trivy-db v0.0.0-20240701103400-8e907467e9ab + github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b github.com/aws/aws-sdk-go-v2 v1.30.3 diff --git a/go.sum b/go.sum index 114f8585b370..0770f06bb8f2 100644 --- a/go.sum +++ b/go.sum @@ -771,8 +771,8 @@ github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gw github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= github.com/aquasecurity/trivy-checks v0.13.0 h1:na6PTdY4U0uK/fjz3HNRYBxvYSJ8vgTb57a5T8Y5t9w= github.com/aquasecurity/trivy-checks v0.13.0/go.mod h1:Xec/SMVGV66I7RgUqOX9MEr+YxBqHXDVLTYmpspPi3E= -github.com/aquasecurity/trivy-db v0.0.0-20240701103400-8e907467e9ab h1:EmpLGFgRJOstPWDpL4KW+Xap4zRYxyctXDTj5luMQdE= -github.com/aquasecurity/trivy-db v0.0.0-20240701103400-8e907467e9ab/go.mod h1:f+wSW9D5txv8S+tw4D4WNOibaUJYwvNnQuQlGQ8gO6c= +github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 h1:6/T8sFdNVG/AwOGoK6X55h7hF7LYqK8bsuPz8iEz8jM= +github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04/go.mod h1:0T6oy2t1Iedt+yi3Ml5cpOYp5FZT4MI1/mx+3p+PIs8= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b h1:h7gsIzHyrxpQnayOuQI0kX7+8rVcqhV6G5bM3KVFyJU= diff --git a/integration/testdata/mariner-1.0.json.golden b/integration/testdata/mariner-1.0.json.golden index 7325bf74f6e6..8805f104c60c 100644 --- a/integration/testdata/mariner-1.0.json.golden +++ b/integration/testdata/mariner-1.0.json.golden @@ -6,7 +6,7 @@ "Metadata": { "OS": { "Family": "cbl-mariner", - "Name": "1.0.20220122" + "Name": "1.0" }, "ImageID": "sha256:8cdcbf18341ed8afa5322e7b0077f8ef3f46896882c921df5f97c51b369f6767", "DiffIDs": [ @@ -34,7 +34,7 @@ }, "Results": [ { - "Target": "testdata/fixtures/images/mariner-1.0.tar.gz (cbl-mariner 1.0.20220122)", + "Target": "testdata/fixtures/images/mariner-1.0.tar.gz (cbl-mariner 1.0)", "Class": "os-pkgs", "Type": "cbl-mariner", "Vulnerabilities": [ @@ -42,7 +42,7 @@ "VulnerabilityID": "CVE-2022-0261", "PkgName": "vim", "PkgIdentifier": { - "PURL": "pkg:rpm/cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64\u0026distro=cbl-mariner-1.0.20220122", + "PURL": "pkg:rpm/cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64\u0026distro=cbl-mariner-1.0", "UID": "3f08cd76fa5ba73d" }, "InstalledVersion": "8.2.4081-1.cm1", @@ -79,7 +79,7 @@ "VulnerabilityID": "CVE-2022-0158", "PkgName": "vim", "PkgIdentifier": { - "PURL": "pkg:rpm/cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64\u0026distro=cbl-mariner-1.0.20220122", + "PURL": "pkg:rpm/cbl-mariner/vim@8.2.4081-1.cm1?arch=x86_64\u0026distro=cbl-mariner-1.0", "UID": "3f08cd76fa5ba73d" }, "InstalledVersion": "8.2.4081-1.cm1", diff --git a/mkdocs.yml b/mkdocs.yml index 2222a30220fb..deddf4a896e4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -75,7 +75,7 @@ nav: - AlmaLinux: docs/coverage/os/alma.md - Alpine Linux: docs/coverage/os/alpine.md - Amazon Linux: docs/coverage/os/amazon.md - - CBL-Mariner: docs/coverage/os/cbl-mariner.md + - Azure Linux (CBL-Mariner): docs/coverage/os/azure.md - CentOS: docs/coverage/os/centos.md - Chainguard: docs/coverage/os/chainguard.md - Conda: docs/coverage/os/conda.md diff --git a/pkg/detector/ospkg/mariner/mariner.go b/pkg/detector/ospkg/azure/azure.go similarity index 81% rename from pkg/detector/ospkg/mariner/mariner.go rename to pkg/detector/ospkg/azure/azure.go index ae9d80157381..98f235353a2d 100644 --- a/pkg/detector/ospkg/mariner/mariner.go +++ b/pkg/detector/ospkg/azure/azure.go @@ -1,4 +1,4 @@ -package mariner +package azure import ( "context" @@ -6,7 +6,7 @@ import ( version "github.com/knqyf263/go-rpm-version" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy-db/pkg/vulnsrc/mariner" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/azure" osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" @@ -16,16 +16,24 @@ import ( // Scanner implements the CBL-Mariner scanner type Scanner struct { - vs mariner.VulnSrc + vs azure.VulnSrc } // NewScanner is the factory method for Scanner -func NewScanner() *Scanner { +func newScanner(distribution azure.Distribution) *Scanner { return &Scanner{ - vs: mariner.NewVulnSrc(), + vs: azure.NewVulnSrc(distribution), } } +func NewAzureScanner() *Scanner { + return newScanner(azure.Azure) +} + +func NewMarinerScanner() *Scanner { + return newScanner(azure.Mariner) +} + // Detect vulnerabilities in package using CBL-Mariner scanner func (s *Scanner) Detect(ctx context.Context, osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { // e.g. 1.0.20210127 @@ -36,10 +44,10 @@ func (s *Scanner) Detect(ctx context.Context, osVer string, _ *ftypes.Repository var vulns []types.DetectedVulnerability for _, pkg := range pkgs { - // CBL Mariner OVAL contains source package names only. + // Azure Linux OVAL contains source package names only. advisories, err := s.vs.Get(osVer, pkg.SrcName) if err != nil { - return nil, xerrors.Errorf("failed to get CBL-Mariner advisories: %w", err) + return nil, xerrors.Errorf("failed to get Azure Linux advisories: %w", err) } sourceVersion := version.NewVersion(utils.FormatSrcVersion(pkg)) diff --git a/pkg/detector/ospkg/mariner/mariner_test.go b/pkg/detector/ospkg/azure/azure_test.go similarity index 69% rename from pkg/detector/ospkg/mariner/mariner_test.go rename to pkg/detector/ospkg/azure/azure_test.go index 6e1ee9a37583..cc9f0a92d2af 100644 --- a/pkg/detector/ospkg/mariner/mariner_test.go +++ b/pkg/detector/ospkg/azure/azure_test.go @@ -1,4 +1,4 @@ -package mariner_test +package azure_test import ( "testing" @@ -8,15 +8,17 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + azurevs "github.com/aquasecurity/trivy-db/pkg/vulnsrc/azure" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" "github.com/aquasecurity/trivy/internal/dbtest" - "github.com/aquasecurity/trivy/pkg/detector/ospkg/mariner" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/azure" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" ) func TestScanner_Detect(t *testing.T) { type args struct { + dist azurevs.Distribution osVer string pkgs []ftypes.Package } @@ -30,10 +32,11 @@ func TestScanner_Detect(t *testing.T) { { name: "happy path 1.0 SrcName and Name are different", fixtures: []string{ - "testdata/fixtures/mariner.yaml", + "testdata/fixtures/azure.yaml", "testdata/fixtures/data-source.yaml", }, args: args{ + dist: azurevs.Mariner, osVer: "1.0", pkgs: []ftypes.Package{ { @@ -69,10 +72,11 @@ func TestScanner_Detect(t *testing.T) { { name: "happy path 2.0", fixtures: []string{ - "testdata/fixtures/mariner.yaml", + "testdata/fixtures/azure.yaml", "testdata/fixtures/data-source.yaml", }, args: args{ + dist: azurevs.Mariner, osVer: "2.0", pkgs: []ftypes.Package{ { @@ -104,6 +108,46 @@ func TestScanner_Detect(t *testing.T) { }, }, }, + { + name: "happy path 3.0", + fixtures: []string{ + "testdata/fixtures/azure.yaml", + "testdata/fixtures/data-source.yaml", + }, + args: args{ + dist: azurevs.Azure, + osVer: "3.0", + pkgs: []ftypes.Package{ + { + Name: "php", + Epoch: 0, + Version: "8.3.6", + Release: "1.azl3", + Arch: "aarch64", + SrcName: "php", + SrcEpoch: 0, + SrcVersion: "8.3.6", + SrcRelease: "1.azl3", + Licenses: []string{"Php"}, + Layer: ftypes.Layer{}, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + PkgName: "php", + VulnerabilityID: "CVE-2024-2408", + InstalledVersion: "8.3.6-1.azl3", + FixedVersion: "8.3.8-1.azl3", + Layer: ftypes.Layer{}, + DataSource: &dbTypes.DataSource{ + ID: vulnerability.AzureLinux, + Name: "Azure Linux Vulnerability Data", + URL: "https://github.com/microsoft/AzureLinuxVulnerabilityData", + }, + }, + }, + }, { name: "broken advisory", fixtures: []string{ @@ -111,6 +155,7 @@ func TestScanner_Detect(t *testing.T) { "testdata/fixtures/data-source.yaml", }, args: args{ + dist: azurevs.Mariner, osVer: "1.0", pkgs: []ftypes.Package{ { @@ -128,7 +173,7 @@ func TestScanner_Detect(t *testing.T) { }, }, }, - wantErr: "failed to get CBL-Mariner advisories", + wantErr: "failed to get Azure Linux advisories", }, } for _, tt := range tests { @@ -136,7 +181,10 @@ func TestScanner_Detect(t *testing.T) { _ = dbtest.InitDB(t, tt.fixtures) defer db.Close() - s := mariner.NewScanner() + s := azure.NewAzureScanner() + if tt.args.dist == azurevs.Mariner { + s = azure.NewMarinerScanner() + } got, err := s.Detect(nil, tt.args.osVer, nil, tt.args.pkgs) if tt.wantErr != "" { require.Error(t, err) diff --git a/pkg/detector/ospkg/mariner/testdata/fixtures/mariner.yaml b/pkg/detector/ospkg/azure/testdata/fixtures/azure.yaml similarity index 68% rename from pkg/detector/ospkg/mariner/testdata/fixtures/mariner.yaml rename to pkg/detector/ospkg/azure/testdata/fixtures/azure.yaml index 7f044d1a8b1a..f9829e17ad41 100644 --- a/pkg/detector/ospkg/mariner/testdata/fixtures/mariner.yaml +++ b/pkg/detector/ospkg/azure/testdata/fixtures/azure.yaml @@ -14,3 +14,11 @@ - bucket: vim pairs: - key: CVE-2022-0261 + +- bucket: Azure Linux 3.0 + pairs: + - bucket: php + pairs: + - key: CVE-2024-2408 + value: + FixedVersion: 8.3.8-1.azl3 diff --git a/pkg/detector/ospkg/azure/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/azure/testdata/fixtures/data-source.yaml new file mode 100644 index 000000000000..7c9f386f157d --- /dev/null +++ b/pkg/detector/ospkg/azure/testdata/fixtures/data-source.yaml @@ -0,0 +1,21 @@ +- bucket: data-source + pairs: + - key: CBL-Mariner 1.0 + value: + ID: "cbl-mariner" + Name: "CBL-Mariner Vulnerability Data" + URL: "https://github.com/microsoft/CBL-MarinerVulnerabilityData" +- bucket: data-source + pairs: + - key: CBL-Mariner 2.0 + value: + ID: "cbl-mariner" + Name: "CBL-Mariner Vulnerability Data" + URL: "https://github.com/microsoft/CBL-MarinerVulnerabilityData" +- bucket: data-source + pairs: + - key: Azure Linux 3.0 + value: + ID: "azure" + Name: "Azure Linux Vulnerability Data" + URL: "https://github.com/microsoft/AzureLinuxVulnerabilityData" diff --git a/pkg/detector/ospkg/mariner/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/azure/testdata/fixtures/invalid.yaml similarity index 100% rename from pkg/detector/ospkg/mariner/testdata/fixtures/invalid.yaml rename to pkg/detector/ospkg/azure/testdata/fixtures/invalid.yaml diff --git a/pkg/detector/ospkg/detect.go b/pkg/detector/ospkg/detect.go index e05b590107ca..0f4a1df2a9d3 100644 --- a/pkg/detector/ospkg/detect.go +++ b/pkg/detector/ospkg/detect.go @@ -10,9 +10,9 @@ import ( "github.com/aquasecurity/trivy/pkg/detector/ospkg/alma" "github.com/aquasecurity/trivy/pkg/detector/ospkg/alpine" "github.com/aquasecurity/trivy/pkg/detector/ospkg/amazon" + "github.com/aquasecurity/trivy/pkg/detector/ospkg/azure" "github.com/aquasecurity/trivy/pkg/detector/ospkg/chainguard" "github.com/aquasecurity/trivy/pkg/detector/ospkg/debian" - "github.com/aquasecurity/trivy/pkg/detector/ospkg/mariner" "github.com/aquasecurity/trivy/pkg/detector/ospkg/oracle" "github.com/aquasecurity/trivy/pkg/detector/ospkg/photon" "github.com/aquasecurity/trivy/pkg/detector/ospkg/redhat" @@ -33,7 +33,8 @@ var ( ftypes.Alpine: alpine.NewScanner(), ftypes.Alma: alma.NewScanner(), ftypes.Amazon: amazon.NewScanner(), - ftypes.CBLMariner: mariner.NewScanner(), + ftypes.Azure: azure.NewAzureScanner(), + ftypes.CBLMariner: azure.NewMarinerScanner(), ftypes.Debian: debian.NewScanner(), ftypes.Ubuntu: ubuntu.NewScanner(), ftypes.RedHat: redhat.NewScanner(), diff --git a/pkg/detector/ospkg/mariner/testdata/fixtures/data-source.yaml b/pkg/detector/ospkg/mariner/testdata/fixtures/data-source.yaml deleted file mode 100644 index 57ce67b2ecd8..000000000000 --- a/pkg/detector/ospkg/mariner/testdata/fixtures/data-source.yaml +++ /dev/null @@ -1,14 +0,0 @@ -- bucket: data-source - pairs: - - key: CBL-Mariner 1.0 - value: - ID: "cbl-mariner" - Name: "CBL-Mariner Vulnerability Data" - URL: "https://github.com/microsoft/CBL-MarinerVulnerabilityData" -- bucket: data-source - pairs: - - key: CBL-Mariner 2.0 - value: - ID: "cbl-mariner" - Name: "CBL-Mariner Vulnerability Data" - URL: "https://github.com/microsoft/CBL-MarinerVulnerabilityData" diff --git a/pkg/fanal/analyzer/all/import.go b/pkg/fanal/analyzer/all/import.go index 1849bcebf682..5345073fd3cf 100644 --- a/pkg/fanal/analyzer/all/import.go +++ b/pkg/fanal/analyzer/all/import.go @@ -41,7 +41,6 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/alpine" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/amazonlinux" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/debian" - _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/mariner" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/redhatbase" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/release" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os/ubuntu" diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 6e9d0332eb61..681f8b9987cc 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -13,6 +13,7 @@ const ( TypeOSRelease Type = "os-release" TypeAlpine Type = "alpine" TypeAmazon Type = "amazon" + TypeAzure Type = "azurelinux" TypeCBLMariner Type = "cbl-mariner" TypeDebian Type = "debian" TypePhoton Type = "photon" diff --git a/pkg/fanal/analyzer/os/mariner/mariner.go b/pkg/fanal/analyzer/os/mariner/mariner.go deleted file mode 100644 index f24a8b1886b3..000000000000 --- a/pkg/fanal/analyzer/os/mariner/mariner.go +++ /dev/null @@ -1,67 +0,0 @@ -package mariner - -import ( - "bufio" - "context" - "io" - "os" - "path/filepath" - "strings" - - "golang.org/x/xerrors" - - "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" - "github.com/aquasecurity/trivy/pkg/fanal/types" -) - -func init() { - analyzer.RegisterAnalyzer(&marinerOSAnalyzer{}) -} - -const ( - version = 1 - requiredFile = "etc/mariner-release" -) - -type marinerOSAnalyzer struct{} - -func (a marinerOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { - foundOS, err := a.parseRelease(input.Content) - if err != nil { - return nil, xerrors.Errorf("release parse error: %w", err) - } - return &analyzer.AnalysisResult{ - OS: foundOS, - }, nil -} - -func (a marinerOSAnalyzer) parseRelease(r io.Reader) (types.OS, error) { - scanner := bufio.NewScanner(r) - for scanner.Scan() { - line := scanner.Text() - fields := strings.Fields(line) - if len(fields) != 2 { - continue - } - if strings.EqualFold(fields[0], "cbl-mariner") { - return types.OS{ - Family: types.CBLMariner, - Name: fields[1], - }, nil - } - } - return types.OS{}, xerrors.Errorf("cbl-mariner: %w", fos.AnalyzeOSError) -} - -func (a marinerOSAnalyzer) Required(filePath string, _ os.FileInfo) bool { - return filepath.ToSlash(filePath) == requiredFile -} - -func (a marinerOSAnalyzer) Type() analyzer.Type { - return analyzer.TypeCBLMariner -} - -func (a marinerOSAnalyzer) Version() int { - return version -} diff --git a/pkg/fanal/analyzer/os/mariner/mariner_test.go b/pkg/fanal/analyzer/os/mariner/mariner_test.go deleted file mode 100644 index e13730a021cb..000000000000 --- a/pkg/fanal/analyzer/os/mariner/mariner_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package mariner - -import ( - "context" - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/types" -) - -func Test_marinerOSAnalyzer_Analyze(t *testing.T) { - tests := []struct { - name string - inputFile string - want *analyzer.AnalysisResult - wantErr string - }{ - { - name: "happy path with CBL Mariner 1.0", - inputFile: "testdata/1.0/mariner-release", - want: &analyzer.AnalysisResult{ - OS: types.OS{ - Family: types.CBLMariner, - Name: "1.0.20220122", - }, - }, - }, - { - name: "sad path", - inputFile: "testdata/sad/mariner-release", - wantErr: "cbl-mariner: unable to analyze OS information", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - a := marinerOSAnalyzer{} - f, err := os.Open(tt.inputFile) - require.NoError(t, err) - defer f.Close() - - ctx := context.Background() - got, err := a.Analyze(ctx, analyzer.AnalysisInput{ - FilePath: "etc/mariner-release", - Content: f, - }) - if tt.wantErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr) - return - } - - require.NoError(t, err) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/fanal/analyzer/os/mariner/testdata/1.0/mariner-release b/pkg/fanal/analyzer/os/mariner/testdata/1.0/mariner-release deleted file mode 100644 index 1a8769674acf..000000000000 --- a/pkg/fanal/analyzer/os/mariner/testdata/1.0/mariner-release +++ /dev/null @@ -1,2 +0,0 @@ -CBL-Mariner 1.0.20220122 -MARINER_BUILD_NUMBER=7da4f23 diff --git a/pkg/fanal/analyzer/os/mariner/testdata/sad/mariner-release b/pkg/fanal/analyzer/os/mariner/testdata/sad/mariner-release deleted file mode 100644 index 4fda2bc57d30..000000000000 --- a/pkg/fanal/analyzer/os/mariner/testdata/sad/mariner-release +++ /dev/null @@ -1 +0,0 @@ -MARINER_BUILD_NUMBER=7da4f23 diff --git a/pkg/fanal/analyzer/os/release/release.go b/pkg/fanal/analyzer/os/release/release.go index 229c13c932aa..8da24644d5f7 100644 --- a/pkg/fanal/analyzer/os/release/release.go +++ b/pkg/fanal/analyzer/os/release/release.go @@ -61,6 +61,10 @@ func (a osReleaseAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInp family = types.Wolfi case "chainguard": family = types.Chainguard + case "azurelinux": + family = types.Azure + case "mariner": + family = types.CBLMariner } if family != "" && versionID != "" { diff --git a/pkg/fanal/analyzer/os/release/release_test.go b/pkg/fanal/analyzer/os/release/release_test.go index 862f39f4cf17..3b534ad7b14d 100644 --- a/pkg/fanal/analyzer/os/release/release_test.go +++ b/pkg/fanal/analyzer/os/release/release_test.go @@ -90,6 +90,36 @@ func Test_osReleaseAnalyzer_Analyze(t *testing.T) { }, }, }, + { + name: "Azure Linux", + inputFile: "testdata/azurelinux-3.0", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.Azure, + Name: "3.0", + }, + }, + }, + { + name: "Mariner 2.0", + inputFile: "testdata/mariner-2.0", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.CBLMariner, + Name: "2.0", + }, + }, + }, + { + name: "Mariner 1.0", + inputFile: "testdata/mariner-1.0", + want: &analyzer.AnalysisResult{ + OS: types.OS{ + Family: types.CBLMariner, + Name: "1.0", + }, + }, + }, { name: "Unknown OS", inputFile: "testdata/unknown", diff --git a/pkg/fanal/analyzer/os/release/testdata/azurelinux-3.0 b/pkg/fanal/analyzer/os/release/testdata/azurelinux-3.0 new file mode 100644 index 000000000000..a033cb09377e --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/azurelinux-3.0 @@ -0,0 +1,9 @@ +NAME="Microsoft Azure Linux" +VERSION="3.0.20240624" +ID=azurelinux +VERSION_ID="3.0" +PRETTY_NAME="Microsoft Azure Linux 3.0" +ANSI_COLOR="1;34" +HOME_URL="https://aka.ms/azurelinux" +BUG_REPORT_URL="https://aka.ms/azurelinux" +SUPPORT_URL="https://aka.ms/azurelinux" diff --git a/pkg/fanal/analyzer/os/release/testdata/mariner-1.0 b/pkg/fanal/analyzer/os/release/testdata/mariner-1.0 new file mode 100644 index 000000000000..aef312e77294 --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/mariner-1.0 @@ -0,0 +1,9 @@ +NAME="Common Base Linux Mariner" +VERSION="1.0.20230713" +ID=mariner +VERSION_ID="1.0" +PRETTY_NAME="CBL-Mariner/Linux" +ANSI_COLOR="1;34" +HOME_URL="https://aka.ms/cbl-mariner" +BUG_REPORT_URL="https://aka.ms/cbl-mariner" +SUPPORT_URL="https://aka.ms/cbl-mariner" diff --git a/pkg/fanal/analyzer/os/release/testdata/mariner-2.0 b/pkg/fanal/analyzer/os/release/testdata/mariner-2.0 new file mode 100644 index 000000000000..c8a70bc4464c --- /dev/null +++ b/pkg/fanal/analyzer/os/release/testdata/mariner-2.0 @@ -0,0 +1,9 @@ +NAME="Common Base Linux Mariner" +VERSION="2.0.20240123" +ID=mariner +VERSION_ID="2.0" +PRETTY_NAME="CBL-Mariner/Linux" +ANSI_COLOR="1;34" +HOME_URL="https://aka.ms/cbl-mariner" +BUG_REPORT_URL="https://aka.ms/cbl-mariner" +SUPPORT_URL="https://aka.ms/cbl-mariner" diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index 7253404c0be1..c257154e24ea 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -24,6 +24,7 @@ const ( Alma OSType = "alma" Alpine OSType = "alpine" Amazon OSType = "amazon" + Azure OSType = "azurelinux" CBLMariner OSType = "cbl-mariner" CentOS OSType = "centos" Chainguard OSType = "chainguard" diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index 12b27e6290e6..ba19d40c26a9 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -477,7 +477,7 @@ func purlType(t ftypes.TargetType) string { case ftypes.RedHat, ftypes.CentOS, ftypes.Rocky, ftypes.Alma, ftypes.Amazon, ftypes.Fedora, ftypes.Oracle, ftypes.OpenSUSE, ftypes.OpenSUSELeap, ftypes.OpenSUSETumbleweed, ftypes.SLES, ftypes.Photon, - ftypes.CBLMariner: + ftypes.Azure, ftypes.CBLMariner: return packageurl.TypeRPM case TypeOCI: return packageurl.TypeOCI From 9d5201808da89607ae43570bdf1f335b482a6b79 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Mon, 22 Jul 2024 14:01:45 +0700 Subject: [PATCH 240/352] fix(server): pass license categories to options (#7203) Signed-off-by: nikpivkin --- pkg/rpc/server/server.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 25b43b2afd92..b0e58f87c0e9 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -9,6 +9,7 @@ import ( "google.golang.org/protobuf/types/known/emptypb" "github.com/aquasecurity/trivy/pkg/cache" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/rpc" "github.com/aquasecurity/trivy/pkg/scanner" @@ -46,10 +47,17 @@ func (s *ScanServer) Scan(ctx context.Context, in *rpcScanner.ScanRequest) (*rpc scanners := lo.Map(in.Options.Scanners, func(s string, index int) types.Scanner { return types.Scanner(s) }) + + licenseCategories := lo.MapEntries(in.Options.LicenseCategories, + func(k string, v *rpcScanner.Licenses) (ftypes.LicenseCategory, []string) { + return ftypes.LicenseCategory(k), v.Names + }) + options := types.ScanOptions{ - PkgTypes: in.Options.PkgTypes, - Scanners: scanners, - IncludeDevDeps: in.Options.IncludeDevDeps, + PkgTypes: in.Options.PkgTypes, + Scanners: scanners, + IncludeDevDeps: in.Options.IncludeDevDeps, + LicenseCategories: licenseCategories, } results, os, err := s.localScanner.Scan(ctx, in.Target, in.ArtifactId, in.BlobIds, options) if err != nil { From 92695630c04398a5e8b763a7da14160de9e41a5d Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Mon, 22 Jul 2024 11:31:26 +0400 Subject: [PATCH 241/352] chore(vex): update subcomponents for CVE-2023-42363/42364/42365/42366 (#7201) Signed-off-by: knqyf263 --- .vex/oci.openvex.json | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/.vex/oci.openvex.json b/.vex/oci.openvex.json index 6a58df8cdf41..b689d43afac1 100644 --- a/.vex/oci.openvex.json +++ b/.vex/oci.openvex.json @@ -14,21 +14,24 @@ "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", "subcomponents": [ {"@id": "pkg:apk/alpine/busybox"}, - {"@id": "pkg:apk/alpine/busybox-binsh"} + {"@id": "pkg:apk/alpine/busybox-binsh"}, + {"@id": "pkg:apk/alpine/ssl_client"} ] }, { "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", "subcomponents": [ {"@id": "pkg:apk/alpine/busybox"}, - {"@id": "pkg:apk/alpine/busybox-binsh"} + {"@id": "pkg:apk/alpine/busybox-binsh"}, + {"@id": "pkg:apk/alpine/ssl_client"} ] }, { "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", "subcomponents": [ {"@id": "pkg:apk/alpine/busybox"}, - {"@id": "pkg:apk/alpine/busybox-binsh"} + {"@id": "pkg:apk/alpine/busybox-binsh"}, + {"@id": "pkg:apk/alpine/ssl_client"} ] } ], @@ -45,22 +48,24 @@ "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", "subcomponents": [ {"@id": "pkg:apk/alpine/busybox"}, - {"@id": "pkg:apk/alpine/busybox-binsh"} + {"@id": "pkg:apk/alpine/busybox-binsh"}, + {"@id": "pkg:apk/alpine/ssl_client"} ] }, { "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", "subcomponents": [ - { - "@id": "pkg:apk/alpine/busybox" - } + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"}, + {"@id": "pkg:apk/alpine/ssl_client"} ] }, { "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", "subcomponents": [ {"@id": "pkg:apk/alpine/busybox"}, - {"@id": "pkg:apk/alpine/busybox-binsh"} + {"@id": "pkg:apk/alpine/busybox-binsh"}, + {"@id": "pkg:apk/alpine/ssl_client"} ] } ], @@ -77,21 +82,24 @@ "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", "subcomponents": [ {"@id": "pkg:apk/alpine/busybox"}, - {"@id": "pkg:apk/alpine/busybox-binsh"} + {"@id": "pkg:apk/alpine/busybox-binsh"}, + {"@id": "pkg:apk/alpine/ssl_client"} ] }, { "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", "subcomponents": [ {"@id": "pkg:apk/alpine/busybox"}, - {"@id": "pkg:apk/alpine/busybox-binsh"} + {"@id": "pkg:apk/alpine/busybox-binsh"}, + {"@id": "pkg:apk/alpine/ssl_client"} ] }, { "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", "subcomponents": [ {"@id": "pkg:apk/alpine/busybox"}, - {"@id": "pkg:apk/alpine/busybox-binsh"} + {"@id": "pkg:apk/alpine/busybox-binsh"}, + {"@id": "pkg:apk/alpine/ssl_client"} ] } ], @@ -108,21 +116,24 @@ "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", "subcomponents": [ {"@id": "pkg:apk/alpine/busybox"}, - {"@id": "pkg:apk/alpine/busybox-binsh"} + {"@id": "pkg:apk/alpine/busybox-binsh"}, + {"@id": "pkg:apk/alpine/ssl_client"} ] }, { "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", "subcomponents": [ {"@id": "pkg:apk/alpine/busybox"}, - {"@id": "pkg:apk/alpine/busybox-binsh"} + {"@id": "pkg:apk/alpine/busybox-binsh"}, + {"@id": "pkg:apk/alpine/ssl_client"} ] }, { "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", "subcomponents": [ {"@id": "pkg:apk/alpine/busybox"}, - {"@id": "pkg:apk/alpine/busybox-binsh"} + {"@id": "pkg:apk/alpine/busybox-binsh"}, + {"@id": "pkg:apk/alpine/ssl_client"} ] } ], From 92b13be668bd20f8e9dac2f0cb8e5a2708b9b3b5 Mon Sep 17 00:00:00 2001 From: afdesk Date: Tue, 23 Jul 2024 16:59:39 +0600 Subject: [PATCH 242/352] fix(secret): trim excessively long lines (#7192) --- pkg/fanal/secret/scanner.go | 14 ++++++-- pkg/fanal/secret/scanner_test.go | 46 ++++++++++++++++++++----- pkg/fanal/secret/testdata/obfuscated.js | 1 + 3 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 pkg/fanal/secret/testdata/obfuscated.js diff --git a/pkg/fanal/secret/scanner.go b/pkg/fanal/secret/scanner.go index cc022bb82db4..c006a38b63a1 100644 --- a/pkg/fanal/secret/scanner.go +++ b/pkg/fanal/secret/scanner.go @@ -476,7 +476,10 @@ func toFinding(rule Rule, loc Location, content []byte) types.SecretFinding { } } -const secretHighlightRadius = 2 // number of lines above + below each secret to include in code output +const ( + secretHighlightRadius = 2 // number of lines above + below each secret to include in code output + maxLineLength = 100 // all lines longer will be cut off +) func findLocation(start, end int, content []byte) (int, int, types.Code, string) { startLineNum := bytes.Count(content[:start], lineSep) @@ -511,9 +514,16 @@ func findLocation(start, end int, content []byte) (int, int, types.Code, string) rawLines := lines[codeStart:codeEnd] var foundFirst bool for i, rawLine := range rawLines { - strRawLine := string(rawLine) realLine := codeStart + i inCause := realLine >= startLineNum && realLine <= endLineNum + + var strRawLine string + if len(rawLine) > maxLineLength { + strRawLine = lo.Ternary(inCause, matchLine, string(rawLine[:maxLineLength])) + } else { + strRawLine = string(rawLine) + } + code.Lines = append(code.Lines, types.Line{ Number: codeStart + i + 1, Content: strRawLine, diff --git a/pkg/fanal/secret/scanner_test.go b/pkg/fanal/secret/scanner_test.go index 04f1f08fc1b2..e5b8f68f943d 100644 --- a/pkg/fanal/secret/scanner_test.go +++ b/pkg/fanal/secret/scanner_test.go @@ -353,8 +353,8 @@ func TestSecretScanner(t *testing.T) { Lines: []types.Line{ { Number: 1, - Content: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa GITHUB_PAT=**************************************** bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - Highlighted: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa GITHUB_PAT=**************************************** bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + Content: "aaaaaaaaaaaaaaaaaa GITHUB_PAT=**************************************** bbbbbbbbbbbbbbbbbbb", + Highlighted: "aaaaaaaaaaaaaaaaaa GITHUB_PAT=**************************************** bbbbbbbbbbbbbbbbbbb", IsCause: true, FirstCause: true, LastCause: true, @@ -462,8 +462,8 @@ func TestSecretScanner(t *testing.T) { Lines: []types.Line{ { Number: 1, - Content: "{\"key\": \"-----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************-----END RSA PRIVATE KEY-----\\n\"}", - Highlighted: "{\"key\": \"-----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************-----END RSA PRIVATE KEY-----\\n\"}", + Content: "----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************-----END RSA PRIVATE", + Highlighted: "----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************-----END RSA PRIVATE", IsCause: true, FirstCause: true, LastCause: true, @@ -483,8 +483,8 @@ func TestSecretScanner(t *testing.T) { Lines: []types.Line{ { Number: 1, - Content: "-----BEGIN RSA PRIVATE KEY-----****************************************************************************************************************************************************************************************-----END RSA PRIVATE KEY-----", - Highlighted: "-----BEGIN RSA PRIVATE KEY-----****************************************************************************************************************************************************************************************-----END RSA PRIVATE KEY-----", + Content: "----BEGIN RSA PRIVATE KEY-----****************************************************************************************************************************************************************************************-----END RSA PRIVATE", + Highlighted: "----BEGIN RSA PRIVATE KEY-----****************************************************************************************************************************************************************************************-----END RSA PRIVATE", IsCause: true, FirstCause: true, LastCause: true, @@ -504,8 +504,8 @@ func TestSecretScanner(t *testing.T) { Lines: []types.Line{ { Number: 1, - Content: "-----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************-----END RSA PRIVATE KEY-----", - Highlighted: "-----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************-----END RSA PRIVATE KEY-----", + Content: "----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************-----END RSA PRIVATE", + Highlighted: "----BEGIN RSA PRIVATE KEY-----**************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************-----END RSA PRIVATE", IsCause: true, FirstCause: true, LastCause: true, @@ -667,6 +667,27 @@ func TestSecretScanner(t *testing.T) { }, }, } + wantFindingTokenInsideJs := types.SecretFinding{ + RuleID: "stripe-publishable-token", + Category: "Stripe", + Title: "Stripe Publishable Key", + Severity: "LOW", + StartLine: 1, + EndLine: 1, + Match: "){case a.ez.PRODUCTION:return\"********************************\";case a.ez.TEST:cas", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "){case a.ez.PRODUCTION:return\"********************************\";case a.ez.TEST:cas", + Highlighted: "){case a.ez.PRODUCTION:return\"********************************\";case a.ez.TEST:cas", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } tests := []struct { name string @@ -982,6 +1003,15 @@ func TestSecretScanner(t *testing.T) { Findings: []types.SecretFinding{wantMultiLine}, }, }, + { + name: "long obfuscated js code with secrets", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: filepath.Join("testdata", "obfuscated.js"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "obfuscated.js"), + Findings: []types.SecretFinding{wantFindingTokenInsideJs}, + }, + }, } for _, tt := range tests { diff --git a/pkg/fanal/secret/testdata/obfuscated.js b/pkg/fanal/secret/testdata/obfuscated.js new file mode 100644 index 000000000000..f3e1c53be227 --- /dev/null +++ b/pkg/fanal/secret/testdata/obfuscated.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmattermost_webapp=self.webpackChunkmattermost_webapp||[]).push([[8055],{59713:(e,t,n)=>{n.d(t,{$d:()=>b,DF:()=>G,Du:()=>B,GA:()=>F,Is:()=>M,JJ:()=>J,K6:()=>re,KB:()=>f,KO:()=>x,Md:()=>ae,NB:()=>ce,O$:()=>E,OT:()=>ee,QQ:()=>oe,SP:()=>K,Sm:()=>se,T8:()=>Y,UV:()=>pe,W3:()=>C,X$:()=>X,_9:()=>de,a6:()=>Q,d1:()=>ne,d5:()=>$,dA:()=>te,gY:()=>ie,go:()=>T,h5:()=>z,iB:,n(61418);var a=n(17554),s=n(98644),i=n(80139),r=n(23712);function o(e){return{type:r.MF.NEEDS_LOGGED_IN_LIMIT_REACHED_CHECK,data:e}}function l(e).func,onInputChange:s().func,onInputBlur:s().func,buttonFooter:s().element},M.defaultProps={className:""};const T=M},30736:(e,t,n)=>{n.d(t,{Z:()=>u});var a=n(23615),s=n.n(a),i=(n(48410),n(84390)),r=n.n(i),o=n(72060),l=n(80623),c=n(83398);const d=[{code:"AL",name:"Alabama"},{code:"AK",name:"Alaska"},{code:"AZ",name:"Arizona"},{code:"AR",name:"Arkansas"},{code:"CA",name:"California"},{code:"CO",name:"Colorado"},{code:"CT",name:"Connecticut"},{code:"DE",name:"Delaware"},{code:"DC",name:"District of Columbia"},{code:"FL",name:"Florida"},{code:"GA",name:"Georgia"},{code:"HI",name:"Hawaii"},{code:"ID",name:"Idaho"},{code:"IL",name:"Illinois"},{code:"IN",name:"Indiana"},{code:"IA",name:"Iowa"},{code:"KS",name:"Kansas"},{code:"KY",name:"Kentucky"},{code:"LA",name:"Louisiana"},{code:"ME",name:"Maine"},{code:"MD",name:"Maryland"},{code:"MA",name:"Massachusetts"},{code:"MI",name:"Michigan"},{code:"MN",name:"Minnesota"},{code:"MS",name:"Mississippi"},{code:"MO",name:"Missouri"},{code:"MT",name:"Montana"},{code:"NE",name:"Nebraska"},{code:"NV",name:"Nevada"},{code:"NH",name:"New Hampshire"},{code:"NJ",name:"New Jersey"},{code:"NM",name:"New Mexico"},{code:"NY",name:"New York"},{code:"NC",name:"North Carolina"},{code:"ND",name:"North Dakota"},{code:"OH",name:"Ohio"},{code:"OK",name:"Oklahoma"},{code:"OR",name:"Oregon"},{code:"PA",name:"Pennsylvania"},{code:"PR",name:"Puerto Rico"},{code:"RI",name:"Rhode Island"},{code:"SC",name:"South Carolina"},{code:"SD",name:"South Dakota"},{code:"TN",name:"Tennessee"},{code:"TX",name:"Texas"},{code:"UT",name:"Utah"},{code:"VT",name:"Vermont"},{code:"VA",name:"Virginia"},{code:"WA",name:"Washington"},{code:"WV",name:"West Virginia"},{code:"WI",name:"Wisconsin"},{code:"WY",name:"Wyoming"}],m=[{code:"AB",name:"Alberta"},{code:"BC",name:"British Columbia"},{code:"MB",name:"Manitoba"},{code:"NB",name:"New Brunswick"},{code:"NL",name:"Newfoundland and Labrador"},{code:"NT",name:"Northwest Territories"},{code:"NS",name:"Nova Scotia"},{code:"NU",name:"Nunavut"},{code:"ON",name:"Ontario"},{code:"PE",name:"Prince Edward Island"},{code:"QC",name:"Quebec"},{code:"SK",name:"Saskatchewan"},{code:"YT",name:"Yukon Territory"}];function u(e){const{formatMessage:t}=(0,o.useIntl)(),n=t=>{e.onChange(t.value)};let a=[];if("US"===e.country?a=d:"CA"===e.country&&(a=m),a.length>0){const s={};return e.testId&&(s.testId=e.testId),r().createElement(l.Z,Object.assign({},s,{onChange:n,value:e.state?{value:e.state,label:e.state}:void 0,options:a.map((e=>({value:e.code,label:e.name}))),legend:t({id:"admin.billing.subscription.stateprovince",defaultMessage:"State/Province"}),placeholder:t({id:"admin.billing.subscription.stateprovince",defaultMessage:"State/Province"}),name:"country_dropdown"}))}return r().createElement(c.Z,{name:"state",type:"text",value:e.state,onChange:t=>{e.onChange(t.target.value)},onBlur:e.onBlur,placeholder:t({id:"admin.billing.subscription.stateprovince",defaultMessage:"State/Province"}),required:!0})}u.propTypes={country:s().string.isRequired,state:s().string.isRequired,testId:s().string,onChange:s().func.isRequired,onBlur:s().func}},35154:(e,t,n)=>{n.d(t,{li:()=>r,tt:()=>l,wW:()=>o}),n(61418);var a=n(2507);function s(e){return e}function i(e){return async(e,t,n)=>({setupIntent:{id:"testid",status:"succeeded"}})}const r=e=>e?i:s,o="https://fonts.googleapis.com/css?family=Open+Sans:400,400i,600,600i&display=swap",l=e=>{switch(e.entities.general.config.ServiceEnvironment){case a.ez.PRODUCTION:return"pk_live_xXX1xXXXx1xXxXxxx11x1XXX";case a.ez.TEST:case a.ez.DEV:return""}return""}},21500:(e,t,n)=>{n.r(t),n.d(t,{default:()=>h});var a=n(23615),s=n.n(a),i=(n(61418),n(14078),n(48410),n(92189),n(66726)),r=n.n(i),o=n(84390),l=n.n(o),c=n(47407),d=n(83765),m=n(26337),u=n(41894);function p(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var a=n.call(e,"string");if("object"!=typeof a)return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}class h extends l().PureComponent{constructor(e){super(e),p(this,"parentNode",null),p(this,"pdfCanvasRef",{}),p(this,"downloadFile",(e=>{const t=this.props.fileInfo.link||(0,c.gN)(this.props.fileInfo.id);e.preventDefault(),window.location.href=t})),p(this,"isInViewport",(e=>{var t,n,a,s;const i=e.getBoundingClientRect(),r=null!==(t=null===(n=this.container.current)||void 0===n?void 0:n.scrollTop)&&void 0!==t?t:0,o=r+(null!==(a=null===(s=this.parentNode)||void 0===s?void 0:s.clientHeight)&&void 0!==a?a:0);return i.top>=r&&i.top<=o||i.bottom>=r&&i.bottom<=o||i.top<=r&&i.bottom>=o})),p(this,"renderPDFPage",(async e=>{const t=this.pdfCanvasRef["pdfCanvasRef-".concat(e)].current;if(!t)return;if(e>=3&&!this.isInViewport(t))return;if(this.pdfPagesRendered[e])return;const n=await this.loadPage(this.state.pdf,e),a=t.getContext("2d"),s=n.getViewport({scale:this.props.scale});t.height=s.height,t.width=s.width;const i={canvasContext:a,viewport:s};await n.render(i).promise,this.pdfPagesRendered[e]=!0})),p(this,"getPdfDocument",(async()=>{try{const e=await Promise.all([n.e(7803),n.e(9707)]).then(n.t.bind(n,47803,23)),t=await n.e(5456).then(n.t.bind(n,65456,23));e.GlobalWorkerOptions.workerSrc=t;const a=await e.getDocument({url:this.props.fileUrl,cMapUrl:(0,u.fO)()+"/static/cmaps/",cMapPacked:!0}).promise;this.onDocumentLoad(a)}catch(e){this.onDocumentLoadError(e)}})),p(this,"onDocumentLoad",(e=>{this.setState({pdf:e,numPages:e.numPages});for(let t=0;t{console.log("Unable to load PDF preview: "+e),this.setState({loading:!1,success:!1})})),p(this,"loadPage",(async(e,t)=>{if(this.state.pdfPagesLoaded[t])return this.state.pdfPages[t];const n=await e.getPage(t+1),a=Object.assign({},this.state.pdfPages);a[t]=n;) \ No newline at end of file From 051ac3901fee8136f54c6fc9b1a541240a44e8aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 10:48:26 +0400 Subject: [PATCH 243/352] chore(deps): bump the docker group across 1 directory with 2 updates (#7208) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: DmitriyLewen --- go.mod | 18 +++---- go.sum | 38 ++++++++------- pkg/fanal/analyzer/buildinfo/dockerfile.go | 55 +++++++++++++++------- 3 files changed, 69 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index b01cd7c01e22..798ea8fa96d6 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/cheggaaa/pb/v3 v3.1.5 github.com/containerd/containerd v1.7.19 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 - github.com/docker/docker v27.0.3+incompatible + github.com/docker/docker v27.1.0+incompatible github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.17.0 github.com/go-git/go-git/v5 v5.12.0 @@ -74,7 +74,6 @@ require ( github.com/liamg/iamgo v0.0.9 github.com/liamg/jfather v0.0.7 github.com/liamg/memoryfs v1.6.0 - github.com/magefile/mage v1.15.0 github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 github.com/masahiro331/go-ext4-filesystem v0.0.0-20231208112839-4339555a0cd4 @@ -86,7 +85,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 - github.com/moby/buildkit v0.14.1 + github.com/moby/buildkit v0.15.0 github.com/open-policy-agent/opa v0.66.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 @@ -133,6 +132,8 @@ require ( sigs.k8s.io/yaml v1.4.0 ) +require github.com/magefile/mage v1.14.0 + require ( cloud.google.com/go v0.112.1 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect @@ -201,9 +202,9 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect - github.com/docker/cli v26.1.4+incompatible // indirect + github.com/docker/cli v27.0.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker-credential-helpers v0.8.0 // indirect + github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -268,7 +269,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.7 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.9 // indirect @@ -301,7 +302,7 @@ require ( github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/opencontainers/runtime-spec v1.1.0 // indirect + github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect @@ -314,7 +315,7 @@ require ( github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.2.0 // indirect @@ -336,6 +337,7 @@ require ( github.com/tchap/go-patricia/v2 v2.3.1 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect + github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/vbatts/tar-split v0.11.5 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect diff --git a/go.sum b/go.sum index 0770f06bb8f2..9f2aaaed05ed 100644 --- a/go.sum +++ b/go.sum @@ -1064,8 +1064,8 @@ github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v26.1.4+incompatible h1:I8PHdc0MtxEADqYJZvhBrW9bo8gawKwwenxRM7/rLu8= -github.com/docker/cli v26.1.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.0.3+incompatible h1:usGs0/BoBW8MWxGeEtqPMkzOY56jZ6kYlSN5BLDioCQ= +github.com/docker/cli v27.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= @@ -1074,11 +1074,11 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE= -github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.1.0+incompatible h1:rEHVQc4GZ0MIQKifQPHSFGV/dVgaZafgRf8fCPtDYBs= +github.com/docker/docker v27.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= -github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= -github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= +github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= +github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= @@ -1385,8 +1385,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= @@ -1554,8 +1554,8 @@ github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHU github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg= @@ -1611,8 +1611,8 @@ github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6w github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= -github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= -github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= +github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -1693,8 +1693,8 @@ github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQ 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/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.14.1 h1:2epLCZTkn4CikdImtsLtIa++7DzCimrrZCT1sway+oI= -github.com/moby/buildkit v0.14.1/go.mod h1:1XssG7cAqv5Bz1xcGMxJL123iCv5TYN4Z/qf647gfuk= +github.com/moby/buildkit v0.15.0 h1:vnZLThPr9JU6SvItctKoa6NfgPZ8oUApg/TCOaa/SVs= +github.com/moby/buildkit v0.15.0/go.mod h1:oN9S+8I7wF26vrqn9NuAF6dFSyGTfXvtiu9o1NlnnH4= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= @@ -1787,8 +1787,8 @@ github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/ github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= -github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= +github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= @@ -1873,8 +1873,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= @@ -2029,6 +2029,8 @@ github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8= +github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= diff --git a/pkg/fanal/analyzer/buildinfo/dockerfile.go b/pkg/fanal/analyzer/buildinfo/dockerfile.go index f17e5987b1b8..10ae20b4a4c9 100644 --- a/pkg/fanal/analyzer/buildinfo/dockerfile.go +++ b/pkg/fanal/analyzer/buildinfo/dockerfile.go @@ -9,6 +9,7 @@ import ( "github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/moby/buildkit/frontend/dockerfile/parser" "github.com/moby/buildkit/frontend/dockerfile/shell" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -25,7 +26,7 @@ const dockerfileAnalyzerVersion = 1 type dockerfileAnalyzer struct{} func (a dockerfileAnalyzer) Analyze(_ context.Context, target analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { - // ported from https://github.com/moby/buildkit/blob/b33357bcd2e3319b0323037c900c13b45a228df1/frontend/dockerfile/dockerfile2llb/convert.go#L73 + // ported from https://github.com/moby/buildkit/blob/e83d79a51fb49aeb921d8a2348ae14a58701c98c/frontend/dockerfile/dockerfile2llb/convert.go#L88-L89 dockerfile, err := parser.Parse(target.Content) if err != nil { return nil, xerrors.Errorf("dockerfile parse error: %w", err) @@ -44,28 +45,27 @@ func (a dockerfileAnalyzer) Analyze(_ context.Context, target analyzer.AnalysisI } shlex := shell.NewLex(dockerfile.EscapeToken) - env := metaArgsToMap(args) - + envs := metaArgsToEnvGetter(args) var component, arch string for _, st := range stages { for _, cmd := range st.Commands { switch c := cmd.(type) { case *instructions.EnvCommand: - for _, kvp := range c.Env { - env[kvp.Key] = kvp.Value - } + envs.addKeyValuePairsToEnvGetter(c.Env) case *instructions.LabelCommand: for _, kvp := range c.Labels { - key, err := shlex.ProcessWordWithMap(kvp.Key, env) + workResult, err := shlex.ProcessWordWithMatches(kvp.Key, envs) if err != nil { return nil, xerrors.Errorf("unable to evaluate the label '%s': %w", kvp.Key, err) } - key = strings.ToLower(key) + key := strings.ToLower(workResult.Result) if key == "com.redhat.component" || key == "bzcomponent" { - component, err = shlex.ProcessWordWithMap(kvp.Value, env) + workResult, err = shlex.ProcessWordWithMatches(kvp.Value, envs) + component = workResult.Result } else if key == "architecture" { - arch, err = shlex.ProcessWordWithMap(kvp.Value, env) + workResult, err = shlex.ProcessWordWithMatches(kvp.Value, envs) + arch = workResult.Result } if err != nil { @@ -117,15 +117,38 @@ func parseVersion(nvr string) string { return version } -// https://github.com/moby/buildkit/blob/b33357bcd2e3319b0323037c900c13b45a228df1/frontend/dockerfile/dockerfile2llb/convert.go#L474-L482 -func metaArgsToMap(metaArgs []instructions.KeyValuePairOptional) map[string]string { - m := make(map[string]string) +type envGetter struct { + m map[string]string +} - for _, arg := range metaArgs { - m[arg.Key] = arg.ValueString() +func (e *envGetter) addKeyValuePairsToEnvGetter(kvp instructions.KeyValuePairs) { + if e.m == nil { + e.m = make(map[string]string) } - return m + for _, kv := range kvp { + e.m[kv.Key] = kv.Value + } +} + +func metaArgsToEnvGetter(metaArgs []instructions.KeyValuePairOptional) *envGetter { + env := &envGetter{ + m: make(map[string]string), + } + + for _, kv := range metaArgs { + env.m[kv.Key] = kv.ValueString() + } + return env +} + +func (e *envGetter) Get(key string) (string, bool) { + v, ok := e.m[key] + return v, ok +} + +func (e *envGetter) Keys() []string { + return lo.Keys(e.m) } func setKVValue(kvpo instructions.KeyValuePairOptional, values map[string]string) instructions.KeyValuePairOptional { From 0e286f074f5bc584b05132f5b4652858da35df17 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 25 Jul 2024 10:22:20 +0500 Subject: [PATCH 244/352] ci: use free runner for all tests except `build tests` (#7215) --- .github/workflows/bypass-test.yaml | 4 ++-- .github/workflows/test.yaml | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/bypass-test.yaml b/.github/workflows/bypass-test.yaml index eafff9769a1d..3a3102e3e574 100644 --- a/.github/workflows/bypass-test.yaml +++ b/.github/workflows/bypass-test.yaml @@ -22,12 +22,12 @@ jobs: runs-on: ${{ matrix.operating-system }} strategy: matrix: - operating-system: [ubuntu-latest-m, windows-latest, macos-latest] + operating-system: [ubuntu-latest, windows-latest, macos-latest] steps: - run: 'echo "No test required"' integration: name: Integration Test - runs-on: ubuntu-latest-m + runs-on: ubuntu-latest steps: - run: 'echo "No test required"' \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2ff471be1c7d..70a21462b29a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,7 +16,7 @@ jobs: runs-on: ${{ matrix.operating-system }} strategy: matrix: - operating-system: [ubuntu-latest-m, windows-latest, macos-latest] + operating-system: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v4.1.6 @@ -31,7 +31,7 @@ jobs: echo "Run 'go mod tidy' and push it" exit 1 fi - if: matrix.operating-system == 'ubuntu-latest-m' + if: matrix.operating-system == 'ubuntu-latest' - name: Lint id: lint @@ -39,7 +39,7 @@ jobs: with: version: v1.59 args: --verbose --out-format=line-number - if: matrix.operating-system == 'ubuntu-latest-m' + if: matrix.operating-system == 'ubuntu-latest' - name: Check if linter failed run: | @@ -60,14 +60,14 @@ jobs: echo "Run 'mage docs:generate' and push it" exit 1 fi - if: matrix.operating-system == 'ubuntu-latest-m' + if: matrix.operating-system == 'ubuntu-latest' - name: Run unit tests run: mage test:unit integration: name: Integration Test - runs-on: ubuntu-latest-m + runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory uses: actions/checkout@v4.1.6 @@ -87,7 +87,7 @@ jobs: k8s-integration: name: K8s Integration Test - runs-on: ubuntu-latest-m + runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory uses: actions/checkout@v4.1.6 @@ -129,7 +129,7 @@ jobs: vm-test: name: VM Integration Test - runs-on: ubuntu-latest-m + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4.1.6 From efb1f6938321eec3529ef4fea6608261f6771ae0 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:49:20 +0500 Subject: [PATCH 245/352] feat(sbom): add vulnerability support for SPDX formats (#7213) --- pkg/flag/options.go | 9 +-- pkg/sbom/spdx/marshal.go | 18 ++++++ pkg/sbom/spdx/marshal_test.go | 106 ++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 7 deletions(-) diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 33190fb76fbe..b485b74ef677 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -410,15 +410,10 @@ func (o *Options) enableSBOM() { o.Scanners.Enable(types.SBOMScanner) } - if o.Format == types.FormatSPDX || o.Format == types.FormatSPDXJSON { - log.Info(`"--format spdx" and "--format spdx-json" disable security scanning`) - o.Scanners = types.Scanners{types.SBOMScanner} - } - - if o.Format == types.FormatCycloneDX { + if o.Format == types.FormatCycloneDX || o.Format == types.FormatSPDX || o.Format == types.FormatSPDXJSON { // Vulnerability scanning is disabled by default for CycloneDX. if !viper.IsSet(ScannersFlag.ConfigName) { - log.Info(`"--format cyclonedx" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the CycloneDX report.`) + log.Info(fmt.Sprintf(`"--format %[1]s" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the "%[1]s" report.`, o.Format)) o.Scanners = nil } o.Scanners.Enable(types.SBOMScanner) diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index 1c4a84d60109..35bd0448aa5d 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -147,6 +147,15 @@ func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*spdx.Document, if err != nil { return nil, xerrors.Errorf("spdx package error: %w", err) } + + // Add advisories for package + // cf. https://spdx.github.io/spdx-spec/v2.3/how-to-use/#k1-including-security-information-in-a-spdx-document + if vulns, ok := bom.Vulnerabilities()[c.ID()]; ok { + for _, v := range vulns { + spdxPackage.PackageExternalReferences = append(spdxPackage.PackageExternalReferences, m.advisoryExternalReference(v.PrimaryURL)) + } + } + packages = append(packages, &spdxPackage) packageIDs[c.ID()] = spdxPackage.PackageSPDXIdentifier @@ -184,6 +193,7 @@ func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*spdx.Document, relationShips = append(relationShips, m.spdxRelationShip(refA, refB, m.spdxRelationshipType(rel.Type))) } } + sortPackages(packages) sortRelationships(relationShips) sortFiles(files) @@ -268,6 +278,14 @@ func (m *Marshaler) purlExternalReference(packageURL string) *spdx.PackageExtern } } +func (m *Marshaler) advisoryExternalReference(primaryURL string) *spdx.PackageExternalReference { + return &spdx.PackageExternalReference{ + Category: common.CategorySecurity, + RefType: common.TypeSecurityAdvisory, + Locator: primaryURL, + } +} + func (m *Marshaler) spdxPackage(c *core.Component, pkgDownloadLocation string) (spdx.Package, error) { pkgID, err := calcPkgID(m.hasher, c) if err != nil { diff --git a/pkg/sbom/spdx/marshal_test.go b/pkg/sbom/spdx/marshal_test.go index 122e529c14d5..4ed35b7fc08c 100644 --- a/pkg/sbom/spdx/marshal_test.go +++ b/pkg/sbom/spdx/marshal_test.go @@ -821,6 +821,112 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, }, + { + name: "happy path with vulnerability", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "log4j-core-2.17.0.jar", + ArtifactType: artifact.TypeFilesystem, + Results: types.Results{ + { + Target: "Java", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + Packages: []ftypes.Package{ + { + Name: "org.apache.logging.log4j:log4j-core", + Version: "2.17.0", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.logging.log4j", + Name: "log4j-core", + Version: "2.17.0", + }, + }, + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2021-44832", + PkgName: "org.apache.logging.log4j:log4j-core", + InstalledVersion: "2.17.0", + FixedVersion: "2.3.2, 2.12.4, 2.17.1", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2021-44832", + }, + }, + }, + }, + }, + wantSBOM: &spdx.Document{ + SPDXVersion: spdx.Version, + DataLicense: spdx.DataLicense, + SPDXIdentifier: "DOCUMENT", + DocumentName: "log4j-core-2.17.0.jar", + DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/log4j-core-2.17.0.jar-3ff14136-e09f-4df9-80ea-000000000003", + CreationInfo: &spdx.CreationInfo{ + Creators: []common.Creator{ + { + Creator: "aquasecurity", + CreatorType: "Organization", + }, + { + Creator: "trivy-0.38.1", + CreatorType: "Tool", + }, + }, + Created: "2021-08-25T12:20:30Z", + }, + Packages: []*spdx.Package{ + { + PackageSPDXIdentifier: spdx.ElementID("Package-4ee6f197f4811213"), + PackageDownloadLocation: "NONE", + PackageName: "org.apache.logging.log4j:log4j-core", + PackageVersion: "2.17.0", + PackageLicenseConcluded: "NONE", + PackageLicenseDeclared: "NONE", + PackageExternalReferences: []*spdx.PackageExternalReference{ + { + Category: tspdx.CategoryPackageManager, + RefType: tspdx.RefTypePurl, + Locator: "pkg:maven/org.apache.logging.log4j/log4j-core@2.17.0", + }, + { + Category: "SECURITY", + RefType: "advisory", + Locator: "https://avd.aquasec.com/nvd/cve-2021-44832", + }, + }, + PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, + PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, + PackageAttributionTexts: []string{ + "PkgType: jar", + }, + }, + { + PackageSPDXIdentifier: spdx.ElementID("Filesystem-121e7e7a43f02ab"), + PackageDownloadLocation: "NONE", + PackageName: "log4j-core-2.17.0.jar", + PackageAttributionTexts: []string{ + "SchemaVersion: 2", + }, + PrimaryPackagePurpose: tspdx.PackagePurposeSource, + }, + }, + Relationships: []*spdx.Relationship{ + { + RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"}, + RefB: spdx.DocElementID{ElementRefID: "Filesystem-121e7e7a43f02ab"}, + Relationship: "DESCRIBES", + }, + { + RefA: spdx.DocElementID{ElementRefID: "Filesystem-121e7e7a43f02ab"}, + RefB: spdx.DocElementID{ElementRefID: "Package-4ee6f197f4811213"}, + Relationship: "CONTAINS", + }, + }, + }, + }, { name: "happy path aggregate results", inputReport: types.Report{ From 8c87194f0a6b194bc5d340c8a65bd99a3132d973 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:49:55 +0500 Subject: [PATCH 246/352] fix(secret): update length of `hugging-face-access-token` (#7216) --- pkg/fanal/secret/builtin-rules.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fanal/secret/builtin-rules.go b/pkg/fanal/secret/builtin-rules.go index 6d0c0eacfdcd..cada98d6681a 100644 --- a/pkg/fanal/secret/builtin-rules.go +++ b/pkg/fanal/secret/builtin-rules.go @@ -165,7 +165,7 @@ var builtinRules = []Rule{ Category: CategoryHuggingFace, Severity: "CRITICAL", Title: "Hugging Face Access Token", - Regex: MustCompile(`hf_[A-Za-z0-9]{39}`), + Regex: MustCompile(`hf_[A-Za-z0-9]{34,40}`), Keywords: []string{"hf_"}, }, { From 7fa5e7d0ab67f20d434b2922725988695e32e6af Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Thu, 25 Jul 2024 11:35:13 +0200 Subject: [PATCH 247/352] fix(cli): error on missing config file (#7154) --- pkg/commands/app.go | 10 ++++++---- pkg/commands/app_test.go | 9 +++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 5ca651193c35..5a9baab6ca0b 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -151,14 +151,16 @@ func loadPluginCommands() []*cobra.Command { return commands } -func initConfig(configFile string) error { +func initConfig(configFile string, pathChanged bool) error { // Read from config viper.SetConfigFile(configFile) viper.SetConfigType("yaml") if err := viper.ReadInConfig(); err != nil { if errors.Is(err, os.ErrNotExist) { - log.Debug("Config file not found", log.FilePath(configFile)) - return nil + if !pathChanged { + log.Debugf("Default config file %q not found, using built in values", log.FilePath(configFile)) + return nil + } } return xerrors.Errorf("config file %q loading error: %s", configFile, err) } @@ -201,7 +203,7 @@ func NewRootCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { // Configure environment variables and config file // It cannot be called in init() because it must be called after viper.BindPFlags. - if err := initConfig(configPath); err != nil { + if err := initConfig(configPath, cmd.Flags().Changed(flag.ConfigFileFlag.ConfigName)); err != nil { return err } diff --git a/pkg/commands/app_test.go b/pkg/commands/app_test.go index 7235a3e94c7d..308732ce918b 100644 --- a/pkg/commands/app_test.go +++ b/pkg/commands/app_test.go @@ -296,6 +296,15 @@ func TestFlags(t *testing.T) { }, wantErr: `invalid argument "foo" for "--format" flag`, }, + { + name: "missing config file", + arguments: []string{ + "test", + "--config", + "none", + }, + wantErr: `config file "none" loading error: open none:`, + }, } for _, tt := range tests { From c3036de6d7719323d306a9666ccc8d928d936f9a Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:01:57 +0500 Subject: [PATCH 248/352] fix(report): hide empty table when all secrets/license/misconfigs are ignored (#7171) Co-authored-by: knqyf263 --- pkg/report/table/licensing.go | 46 ++++++++++++++++++++++++++++++----- pkg/report/table/misconfig.go | 5 ++++ pkg/report/table/secret.go | 5 ++++ 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/pkg/report/table/licensing.go b/pkg/report/table/licensing.go index 602dc9033e00..5b41d7d25cc1 100644 --- a/pkg/report/table/licensing.go +++ b/pkg/report/table/licensing.go @@ -39,6 +39,12 @@ func NewPkgLicenseRenderer(result types.Result, isTerminal bool, severities []db } func (r pkgLicenseRenderer) Render() string { + // Trivy doesn't currently support showing suppressed licenses + // So just skip this result + if len(r.result.Licenses) == 0 { + return "" + } + r.setHeaders() r.setRows() @@ -54,7 +60,12 @@ func (r pkgLicenseRenderer) Render() string { } func (r pkgLicenseRenderer) setHeaders() { - header := []string{"Package", "License", "Classification", "Severity"} + header := []string{ + "Package", + "License", + "Classification", + "Severity", + } r.tableWriter.SetHeaders(header...) } @@ -63,11 +74,17 @@ func (r pkgLicenseRenderer) setRows() { var row []string if r.isTerminal { row = []string{ - l.PkgName, l.Name, colorizeLicenseCategory(l.Category), ColorizeSeverity(l.Severity, l.Severity), + l.PkgName, + l.Name, + colorizeLicenseCategory(l.Category), + ColorizeSeverity(l.Severity, l.Severity), } } else { row = []string{ - l.PkgName, l.Name, string(l.Category), l.Severity, + l.PkgName, + l.Name, + string(l.Category), + l.Severity, } } r.tableWriter.AddRow(row...) @@ -109,6 +126,12 @@ func NewFileLicenseRenderer(result types.Result, isTerminal bool, severities []d } func (r fileLicenseRenderer) Render() string { + // Trivy doesn't currently support showing suppressed licenses + // So just skip this result + if len(r.result.Licenses) == 0 { + return "" + } + r.setHeaders() r.setRows() @@ -124,7 +147,12 @@ func (r fileLicenseRenderer) Render() string { } func (r fileLicenseRenderer) setHeaders() { - header := []string{"Classification", "Severity", "License", "File Location"} + header := []string{ + "Classification", + "Severity", + "License", + "File Location", + } r.tableWriter.SetHeaders(header...) } @@ -148,11 +176,17 @@ func (r fileLicenseRenderer) setRows() { var row []string if r.isTerminal { row = []string{ - colorizeLicenseCategory(l.Category), ColorizeSeverity(l.Severity, l.Severity), l.Name, l.FilePath, + colorizeLicenseCategory(l.Category), + ColorizeSeverity(l.Severity, l.Severity), + l.Name, + l.FilePath, } } else { row = []string{ - string(l.Category), l.Severity, l.Name, l.FilePath, + string(l.Category), + l.Severity, + l.Name, + l.FilePath, } } r.tableWriter.AddRow(row...) diff --git a/pkg/report/table/misconfig.go b/pkg/report/table/misconfig.go index 5f706fbd5787..112d783d0875 100644 --- a/pkg/report/table/misconfig.go +++ b/pkg/report/table/misconfig.go @@ -50,6 +50,11 @@ func NewMisconfigRenderer(result types.Result, severities []dbTypes.Severity, tr } func (r *misconfigRenderer) Render() string { + // Trivy doesn't currently support showing suppressed misconfigs + // So just skip this result + if len(r.result.Misconfigurations) == 0 { + return "" + } target := fmt.Sprintf("%s (%s)", r.result.Target, r.result.Type) RenderTarget(r.w, target, r.ansi) diff --git a/pkg/report/table/secret.go b/pkg/report/table/secret.go index 4b4fe4b0d1bc..cff91a98d9ed 100644 --- a/pkg/report/table/secret.go +++ b/pkg/report/table/secret.go @@ -40,6 +40,11 @@ func NewSecretRenderer(target string, secrets []types.DetectedSecret, ansi bool, } func (r *secretRenderer) Render() string { + // Trivy doesn't currently support showing suppressed secrets + // So just skip this result + if len(r.secrets) == 0 { + return "" + } target := r.target + " (secrets)" RenderTarget(r.w, target, r.ansi) From bff317c77bf4a5f615a80d9875d129213bd52f6d Mon Sep 17 00:00:00 2001 From: afdesk Date: Thu, 25 Jul 2024 16:41:40 +0600 Subject: [PATCH 249/352] feat: share build-in rules (#7207) --- pkg/fanal/secret/builtin-rules.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/fanal/secret/builtin-rules.go b/pkg/fanal/secret/builtin-rules.go index cada98d6681a..ed23c4fa18b0 100644 --- a/pkg/fanal/secret/builtin-rules.go +++ b/pkg/fanal/secret/builtin-rules.go @@ -82,6 +82,11 @@ const ( aws = `aws_?` ) +// This function is exported for trivy-plugin-aqua purposes only +func GetBuiltinRules() []Rule { + return builtinRules +} + // This function is exported for trivy-plugin-aqua purposes only func GetSecretRulesMetadata() []iacRules.Check { return lo.Map(builtinRules, func(rule Rule, i int) iacRules.Check { From 174b1e3515a6394cf8d523216d6267c1aefb820a Mon Sep 17 00:00:00 2001 From: afdesk Date: Thu, 25 Jul 2024 18:03:04 +0600 Subject: [PATCH 250/352] fix(secret): skip regular strings contain secret patterns (#7182) --- pkg/fanal/secret/builtin-rules.go | 155 ++++++++++-------- pkg/fanal/secret/scanner.go | 5 + pkg/fanal/secret/scanner_test.go | 72 ++++++++ .../testdata/wrapped-secrets-sensitive.txt | 2 + pkg/fanal/secret/testdata/wrapped-secrets.txt | 11 ++ 5 files changed, 173 insertions(+), 72 deletions(-) create mode 100644 pkg/fanal/secret/testdata/wrapped-secrets-sensitive.txt create mode 100644 pkg/fanal/secret/testdata/wrapped-secrets.txt diff --git a/pkg/fanal/secret/builtin-rules.go b/pkg/fanal/secret/builtin-rules.go index ed23c4fa18b0..9cb0aa361025 100644 --- a/pkg/fanal/secret/builtin-rules.go +++ b/pkg/fanal/secret/builtin-rules.go @@ -74,10 +74,10 @@ var ( // Reusable regex patterns const ( - quote = `["']?` - connect = `\s*(:|=>|=)?\s*` - startSecret = `(^|\s+)` - endSecret = `[.,]?(\s+|$)` + quote = `["']?` + connect = `\s*(:|=>|=)?\s*` + endSecret = `[.,]?(\s+|$)` + startWord = "([^0-9a-zA-Z]|^)" aws = `aws_?` ) @@ -103,7 +103,7 @@ var builtinRules = []Rule{ Category: CategoryAWS, Severity: "CRITICAL", Title: "AWS Access Key ID", - Regex: MustCompile(fmt.Sprintf(`%s(?P(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16})%s%s`, quote, quote, endSecret)), + Regex: MustCompileWithoutWordPrefix(fmt.Sprintf(`(?P(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16})%s%s`, quote, endSecret)), SecretGroupName: "secret", Keywords: []string{"AKIA", "AGPA", "AIDA", "AROA", "AIPA", "ANPA", "ANVA", "ASIA"}, }, @@ -112,41 +112,45 @@ var builtinRules = []Rule{ Category: CategoryAWS, Severity: "CRITICAL", Title: "AWS Secret Access Key", - Regex: MustCompile(fmt.Sprintf(`(?i)%s%s%s(sec(ret)?)?_?(access)?_?key%s%s%s(?P[A-Za-z0-9\/\+=]{40})%s%s`, startSecret, quote, aws, quote, connect, quote, quote, endSecret)), + Regex: MustCompile(fmt.Sprintf(`(?i)%s%s(sec(ret)?)?_?(access)?_?key%s%s%s(?P[A-Za-z0-9\/\+=]{40})%s%s`, quote, aws, quote, connect, quote, quote, endSecret)), SecretGroupName: "secret", Keywords: []string{"key"}, }, { - ID: "github-pat", - Category: CategoryGitHub, - Title: "GitHub Personal Access Token", - Severity: "CRITICAL", - Regex: MustCompile(`ghp_[0-9a-zA-Z]{36}`), - Keywords: []string{"ghp_"}, + ID: "github-pat", + Category: CategoryGitHub, + Title: "GitHub Personal Access Token", + Severity: "CRITICAL", + Regex: MustCompileWithoutWordPrefix(`?Pghp_[0-9a-zA-Z]{36}`), + SecretGroupName: "secret", + Keywords: []string{"ghp_"}, }, { - ID: "github-oauth", - Category: CategoryGitHub, - Title: "GitHub OAuth Access Token", - Severity: "CRITICAL", - Regex: MustCompile(`gho_[0-9a-zA-Z]{36}`), - Keywords: []string{"gho_"}, + ID: "github-oauth", + Category: CategoryGitHub, + Title: "GitHub OAuth Access Token", + Severity: "CRITICAL", + Regex: MustCompileWithoutWordPrefix(`?Pgho_[0-9a-zA-Z]{36}`), + SecretGroupName: "secret", + Keywords: []string{"gho_"}, }, { - ID: "github-app-token", - Category: CategoryGitHub, - Title: "GitHub App Token", - Severity: "CRITICAL", - Regex: MustCompile(`(ghu|ghs)_[0-9a-zA-Z]{36}`), - Keywords: []string{"ghu_", "ghs_"}, + ID: "github-app-token", + Category: CategoryGitHub, + Title: "GitHub App Token", + Severity: "CRITICAL", + Regex: MustCompileWithoutWordPrefix(`?P(ghu|ghs)_[0-9a-zA-Z]{36}`), + SecretGroupName: "secret", + Keywords: []string{"ghu_", "ghs_"}, }, { - ID: "github-refresh-token", - Category: CategoryGitHub, - Title: "GitHub Refresh Token", - Severity: "CRITICAL", - Regex: MustCompile(`ghr_[0-9a-zA-Z]{76}`), - Keywords: []string{"ghr_"}, + ID: "github-refresh-token", + Category: CategoryGitHub, + Title: "GitHub Refresh Token", + Severity: "CRITICAL", + Regex: MustCompileWithoutWordPrefix(`?Pghr_[0-9a-zA-Z]{76}`), + SecretGroupName: "secret", + Keywords: []string{"ghr_"}, }, { ID: "github-fine-grained-pat", @@ -157,21 +161,23 @@ var builtinRules = []Rule{ Keywords: []string{"github_pat_"}, }, { - ID: "gitlab-pat", - Category: CategoryGitLab, - Title: "GitLab Personal Access Token", - Severity: "CRITICAL", - Regex: MustCompile(`glpat-[0-9a-zA-Z\-\_]{20}`), - Keywords: []string{"glpat-"}, + ID: "gitlab-pat", + Category: CategoryGitLab, + Title: "GitLab Personal Access Token", + Severity: "CRITICAL", + Regex: MustCompileWithoutWordPrefix(`?Pglpat-[0-9a-zA-Z\-\_]{20}`), + SecretGroupName: "secret", + Keywords: []string{"glpat-"}, }, { // cf. https://huggingface.co/docs/hub/en/security-tokens - ID: "hugging-face-access-token", - Category: CategoryHuggingFace, - Severity: "CRITICAL", - Title: "Hugging Face Access Token", - Regex: MustCompile(`hf_[A-Za-z0-9]{34,40}`), - Keywords: []string{"hf_"}, + ID: "hugging-face-access-token", + Category: CategoryHuggingFace, + Severity: "CRITICAL", + Title: "Hugging Face Access Token", + Regex: MustCompileWithoutWordPrefix(`?Phf_[A-Za-z0-9]{34,40}`), + SecretGroupName: "secret", + Keywords: []string{"hf_"}, }, { ID: "private-key", @@ -191,28 +197,31 @@ var builtinRules = []Rule{ Keywords: []string{"shpss_", "shpat_", "shpca_", "shppa_"}, }, { - ID: "slack-access-token", - Category: CategorySlack, - Title: "Slack token", - Severity: "HIGH", - Regex: MustCompile(`xox[baprs]-([0-9a-zA-Z]{10,48})`), - Keywords: []string{"xoxb-", "xoxa-", "xoxp-", "xoxr-", "xoxs-"}, + ID: "slack-access-token", + Category: CategorySlack, + Title: "Slack token", + Severity: "HIGH", + Regex: MustCompileWithoutWordPrefix(`?Pxox[baprs]-([0-9a-zA-Z]{10,48})`), + SecretGroupName: "secret", + Keywords: []string{"xoxb-", "xoxa-", "xoxp-", "xoxr-", "xoxs-"}, }, { - ID: "stripe-publishable-token", - Category: CategoryStripe, - Title: "Stripe Publishable Key", - Severity: "LOW", - Regex: MustCompile(`(?i)pk_(test|live)_[0-9a-z]{10,32}`), - Keywords: []string{"pk_test_", "pk_live_"}, + ID: "stripe-publishable-token", + Category: CategoryStripe, + Title: "Stripe Publishable Key", + Severity: "LOW", + Regex: MustCompileWithoutWordPrefix(`?P(?i)pk_(test|live)_[0-9a-z]{10,32}`), + SecretGroupName: "secret", + Keywords: []string{"pk_test_", "pk_live_"}, }, { - ID: "stripe-secret-token", - Category: CategoryStripe, - Title: "Stripe Secret Key", - Severity: "CRITICAL", - Regex: MustCompile(`(?i)sk_(test|live)_[0-9a-z]{10,32}`), - Keywords: []string{"sk_test_", "sk_live_"}, + ID: "stripe-secret-token", + Category: CategoryStripe, + Title: "Stripe Secret Key", + Severity: "CRITICAL", + Regex: MustCompileWithoutWordPrefix(`?P(?i)sk_(test|live)_[0-9a-z]{10,32}`), + SecretGroupName: "secret", + Keywords: []string{"sk_test_", "sk_live_"}, }, { ID: "pypi-upload-token", @@ -506,20 +515,22 @@ var builtinRules = []Rule{ Keywords: []string{"finicity"}, }, { - ID: "flutterwave-public-key", - Category: CategoryFlutterwave, - Title: "Flutterwave public/secret key", - Severity: "MEDIUM", - Regex: MustCompile(`FLW(PUB|SEC)K_TEST-(?i)[a-h0-9]{32}-X`), - Keywords: []string{"FLWSECK_TEST-", "FLWPUBK_TEST-"}, + ID: "flutterwave-public-key", + Category: CategoryFlutterwave, + Title: "Flutterwave public/secret key", + Severity: "MEDIUM", + Regex: MustCompileWithoutWordPrefix(`?PFLW(PUB|SEC)K_TEST-(?i)[a-h0-9]{32}-X`), + SecretGroupName: "secret", + Keywords: []string{"FLWSECK_TEST-", "FLWPUBK_TEST-"}, }, { - ID: "flutterwave-enc-key", - Category: CategoryFlutterwave, - Title: "Flutterwave encrypted key", - Severity: "MEDIUM", - Regex: MustCompile(`FLWSECK_TEST[a-h0-9]{12}`), - Keywords: []string{"FLWSECK_TEST"}, + ID: "flutterwave-enc-key", + Category: CategoryFlutterwave, + Title: "Flutterwave encrypted key", + Severity: "MEDIUM", + Regex: MustCompileWithoutWordPrefix(`?PFLWSECK_TEST[a-h0-9]{12}`), + SecretGroupName: "secret", + Keywords: []string{"FLWSECK_TEST"}, }, { ID: "frameio-api-token", diff --git a/pkg/fanal/secret/scanner.go b/pkg/fanal/secret/scanner.go index c006a38b63a1..b8591a69228c 100644 --- a/pkg/fanal/secret/scanner.go +++ b/pkg/fanal/secret/scanner.go @@ -3,6 +3,7 @@ package secret import ( "bytes" "errors" + "fmt" "os" "regexp" "slices" @@ -62,6 +63,10 @@ type Regexp struct { *regexp.Regexp } +func MustCompileWithoutWordPrefix(str string) *Regexp { + return MustCompile(fmt.Sprintf("%s(%s)", startWord, str)) +} + func MustCompile(str string) *Regexp { return &Regexp{regexp.MustCompile(str)} } diff --git a/pkg/fanal/secret/scanner_test.go b/pkg/fanal/secret/scanner_test.go index e5b8f68f943d..86d813f4785e 100644 --- a/pkg/fanal/secret/scanner_test.go +++ b/pkg/fanal/secret/scanner_test.go @@ -315,6 +315,59 @@ func TestSecretScanner(t *testing.T) { }, }, } + wantFindingMyAwsAccessKey := types.SecretFinding{ + RuleID: "aws-secret-access-key", + Category: secret.CategoryAWS, + Title: "AWS Secret Access Key", + Severity: "CRITICAL", + StartLine: 1, + EndLine: 1, + Match: `MyAWS_secret_KEY="****************************************"`, + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "MyAWS_secret_KEY=\"****************************************\"", + Highlighted: "MyAWS_secret_KEY=\"****************************************\"", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 2, + Content: "our*********************************************************************************************", + Highlighted: "our*********************************************************************************************", + }, + }, + }, + } + + wantFindingMyGitHubPAT := types.SecretFinding{ + RuleID: "github-fine-grained-pat", + Category: secret.CategoryGitHub, + Title: "GitHub Fine-grained personal access tokens", + Severity: "CRITICAL", + StartLine: 2, + EndLine: 2, + Match: "our*********************************************************************************************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "MyAWS_secret_KEY=\"****************************************\"", + Highlighted: "MyAWS_secret_KEY=\"****************************************\"", + }, + { + Number: 2, + Content: "our*********************************************************************************************", + Highlighted: "our*********************************************************************************************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + }, + }, + } wantFindingGHButDisableAWS := types.SecretFinding{ RuleID: "github-pat", Category: secret.CategoryGitHub, @@ -419,6 +472,7 @@ func TestSecretScanner(t *testing.T) { }, }, } + wantFinding10 := types.SecretFinding{ RuleID: "aws-secret-access-key", Category: secret.CategoryAWS, @@ -979,6 +1033,24 @@ func TestSecretScanner(t *testing.T) { inputFilePath: filepath.Join("testdata", "invalid-aws-secrets.txt"), want: types.Secret{}, }, + { + name: "secret inside another word", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: filepath.Join("testdata", "wrapped-secrets.txt"), + want: types.Secret{}, + }, + { + name: "sensitive secret inside another word", + configPath: filepath.Join("testdata", "skip-test.yaml"), + inputFilePath: filepath.Join("testdata", "wrapped-secrets-sensitive.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "wrapped-secrets-sensitive.txt"), + Findings: []types.SecretFinding{ + wantFindingMyAwsAccessKey, + wantFindingMyGitHubPAT, + }, + }, + }, { name: "asymmetric file", configPath: filepath.Join("testdata", "skip-test.yaml"), diff --git a/pkg/fanal/secret/testdata/wrapped-secrets-sensitive.txt b/pkg/fanal/secret/testdata/wrapped-secrets-sensitive.txt new file mode 100644 index 000000000000..cda3a74921d3 --- /dev/null +++ b/pkg/fanal/secret/testdata/wrapped-secrets-sensitive.txt @@ -0,0 +1,2 @@ +MyAWS_secret_KEY="12ASD34qwe56CXZ78tyH10Tna543VBokN85RHCas" +our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 \ No newline at end of file diff --git a/pkg/fanal/secret/testdata/wrapped-secrets.txt b/pkg/fanal/secret/testdata/wrapped-secrets.txt new file mode 100644 index 000000000000..7e6e0e2c5e74 --- /dev/null +++ b/pkg/fanal/secret/testdata/wrapped-secrets.txt @@ -0,0 +1,11 @@ +DISPID_ICANVASRENDERINGCONTEXT2D_CANVAS DISPID_CANVASRENDERCONTEXT2D +ABCDFLWSECK_TEST-CANVASRENDERCONTEXT2DCANVASRENDA +ABCFLWSECK_TEST123456789012 +Rought_ICANVASRENDERINGVIACONTEXT2D3D5D7D8D +Sogho_ICANVASRENDERINGVIACONTEXT2D3D5D7D8D +Soghu_ICANVASRENDERINGVIACONTEXT2D3D5D7D8D +Bighr_ICANVASRENDERINGVIACONTEXT2D3D5D7D8DICANVASRENDERINGVIACONTEXT2D3D5D7D8D9D22 +Surhf_ICANVASRENDERINGVIACONTEXT2D3D5D6D7D8D9 +abcdexoxb-1234567890 +APK_TEST_1234567890 +ask_live_superlive1 From 88ba46047c93e6046292523ae701de774dfdc4dc Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 25 Jul 2024 16:18:37 +0400 Subject: [PATCH 251/352] feat(vex): VEX Repository support (#7206) Signed-off-by: knqyf263 Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> --- .github/workflows/test.yaml | 2 +- docs/docs/configuration/cache.md | 3 +- docs/docs/configuration/filtering.md | 2 +- .../references/configuration/cli/trivy.md | 1 + .../configuration/cli/trivy_clean.md | 1 + .../configuration/cli/trivy_filesystem.md | 3 +- .../configuration/cli/trivy_image.md | 3 +- .../configuration/cli/trivy_kubernetes.md | 3 +- .../configuration/cli/trivy_repository.md | 3 +- .../configuration/cli/trivy_rootfs.md | 3 +- .../configuration/cli/trivy_sbom.md | 3 +- .../references/configuration/cli/trivy_vex.md | 28 + .../configuration/cli/trivy_vex_repo.md | 44 + .../cli/trivy_vex_repo_download.md | 35 + .../configuration/cli/trivy_vex_repo_init.md | 31 + .../configuration/cli/trivy_vex_repo_list.md | 31 + .../references/configuration/cli/trivy_vm.md | 3 +- .../docs/supply-chain/{vex.md => vex/file.md} | 8 +- docs/docs/supply-chain/vex/index.md | 33 + docs/docs/supply-chain/vex/repo.md | 210 +++++ go.mod | 2 + go.sum | 4 + integration/integration_test.go | 39 + integration/repo_test.go | 34 +- .../fixtures/vex/config/repository.yaml | 4 + .../testdata/fixtures/vex/file/openvex.json | 23 + .../vex/repositories/default/0.1/index.json | 9 + .../repositories/default/vex-repository.json | 15 + integration/testdata/gomod-vex.json.golden | 148 ++++ internal/testutil/fs.go | 56 ++ mkdocs.yml | 11 +- pkg/commands/app.go | 88 ++ pkg/commands/artifact/run.go | 5 + pkg/commands/clean/run.go | 18 +- pkg/commands/operation/operation.go | 37 +- pkg/dependency/id.go | 3 + pkg/downloader/download.go | 151 +++- pkg/downloader/downloader_test.go | 4 +- pkg/flag/clean_flags.go | 18 +- pkg/flag/options.go | 7 +- pkg/flag/vulnerability_flags.go | 38 +- pkg/log/logger.go | 7 +- pkg/oci/artifact.go | 2 +- pkg/plugin/index.go | 3 +- pkg/plugin/manager.go | 6 +- pkg/plugin/plugin.go | 4 +- pkg/result/filter.go | 47 +- pkg/result/filter_test.go | 15 +- pkg/sbom/io/decode.go | 6 +- pkg/sbom/io/encode.go | 4 + pkg/utils/fsutils/fs.go | 4 + pkg/vex/csaf.go | 6 +- pkg/vex/cyclonedx.go | 56 +- pkg/vex/document.go | 98 +++ pkg/vex/openvex.go | 10 +- pkg/vex/repo.go | 143 +++ pkg/vex/repo/manager.go | 189 ++++ pkg/vex/repo/manager_test.go | 335 +++++++ pkg/vex/repo/repo.go | 327 +++++++ pkg/vex/repo/repo_test.go | 366 ++++++++ .../repo/testdata/.trivy/vex/repository.yaml | 4 + pkg/vex/repo/testdata/test-repo/index.json | 9 + .../testdata/test-repo/vex-repository.json | 16 + pkg/vex/repo_test.go | 113 +++ pkg/vex/testdata/csaf-relationships.json | 40 +- pkg/vex/testdata/csaf.json | 18 +- pkg/vex/testdata/cyclonedx.json | 6 +- .../repositories/default/0.1/bash-vex.json | 22 + .../vex/repositories/default/0.1/index.json | 10 + .../high-priority/0.1/bash-vex.json | 21 + .../repositories/high-priority/0.1/index.json | 10 + pkg/vex/testdata/openvex-oci-mismatch.json | 26 + .../repositories/default/0.1/bash-vex.json | 22 + .../vex/repositories/default/0.1/index.json | 10 + .../repositories/default/vex-repository.json | 15 + pkg/vex/vex.go | 173 ++-- pkg/vex/vex_test.go | 833 ++++++++++-------- 77 files changed, 3497 insertions(+), 643 deletions(-) create mode 100644 docs/docs/references/configuration/cli/trivy_vex.md create mode 100644 docs/docs/references/configuration/cli/trivy_vex_repo.md create mode 100644 docs/docs/references/configuration/cli/trivy_vex_repo_download.md create mode 100644 docs/docs/references/configuration/cli/trivy_vex_repo_init.md create mode 100644 docs/docs/references/configuration/cli/trivy_vex_repo_list.md rename docs/docs/supply-chain/{vex.md => vex/file.md} (97%) create mode 100644 docs/docs/supply-chain/vex/index.md create mode 100644 docs/docs/supply-chain/vex/repo.md create mode 100644 integration/testdata/fixtures/vex/config/repository.yaml create mode 100644 integration/testdata/fixtures/vex/file/openvex.json create mode 100644 integration/testdata/fixtures/vex/repositories/default/0.1/index.json create mode 100644 integration/testdata/fixtures/vex/repositories/default/vex-repository.json create mode 100644 integration/testdata/gomod-vex.json.golden create mode 100644 pkg/vex/document.go create mode 100644 pkg/vex/repo.go create mode 100644 pkg/vex/repo/manager.go create mode 100644 pkg/vex/repo/manager_test.go create mode 100644 pkg/vex/repo/repo.go create mode 100644 pkg/vex/repo/repo_test.go create mode 100644 pkg/vex/repo/testdata/.trivy/vex/repository.yaml create mode 100644 pkg/vex/repo/testdata/test-repo/index.json create mode 100644 pkg/vex/repo/testdata/test-repo/vex-repository.json create mode 100644 pkg/vex/repo_test.go create mode 100644 pkg/vex/testdata/multi-repos/vex/repositories/default/0.1/bash-vex.json create mode 100644 pkg/vex/testdata/multi-repos/vex/repositories/default/0.1/index.json create mode 100644 pkg/vex/testdata/multi-repos/vex/repositories/high-priority/0.1/bash-vex.json create mode 100644 pkg/vex/testdata/multi-repos/vex/repositories/high-priority/0.1/index.json create mode 100644 pkg/vex/testdata/openvex-oci-mismatch.json create mode 100644 pkg/vex/testdata/single-repo/vex/repositories/default/0.1/bash-vex.json create mode 100644 pkg/vex/testdata/single-repo/vex/repositories/default/0.1/index.json create mode 100644 pkg/vex/testdata/single-repo/vex/repositories/default/vex-repository.json diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 70a21462b29a..13f279b519b7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -151,7 +151,7 @@ jobs: runs-on: ${{ matrix.operating-system }} strategy: matrix: - operating-system: [ubuntu-latest-m, windows-latest, macos-latest] + operating-system: [ubuntu-latest, windows-latest, macos-latest] env: DOCKER_CLI_EXPERIMENTAL: "enabled" steps: diff --git a/docs/docs/configuration/cache.md b/docs/docs/configuration/cache.md index 8817a2adb3ea..2ad5086d0e87 100644 --- a/docs/docs/configuration/cache.md +++ b/docs/docs/configuration/cache.md @@ -1,10 +1,11 @@ # Cache The cache directory includes +- Cache of previous scans (Scan cache). - [Vulnerability Database][trivy-db][^1] - [Java Index Database][trivy-java-db][^2] - [Misconfiguration Checks][misconf-checks][^3] -- Cache of previous scans. +- [VEX Repositories](../supply-chain/vex/repo.md) The cache option is common to all scanners. diff --git a/docs/docs/configuration/filtering.md b/docs/docs/configuration/filtering.md index e3d38f3cdc15..abe8e84ff7e3 100644 --- a/docs/docs/configuration/filtering.md +++ b/docs/docs/configuration/filtering.md @@ -493,7 +493,7 @@ You can find more example checks [here](https://github.com/aquasecurity/trivy/tr | Secret | | | License | | -Please refer to the [VEX documentation](../supply-chain/vex.md) for the details. +Please refer to the [VEX documentation](../supply-chain/vex/index.md) for the details. [^1]: license name is used as id for `.trivyignore.yaml` files. diff --git a/docs/docs/references/configuration/cli/trivy.md b/docs/docs/references/configuration/cli/trivy.md index 2992bc0faa9b..3d8ece9cd0e8 100644 --- a/docs/docs/references/configuration/cli/trivy.md +++ b/docs/docs/references/configuration/cli/trivy.md @@ -56,5 +56,6 @@ trivy [global flags] command [flags] target * [trivy sbom](trivy_sbom.md) - Scan SBOM for vulnerabilities and licenses * [trivy server](trivy_server.md) - Server mode * [trivy version](trivy_version.md) - Print the version +* [trivy vex](trivy_vex.md) - [EXPERIMENTAL] VEX utilities * [trivy vm](trivy_vm.md) - [EXPERIMENTAL] Scan a virtual machine image diff --git a/docs/docs/references/configuration/cli/trivy_clean.md b/docs/docs/references/configuration/cli/trivy_clean.md index 7a997bf7b581..65b827136f5b 100644 --- a/docs/docs/references/configuration/cli/trivy_clean.md +++ b/docs/docs/references/configuration/cli/trivy_clean.md @@ -28,6 +28,7 @@ trivy clean [flags] -h, --help help for clean --java-db remove Java database --scan-cache remove scan cache (container and VM image analysis results) + --vex-repo remove VEX repositories --vuln-db remove vulnerability database ``` diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 2fb1ad1c984c..86ac2c2f8918 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -82,6 +82,7 @@ trivy filesystem [flags] PATH --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database + --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --tf-vars strings specify paths to override the Terraform tfvars files @@ -89,7 +90,7 @@ trivy filesystem [flags] PATH --token-header string specify a header name for token in client/server mode (default "Trivy-Token") --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. - --vex string [EXPERIMENTAL] file path to VEX + --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 2ae526d9405c..62e731e14126 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -103,13 +103,14 @@ trivy image [flags] IMAGE_NAME --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database + --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --token string for authentication in client/server mode --token-header string specify a header name for token in client/server mode (default "Trivy-Token") --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. - --vex string [EXPERIMENTAL] file path to VEX + --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 3f20a33e866d..2f8539dfeb47 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -98,12 +98,13 @@ trivy kubernetes [flags] [CONTEXT] --skip-files strings specify the files or glob patterns to skip --skip-images skip the downloading and scanning of images (vulnerabilities and secrets) in the cluster resources --skip-java-db-update skip updating Java index database + --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --tolerations strings specify node-collector job tolerations (example: key1=value1:NoExecute,key2=value2:NoSchedule) --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. - --vex string [EXPERIMENTAL] file path to VEX + --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 4831c2bad4ae..661381b76ebf 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -81,6 +81,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database + --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update --tag string pass the tag name to be scanned -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules @@ -89,7 +90,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --token-header string specify a header name for token in client/server mode (default "Trivy-Token") --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. - --vex string [EXPERIMENTAL] file path to VEX + --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index ca433b327f0d..01ed2ce062c6 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -83,6 +83,7 @@ trivy rootfs [flags] ROOTDIR --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database + --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --tf-vars strings specify paths to override the Terraform tfvars files @@ -90,7 +91,7 @@ trivy rootfs [flags] ROOTDIR --token-header string specify a header name for token in client/server mode (default "Trivy-Token") --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. - --vex string [EXPERIMENTAL] file path to VEX + --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index 3d4d25e47fcb..0c7508c854e1 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -58,10 +58,11 @@ trivy sbom [flags] SBOM_PATH --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database + --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update -t, --template string output template --token string for authentication in client/server mode --token-header string specify a header name for token in client/server mode (default "Trivy-Token") - --vex string [EXPERIMENTAL] file path to VEX + --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_vex.md b/docs/docs/references/configuration/cli/trivy_vex.md new file mode 100644 index 000000000000..e7b4e31cb994 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_vex.md @@ -0,0 +1,28 @@ +## trivy vex + +[EXPERIMENTAL] VEX utilities + +### Options + +``` + -h, --help help for vex +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner +* [trivy vex repo](trivy_vex_repo.md) - Manage VEX repositories + diff --git a/docs/docs/references/configuration/cli/trivy_vex_repo.md b/docs/docs/references/configuration/cli/trivy_vex_repo.md new file mode 100644 index 000000000000..32777ba4bab8 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_vex_repo.md @@ -0,0 +1,44 @@ +## trivy vex repo + +Manage VEX repositories + +### Examples + +``` + # Initialize the configuration file + $ trivy vex repo init + + # List VEX repositories + $ trivy vex repo list + + # Download the VEX repositories + $ trivy vex repo download + +``` + +### Options + +``` + -h, --help help for repo +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy vex](trivy_vex.md) - [EXPERIMENTAL] VEX utilities +* [trivy vex repo download](trivy_vex_repo_download.md) - Download the VEX repositories +* [trivy vex repo init](trivy_vex_repo_init.md) - Initialize a configuration file +* [trivy vex repo list](trivy_vex_repo_list.md) - List VEX repositories + diff --git a/docs/docs/references/configuration/cli/trivy_vex_repo_download.md b/docs/docs/references/configuration/cli/trivy_vex_repo_download.md new file mode 100644 index 000000000000..eebf63f81187 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_vex_repo_download.md @@ -0,0 +1,35 @@ +## trivy vex repo download + +Download the VEX repositories + +### Synopsis + +Downloads enabled VEX repositories. If specific repository names are provided as arguments, only those repositories will be downloaded. Otherwise, all enabled repositories are downloaded. + +``` +trivy vex repo download [REPO_NAMES] [flags] +``` + +### Options + +``` + -h, --help help for download +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy vex repo](trivy_vex_repo.md) - Manage VEX repositories + diff --git a/docs/docs/references/configuration/cli/trivy_vex_repo_init.md b/docs/docs/references/configuration/cli/trivy_vex_repo_init.md new file mode 100644 index 000000000000..6e9a9b0f9523 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_vex_repo_init.md @@ -0,0 +1,31 @@ +## trivy vex repo init + +Initialize a configuration file + +``` +trivy vex repo init [flags] +``` + +### Options + +``` + -h, --help help for init +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy vex repo](trivy_vex_repo.md) - Manage VEX repositories + diff --git a/docs/docs/references/configuration/cli/trivy_vex_repo_list.md b/docs/docs/references/configuration/cli/trivy_vex_repo_list.md new file mode 100644 index 000000000000..5f1c77c23f93 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_vex_repo_list.md @@ -0,0 +1,31 @@ +## trivy vex repo list + +List VEX repositories + +``` +trivy vex repo list [flags] +``` + +### Options + +``` + -h, --help help for list +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy vex repo](trivy_vex_repo.md) - Manage VEX repositories + diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index dab35eeb93e1..c250b9bcf06b 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -72,11 +72,12 @@ trivy vm [flags] VM_IMAGE --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database + --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --token string for authentication in client/server mode --token-header string specify a header name for token in client/server mode (default "Trivy-Token") - --vex string [EXPERIMENTAL] file path to VEX + --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/supply-chain/vex.md b/docs/docs/supply-chain/vex/file.md similarity index 97% rename from docs/docs/supply-chain/vex.md rename to docs/docs/supply-chain/vex/file.md index 0ceaeeeb67b3..7c847ec49e14 100644 --- a/docs/docs/supply-chain/vex.md +++ b/docs/docs/supply-chain/vex/file.md @@ -1,11 +1,11 @@ -# Vulnerability Exploitability Exchange (VEX) +# Local VEX Files !!! warning "EXPERIMENTAL" This feature might change without preserving backwards compatibility. -Trivy supports filtering detected vulnerabilities using [the Vulnerability Exploitability Exchange (VEX)](https://www.ntia.gov/files/ntia/publications/vex_one-page_summary.pdf), a standardized format for sharing and exchanging information about vulnerabilities. -By providing VEX during scanning, it is possible to filter vulnerabilities based on their status. -Currently, Trivy supports the following three formats: +In addition to [VEX repositories](./repo.md), Trivy also supports the use of local VEX files for vulnerability filtering. +This method is useful when you have specific VEX documents that you want to apply to your scans. +Currently, Trivy supports the following formats: - [CycloneDX](https://cyclonedx.org/capabilities/vex/) - [OpenVEX](https://github.com/openvex/spec) diff --git a/docs/docs/supply-chain/vex/index.md b/docs/docs/supply-chain/vex/index.md new file mode 100644 index 000000000000..4f8d59abe126 --- /dev/null +++ b/docs/docs/supply-chain/vex/index.md @@ -0,0 +1,33 @@ +# Vulnerability Exploitability Exchange (VEX) + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy supports filtering detected vulnerabilities using the [Vulnerability Exploitability eXchange (VEX)](https://www.ntia.gov/files/ntia/publications/vex_one-page_summary.pdf), a standardized format for sharing and exchanging information about vulnerabilities. +By providing VEX during scanning, it is possible to filter vulnerabilities based on their status. + +## VEX Usage Methods + +Trivy currently supports two methods for utilizing VEX: + +1. [VEX Repository](./repo.md) +2. [Local VEX Files](./file.md) + +### Enabling VEX +To enable VEX, use the `--vex` option. +You can specify the method to use: + +- To enable the VEX Repository: `--vex repo` +- To use a local VEX file: `--vex /path/to/vex-document.json` + +```bash +$ trivy image ghcr.io/aquasecurity/trivy:0.52.0 --vex repo +``` + +You can enable both methods simultaneously. +The order of specification determines the priority: + +- `--vex repo --vex /path/to/vex-document.json`: VEX Repository has priority +- `--vex /path/to/vex-document.json --vex repo`: Local file has priority + +For detailed information on each method, please refer to each page. \ No newline at end of file diff --git a/docs/docs/supply-chain/vex/repo.md b/docs/docs/supply-chain/vex/repo.md new file mode 100644 index 000000000000..b6c641aee9da --- /dev/null +++ b/docs/docs/supply-chain/vex/repo.md @@ -0,0 +1,210 @@ +# VEX Repository + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +## Using VEX Repository + +Trivy can download and utilize VEX documents from repositories that comply with [the VEX Repository Specification][vex-repo]. +While it's planned to be enabled by default in the future, currently it can be activated by explicitly specifying `--vex repo`. + +``` +$ trivy image ghcr.io/aquasecurity/trivy:0.52.0 --vex repo +2024-07-20T11:22:58+04:00 INFO [vex] The default repository config has been created +file_path="/Users/teppei/.trivy/vex/repository.yaml" +2024-07-20T11:23:23+04:00 INFO [vex] Updating repository... repo="default" url="https://github.com/aquasecurity/vexhub" +``` + +During scanning, Trivy generates PURLs for discovered packages and searches for matching PURLs in the VEX Repository. +If a match is found, the corresponding VEX is utilized. + +### Configuration File + +#### Default Configuration + +When `--vex repo` is specified for the first time, a default configuration file is created at `$HOME/.trivy/vex/repository.yaml`. +The home directory can be configured through environment variable `$XDG_DATA_HOME`. + +You can also create the configuration file in advance using the `trivy vex repo init` command and edit it. + +The default configuration file looks like this: + +```yaml +repositories: + - name: default + url: https://github.com/aquasecurity/vexhub + enabled: true + username: "" + password: "" + token: "" +``` + +By default, [VEX Hub][vexhub] managed by Aqua Security is used. +VEX Hub primarily trusts VEX documents published by the package maintainers. + +#### Show Configuration +You can see the config file path and the configured repositories with `trivy vex repo list`: + +```bash +$ trivy vex repo list +VEX Repositories (config: /home/username/.trivy/vex/repository.yaml) + +- Name: default + URL: https://github.com/aquasecurity/vexhub + Status: Enabled +``` + +#### Custom Repositories + +If you want to trust VEX documents published by other organizations or use your own VEX repository, you can specify a custom repository that complies with [the VEX Repository Specification][vex-repo]. +You can add a custom repository as below: + +```yaml +- name: custom + url: https://example.com/custom-repo + enabled: true +``` + + +#### Authentication + +For private repositories: + +- `username`/`password` can be used for Basic authentication +- `token` can be used for Bearer authentication + +```yaml +- name: custom + url: https://example.com/custom-repo + enabled: true + token: "my-token" +``` + +#### Repository Priority + +The priority of VEX repositories is determined by their order in the configuration file. +You can add repositories with higher priority than the default or even remove the default VEX Hub. + +```yaml +- name: repo1 + url: https://example.com/repo1 +- name: repo2 + url: https://example.com/repo2 +``` + +In this configuration, when Trivy detects a vulnerability in a package, it generates a PURL for that package and searches for matching VEX documents in the configured repositories. +The search process follows this order: + +1. Trivy first looks for a VEX document matching the package's PURL in `repo1`. +2. If no matching VEX document is found in `repo1`, Trivy then searches in `repo2`. +3. This process continues through all configured repositories until a match is found. + +If a matching VEX document is found in any repository (e.g., `repo1`), the search stops, and Trivy uses that VEX document. +Subsequent repositories (e.g., `repo2`) are not checked for that specific vulnerability and package combination. + +It's important to note that the first matching VEX document found determines the final status of the vulnerability. +For example, if `repo1` states that a package is "Affected" by a vulnerability, this status will be used even if `repo2` states that the same package is "Not Affected" for the same vulnerability. +The "Affected" status from the higher-priority repository (`repo1`) takes precedence, and Trivy will consider the package as affected by the vulnerability. + +### Repository Updates + +VEX repositories are automatically updated during scanning. +Updates are performed based on the update frequency specified by the repository. + +To disable auto-update, pass `--skip-vex-repo-update`. + +```shell +$ trivy image ghcr.io/aquasecurity/trivy:0.50.0 --vex repo --skip-vex-repo-update +``` + +To download VEX repositories in advance without scanning, use `trivy vex repo download`. + +The cache can be cleared with `trivy clean --vex-repo`. + +### Displaying Filtered Vulnerabilities + +To see which vulnerabilities were filtered and why, use the `--show-suppressed` option: + +```shell +$ trivy image ghcr.io/aquasecurity/trivy:0.50.0 --vex repo --show-suppressed +... + +Suppressed Vulnerabilities (Total: 4) +===================================== +┌───────────────┬────────────────┬──────────┬──────────────┬───────────────────────────────────────────────────┬──────────────────────────────────────────┐ +│ Library │ Vulnerability │ Severity │ Status │ Statement │ Source │ +├───────────────┼────────────────┼──────────┼──────────────┼───────────────────────────────────────────────────┼──────────────────────────────────────────┤ +│ busybox │ CVE-2023-42364 │ MEDIUM │ not_affected │ vulnerable_code_cannot_be_controlled_by_adversary │ VEX Repository: default │ +│ │ │ │ │ │ (https://github.com/aquasecurity/vexhub) │ +│ ├────────────────┤ │ │ │ │ +│ │ CVE-2023-42365 │ │ │ │ │ +│ │ │ │ │ │ │ +├───────────────┼────────────────┤ │ │ │ │ +│ busybox-binsh │ CVE-2023-42364 │ │ │ │ │ +│ │ │ │ │ │ │ +│ ├────────────────┤ │ │ │ │ +│ │ CVE-2023-42365 │ │ │ │ │ +│ │ │ │ │ │ │ +└───────────────┴────────────────┴──────────┴──────────────┴───────────────────────────────────────────────────┴──────────────────────────────────────────┘ + +``` + +## Publishing VEX Documents + +### For OSS Projects + +As an OSS developer or maintainer, you may encounter vulnerabilities in the packages your project depends on. +These vulnerabilities might be discovered through your own scans or reported by third parties using your OSS project. + +While Trivy strives to minimize false positives, it doesn't perform code graph analysis, which means it can't evaluate exploitability at the code level. +Consequently, Trivy may report vulnerabilities even in cases where: + +1. The vulnerable function in a dependency is never called in your project. +2. The vulnerable code cannot be controlled by an attacker in the context of your project. + +If you're confident that a reported vulnerability in a dependency doesn't affect your OSS project or container image, you can publish a VEX document to reduce noise in Trivy scans. +To assess exploitability, you have several options: + +1. Manual assessment: As a maintainer, you can read the source code and determine if the vulnerability is exploitable in your project's context. +2. Automated assessment: You can use SAST (Static Application Security Testing) tools or similar tools to analyze the code and determine exploitability. + +By publishing VEX documents in the source repository, Trivy can automatically utilize them through VEX Hub. +The main steps are: + +1. Generate a VEX document +2. Commit the VEX document to the `.vex/` directory in the source repository (e.g., [Trivy's VEX][trivy-vex]) +3. Register your project's [PURL][purl] in VEX Hub + +Step 3 is only necessary once. +After that, updating the VEX file in your repository will automatically be fetched by VEX Hub and utilized by Trivy. +See the [VEX Hub repository][vexhub] for more information. + +If you want to issue a VEX for an OSS project that you don't maintain, consider first proposing the VEX publication to the original repository. +Many OSS maintainers are open to contributions that improve the security posture of their projects. +However, if your proposal is not accepted, or if you want to issue a VEX with statements that differ from the maintainer's judgment, you may want to consider creating a [custom repository](#hosting-custom-repositories). + +### For Private Projects + +If you're working on private software or personal projects, you have several options: + +1. [Local VEX files](./file.md): You can create local VEX files and have Trivy read them during scans. This is suitable for individual use or small teams. +2. [.trivyignore](../../configuration/filtering.md#trivyignore): For simpler cases, using a .trivyignore file might be sufficient to suppress specific vulnerabilities. +3. [Custom repositories](#hosting-custom-repositories): For large organizations wanting to share VEX information for internally used software across different departments, setting up a custom VEX repository might be the best approach. + +## Hosting Custom Repositories + +While the principle is to store VEX documents for OSS packages in the source repository, it's possible to create a custom repository if that's difficult. + +There are various use cases for providing custom repositories: + +- A Pull Request to add a VEX document upstream was not merged +- Consolidating VEX documents output by SAST tools +- Publishing vendor-specific VEX documents that differ from OSS maintainer statements +- Creating a private VEX repository to publish common VEX for your company + +In these cases, you can create a repository that complies with [the VEX Repository Specification][vex-repo] to make it available for use with Trivy. + +[vex-repo]: https://github.com/aquasecurity/vex-repo-spec +[vexhub]: https://github.com/aquasecurity/vexhub +[trivy-vex]: https://github.com/aquasecurity/trivy/blob/b76a7250912cfc028cfef743f0f98cd81b39f8aa/.vex/trivy.openvex.json +[purl]: https://github.com/package-url/purl-spec \ No newline at end of file diff --git a/go.mod b/go.mod index 798ea8fa96d6..de5fed7f1b4e 100644 --- a/go.mod +++ b/go.mod @@ -52,6 +52,7 @@ require ( github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/go-containerregistry v0.20.0 + github.com/google/go-github/v62 v62.0.0 github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/uuid v1.6.0 github.com/google/wire v0.6.0 @@ -244,6 +245,7 @@ require ( github.com/google/btree v1.1.2 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect diff --git a/go.sum b/go.sum index 9f2aaaed05ed..b8d36339bfe0 100644 --- a/go.sum +++ b/go.sum @@ -1357,6 +1357,10 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/go-containerregistry v0.20.0 h1:wRqHpOeVh3DnenOrPy9xDOLdnLatiGuuNRVelR2gSbg= github.com/google/go-containerregistry v0.20.0/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= +github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= diff --git a/integration/integration_test.go b/integration/integration_test.go index c7c923af0c33..c263ec2fc51e 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -28,11 +28,13 @@ import ( "github.com/aquasecurity/trivy-db/pkg/metadata" "github.com/aquasecurity/trivy/internal/dbtest" + "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/commands" "github.com/aquasecurity/trivy/pkg/db" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/uuid" + "github.com/aquasecurity/trivy/pkg/vex/repo" _ "modernc.org/sqlite" ) @@ -68,6 +70,43 @@ func initDB(t *testing.T) string { return cacheDir } +func initVEXRepository(t *testing.T, homeDir, cacheDir string) { + t.Helper() + + // Copy config directory + configSrc := "testdata/fixtures/vex/config/repository.yaml" + configDst := filepath.Join(homeDir, ".trivy", "vex", "repository.yaml") + testutil.CopyFile(t, configSrc, configDst) + + // Copy repository directory + repoSrc := "testdata/fixtures/vex/repositories" + repoDst := filepath.Join(cacheDir, "vex", "repositories") + testutil.CopyDir(t, repoSrc, repoDst) + + // Copy VEX file + vexSrc := "testdata/fixtures/vex/file/openvex.json" + repoDir := filepath.Join(repoDst, "default") + vexDst := filepath.Join(repoDir, "0.1", "openvex.json") + testutil.CopyFile(t, vexSrc, vexDst) + + // Write a dummy cache metadata + testutil.MustWriteJSON(t, filepath.Join(repoDir, "cache.json"), repo.CacheMetadata{ + UpdatedAt: time.Now(), + }) + + // Verify that necessary files exist + requiredFiles := []string{ + configDst, + filepath.Join(repoDir, "vex-repository.json"), + filepath.Join(repoDir, "0.1", "index.json"), + filepath.Join(repoDir, "0.1", "openvex.json"), + } + + for _, file := range requiredFiles { + require.FileExists(t, file) + } +} + func getFreePort() (int, error) { addr, err := net.ResolveTCPAddr("tcp", "localhost:0") if err != nil { diff --git a/integration/repo_test.go b/integration/repo_test.go index b95b4f4f461d..69833bcf2b77 100644 --- a/integration/repo_test.go +++ b/integration/repo_test.go @@ -8,9 +8,10 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/require" ) // TestRepository tests `trivy repo` with the local code repositories @@ -32,6 +33,7 @@ func TestRepository(t *testing.T) { format types.Format includeDevDeps bool parallel int + vex string } tests := []struct { name string @@ -74,6 +76,24 @@ func TestRepository(t *testing.T) { }, golden: "testdata/gomod.json.golden", }, + { + name: "gomod with local VEX file", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/gomod", + vex: "testdata/fixtures/vex/file/openvex.json", + }, + golden: "testdata/gomod-vex.json.golden", + }, + { + name: "gomod with VEX repository", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/gomod", + vex: "repo", + }, + golden: "testdata/gomod-vex.json.golden", + }, { name: "npm", args: args{ @@ -437,9 +457,15 @@ func TestRepository(t *testing.T) { // Set up testing DB cacheDir := initDB(t) - // Set a temp dir so that modules will not be loaded + // Set up VEX + initVEXRepository(t, cacheDir, cacheDir) + + // Set a temp dir so that the VEX config will be loaded and modules will not be loaded t.Setenv("XDG_DATA_HOME", cacheDir) + // Disable Go license detection + t.Setenv("GOPATH", cacheDir) + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { command := "repo" @@ -532,6 +558,10 @@ func TestRepository(t *testing.T) { osArgs = append(osArgs, "--secret-config", tt.args.secretConfig) } + if tt.args.vex != "" { + osArgs = append(osArgs, "--vex", tt.args.vex) + } + runTest(t, osArgs, tt.golden, "", format, runOptions{ fakeUUID: "3ff14136-e09f-4df9-80ea-%012d", override: tt.override, diff --git a/integration/testdata/fixtures/vex/config/repository.yaml b/integration/testdata/fixtures/vex/config/repository.yaml new file mode 100644 index 000000000000..cd1865d9d4a5 --- /dev/null +++ b/integration/testdata/fixtures/vex/config/repository.yaml @@ -0,0 +1,4 @@ +repositories: + - name: default + url: https://localhost + enabled: true \ No newline at end of file diff --git a/integration/testdata/fixtures/vex/file/openvex.json b/integration/testdata/fixtures/vex/file/openvex.json new file mode 100644 index 000000000000..38773ba57694 --- /dev/null +++ b/integration/testdata/fixtures/vex/file/openvex.json @@ -0,0 +1,23 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "aquasecurity/trivy:613fd55abbc2857b5ca28b07a26f3cd4c8b0ddc4c8a97c57497a2d4c4880d7fc", + "author": "Aqua Security", + "timestamp": "2024-07-09T11:38:00.115697+04:00", + "version": 1, + "statements": [ + { + "vulnerability": { "@id": "CVE-2022-23628" }, + "products": [ + { + "@id": "pkg:golang/github.com/testdata/testdata", + "subcomponents": [ + { "@id": "pkg:golang/github.com/open-policy-agent/opa@0.35.0" } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "The vulnerable code isn't called" + } + ] +} diff --git a/integration/testdata/fixtures/vex/repositories/default/0.1/index.json b/integration/testdata/fixtures/vex/repositories/default/0.1/index.json new file mode 100644 index 000000000000..12d7ee936e8f --- /dev/null +++ b/integration/testdata/fixtures/vex/repositories/default/0.1/index.json @@ -0,0 +1,9 @@ +{ + "version": 1, + "packages": [ + { + "ID": "pkg:golang/github.com/testdata/testdata", + "Location": "./openvex.json" + } + ] +} diff --git a/integration/testdata/fixtures/vex/repositories/default/vex-repository.json b/integration/testdata/fixtures/vex/repositories/default/vex-repository.json new file mode 100644 index 000000000000..e064c0e1b3cf --- /dev/null +++ b/integration/testdata/fixtures/vex/repositories/default/vex-repository.json @@ -0,0 +1,15 @@ +{ + "name": "Test VEX Repository", + "description": "VEX Repository for Testing", + "versions": [ + { + "spec_version": "0.1", + "locations": [ + { + "url": "never used" + } + ], + "update_interval": "24h" + } + ] +} \ No newline at end of file diff --git a/integration/testdata/gomod-vex.json.golden b/integration/testdata/gomod-vex.json.golden new file mode 100644 index 000000000000..a2269bd1d0b7 --- /dev/null +++ b/integration/testdata/gomod-vex.json.golden @@ -0,0 +1,148 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/gomod", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "go.mod", + "Class": "lang-pkgs", + "Type": "gomod", + "Vulnerabilities": [ + { + "VulnerabilityID": "GMS-2022-20", + "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", + "PkgName": "github.com/docker/distribution", + "PkgIdentifier": { + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible", + "UID": "de19cd663ca047a8" + }, + "InstalledVersion": "2.7.1+incompatible", + "FixedVersion": "v2.8.0", + "Status": "fixed", + "Layer": {}, + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Title": "OCI Manifest Type Confusion Issue", + "Description": "### Impact\n\nSystems that rely on digest equivalence for image attestations may be vulnerable to type confusion.", + "Severity": "UNKNOWN", + "References": [ + "https://github.com/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/distribution/distribution/commit/b59a6f827947f9e0e67df0cfb571046de4733586", + "https://github.com/distribution/distribution/security/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/opencontainers/image-spec/pull/411" + ] + }, + { + "VulnerabilityID": "CVE-2021-38561", + "PkgID": "golang.org/x/text@v0.3.6", + "PkgName": "golang.org/x/text", + "PkgIdentifier": { + "PURL": "pkg:golang/golang.org/x/text@0.3.6", + "UID": "825dc613c0f39d45" + }, + "InstalledVersion": "0.3.6", + "FixedVersion": "0.3.7", + "Status": "fixed", + "Layer": {}, + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-38561", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Description": "Due to improper index calculation, an incorrectly formatted language tag can cause Parse\nto panic via an out of bounds read. If Parse is used to process untrusted user inputs,\nthis may be used as a vector for a denial of service attack.\n", + "Severity": "UNKNOWN", + "References": [ + "https://go-review.googlesource.com/c/text/+/340830", + "https://go.googlesource.com/text/+/383b2e75a7a4198c42f8f87833eefb772868a56f", + "https://pkg.go.dev/vuln/GO-2021-0113" + ] + } + ] + }, + { + "Target": "submod/go.mod", + "Class": "lang-pkgs", + "Type": "gomod", + "Vulnerabilities": [ + { + "VulnerabilityID": "GMS-2022-20", + "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", + "PkgName": "github.com/docker/distribution", + "PkgIdentifier": { + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible", + "UID": "94376dc37054a7e8" + }, + "InstalledVersion": "2.7.1+incompatible", + "FixedVersion": "v2.8.0", + "Status": "fixed", + "Layer": {}, + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Title": "OCI Manifest Type Confusion Issue", + "Description": "### Impact\n\nSystems that rely on digest equivalence for image attestations may be vulnerable to type confusion.", + "Severity": "UNKNOWN", + "References": [ + "https://github.com/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/distribution/distribution/commit/b59a6f827947f9e0e67df0cfb571046de4733586", + "https://github.com/distribution/distribution/security/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/opencontainers/image-spec/pull/411" + ] + } + ] + }, + { + "Target": "submod2/go.mod", + "Class": "lang-pkgs", + "Type": "gomod", + "Vulnerabilities": [ + { + "VulnerabilityID": "GMS-2022-20", + "PkgID": "github.com/docker/distribution@v2.7.1+incompatible", + "PkgName": "github.com/docker/distribution", + "PkgIdentifier": { + "PURL": "pkg:golang/github.com/docker/distribution@2.7.1%2Bincompatible", + "UID": "94306cdcf85fb50a" + }, + "InstalledVersion": "2.7.1+incompatible", + "FixedVersion": "v2.8.0", + "Status": "fixed", + "Layer": {}, + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Go", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Ago" + }, + "Title": "OCI Manifest Type Confusion Issue", + "Description": "### Impact\n\nSystems that rely on digest equivalence for image attestations may be vulnerable to type confusion.", + "Severity": "UNKNOWN", + "References": [ + "https://github.com/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/distribution/distribution/commit/b59a6f827947f9e0e67df0cfb571046de4733586", + "https://github.com/distribution/distribution/security/advisories/GHSA-qq97-vm5h-rrhg", + "https://github.com/opencontainers/image-spec/pull/411" + ] + } + ] + } + ] +} diff --git a/internal/testutil/fs.go b/internal/testutil/fs.go index 4a1162aa1bab..842cf7042c55 100644 --- a/internal/testutil/fs.go +++ b/internal/testutil/fs.go @@ -1,15 +1,24 @@ package testutil import ( + "encoding/json" "os" "path/filepath" "testing" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) +func CopyFile(t *testing.T, src, dst string) { + MustMkdirAll(t, filepath.Dir(dst)) + + _, err := fsutils.CopyFile(src, dst) + require.NoError(t, err) +} + // CopyDir copies the directory content from src to dst. // It supports only simple cases for testing. func CopyDir(t *testing.T, src, dst string) { @@ -34,3 +43,50 @@ func CopyDir(t *testing.T, src, dst string) { } } } + +func MustWriteYAML(t *testing.T, path string, data any) { + t.Helper() + MustMkdirAll(t, filepath.Dir(path)) + + f, err := os.Create(path) + require.NoError(t, err) + defer f.Close() + + require.NoError(t, yaml.NewEncoder(f).Encode(data)) +} + +func MustReadYAML(t *testing.T, path string, out any) { + t.Helper() + f, err := os.Open(path) + require.NoError(t, err) + defer f.Close() + + require.NoError(t, yaml.NewDecoder(f).Decode(out)) +} + +func MustMkdirAll(t *testing.T, dir string) { + err := os.MkdirAll(dir, 0750) + require.NoError(t, err) +} + +func MustReadJSON(t *testing.T, filePath string, v any) { + b, err := os.ReadFile(filePath) + require.NoError(t, err) + err = json.Unmarshal(b, v) + require.NoError(t, err) +} + +func MustWriteJSON(t *testing.T, filePath string, v any) { + data, err := json.Marshal(v) + require.NoError(t, err) + + MustWriteFile(t, filePath, data) +} + +func MustWriteFile(t *testing.T, filePath string, content []byte) { + dir := filepath.Dir(filePath) + MustMkdirAll(t, dir) + + err := os.WriteFile(filePath, content, 0600) + require.NoError(t, err) +} diff --git a/mkdocs.yml b/mkdocs.yml index deddf4a896e4..984332633203 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -124,10 +124,13 @@ nav: - Supply Chain: - SBOM: docs/supply-chain/sbom.md - Attestation: - - SBOM: docs/supply-chain/attestation/sbom.md - - Cosign Vulnerability Scan Record: docs/supply-chain/attestation/vuln.md - - SBOM Attestation in Rekor: docs/supply-chain/attestation/rekor.md - - VEX: docs/supply-chain/vex.md + - SBOM: docs/supply-chain/attestation/sbom.md + - Cosign Vulnerability Scan Record: docs/supply-chain/attestation/vuln.md + - SBOM Attestation in Rekor: docs/supply-chain/attestation/rekor.md + - VEX: + - Overview: docs/supply-chain/vex/index.md + - VEX Repository: docs/supply-chain/vex/repo.md + - Local VEX Files: docs/supply-chain/vex/file.md - Compliance: - Built-in Compliance: docs/compliance/compliance.md - Custom Compliance: docs/compliance/contrib-compliance.md diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 5a9baab6ca0b..4977db6e7ba4 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -27,6 +27,7 @@ import ( "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/version" "github.com/aquasecurity/trivy/pkg/version/app" + vexrepo "github.com/aquasecurity/trivy/pkg/vex/repo" xstrings "github.com/aquasecurity/trivy/pkg/x/strings" ) @@ -98,6 +99,7 @@ func NewApp() *cobra.Command { NewVersionCommand(globalFlags), NewVMCommand(globalFlags), NewCleanCommand(globalFlags), + NewVEXCommand(globalFlags), ) if plugins := loadPluginCommands(); len(plugins) > 0 { @@ -1228,6 +1230,92 @@ func NewCleanCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return cmd } +func NewVEXCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + vexFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + } + var vexOptions flag.Options + cmd := &cobra.Command{ + Use: "vex subcommand", + GroupID: groupManagement, + Short: "[EXPERIMENTAL] VEX utilities", + SilenceErrors: true, + SilenceUsage: true, + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + cmd.SetContext(log.WithContextPrefix(cmd.Context(), "vex")) + + vexOptions, err = vexFlags.ToOptions(args) + if err != nil { + return err + } + return nil + }, + } + + repoCmd := &cobra.Command{ + Use: "repo subcommand", + Short: "Manage VEX repositories", + SilenceErrors: true, + SilenceUsage: true, + Example: ` # Initialize the configuration file + $ trivy vex repo init + + # List VEX repositories + $ trivy vex repo list + + # Download the VEX repositories + $ trivy vex repo download +`, + } + + repoCmd.AddCommand( + &cobra.Command{ + Use: "init", + Short: "Initialize a configuration file", + SilenceErrors: true, + SilenceUsage: true, + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + if err := vexrepo.NewManager(vexOptions.CacheDir).Init(cmd.Context()); err != nil { + return xerrors.Errorf("config init error: %w", err) + } + return nil + }, + }, + &cobra.Command{ + Use: "list", + Short: "List VEX repositories", + SilenceErrors: true, + SilenceUsage: true, + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + if err := vexrepo.NewManager(vexOptions.CacheDir).List(cmd.Context()); err != nil { + return xerrors.Errorf("list error: %w", err) + } + return nil + }, + }, + &cobra.Command{ + Use: "download [REPO_NAMES]", + Short: "Download the VEX repositories", + Long: `Downloads enabled VEX repositories. If specific repository names are provided as arguments, only those repositories will be downloaded. Otherwise, all enabled repositories are downloaded.`, + SilenceErrors: true, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + err := vexrepo.NewManager(vexOptions.CacheDir).DownloadRepositories(cmd.Context(), args, + vexrepo.Options{Insecure: vexOptions.Insecure}) + if err != nil { + return xerrors.Errorf("repository download error: %w", err) + } + return nil + }, + }, + ) + + cmd.AddCommand(repoCmd) + return cmd +} + func NewVersionCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { var versionFormat string cmd := &cobra.Command{ diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 1343f4be61f0..043109cc0fd5 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -119,6 +119,11 @@ func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...RunnerOptio return nil, xerrors.Errorf("DB error: %w", err) } + // Update the VEX repositories if needed + if err := operation.DownloadVEXRepositories(ctx, cliOptions); err != nil { + return nil, xerrors.Errorf("VEX repositories download error: %w", err) + } + // Initialize WASM modules m, err := module.NewManager(ctx, module.Options{ Dir: cliOptions.ModuleDir, diff --git a/pkg/commands/clean/run.go b/pkg/commands/clean/run.go index 9d00d431b962..c60227360d19 100644 --- a/pkg/commands/clean/run.go +++ b/pkg/commands/clean/run.go @@ -12,13 +12,15 @@ import ( "github.com/aquasecurity/trivy/pkg/javadb" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/policy" + "github.com/aquasecurity/trivy/pkg/vex/repo" ) func Run(ctx context.Context, opts flag.Options) error { ctx, cancel := context.WithTimeout(ctx, opts.Timeout) defer cancel() - if !opts.CleanAll && !opts.CleanScanCache && !opts.CleanVulnerabilityDB && !opts.CleanJavaDB && !opts.CleanChecksBundle { + if !opts.CleanAll && !opts.CleanScanCache && !opts.CleanVulnerabilityDB && !opts.CleanJavaDB && + !opts.CleanChecksBundle && !opts.CleanVEXRepositories { return xerrors.New("no clean option is specified") } @@ -49,6 +51,12 @@ func Run(ctx context.Context, opts flag.Options) error { return xerrors.Errorf("check bundle clean error: %w", err) } } + + if opts.CleanVEXRepositories { + if err := cleanVEXRepositories(opts); err != nil { + return xerrors.Errorf("VEX repositories clean error: %w", err) + } + } return nil } @@ -102,3 +110,11 @@ func cleanCheckBundle(opts flag.Options) error { } return nil } + +func cleanVEXRepositories(opts flag.Options) error { + log.Info("Removing VEX repositories...") + if err := repo.NewManager(opts.CacheDir).Clear(); err != nil { + return xerrors.Errorf("clear VEX repositories: %w", err) + } + return nil +} diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index 92e45e5e696e..16aa72085949 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/google/go-containerregistry/pkg/name" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/db" @@ -13,6 +14,8 @@ import ( "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/policy" "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/vex" + "github.com/aquasecurity/trivy/pkg/vex/repo" ) var mu sync.Mutex @@ -23,6 +26,7 @@ func DownloadDB(ctx context.Context, appVersion, cacheDir string, dbRepository n mu.Lock() defer mu.Unlock() + ctx = log.WithContextPrefix(ctx, "db") dbDir := db.Dir(cacheDir) client := db.NewClient(dbDir, quiet, db.WithDBRepository(dbRepository)) needsUpdate, err := client.NeedsUpdate(ctx, appVersion, skipUpdate) @@ -31,8 +35,8 @@ func DownloadDB(ctx context.Context, appVersion, cacheDir string, dbRepository n } if needsUpdate { - log.Info("Need to update DB") - log.Info("Downloading DB...", log.String("repository", dbRepository.String())) + log.InfoContext(ctx, "Need to update DB") + log.InfoContext(ctx, "Downloading DB...", log.String("repository", dbRepository.String())) if err = client.Download(ctx, dbDir, opt); err != nil { return xerrors.Errorf("failed to download vulnerability DB: %w", err) } @@ -45,6 +49,35 @@ func DownloadDB(ctx context.Context, appVersion, cacheDir string, dbRepository n return nil } +func DownloadVEXRepositories(ctx context.Context, opts flag.Options) error { + ctx = log.WithContextPrefix(ctx, "vex") + if opts.SkipVEXRepoUpdate { + log.InfoContext(ctx, "Skipping VEX repository update") + return nil + } + + mu.Lock() + defer mu.Unlock() + + // Download VEX repositories only if `--vex repo` is passed. + _, enabled := lo.Find(opts.VEXSources, func(src vex.Source) bool { + return src.Type == vex.TypeRepository + }) + if !enabled { + return nil + } + + err := repo.NewManager(opts.CacheDir).DownloadRepositories(ctx, nil, repo.Options{ + Insecure: opts.Insecure, + }) + if err != nil { + return xerrors.Errorf("failed to download vex repositories: %w", err) + } + + return nil + +} + // InitBuiltinPolicies downloads the built-in policies and loads them func InitBuiltinPolicies(ctx context.Context, cacheDir string, quiet, skipUpdate bool, checkBundleRepository string, registryOpts ftypes.RegistryOptions) ([]string, error) { mu.Lock() diff --git a/pkg/dependency/id.go b/pkg/dependency/id.go index 77dd85bed3e0..12ef6ef773b3 100644 --- a/pkg/dependency/id.go +++ b/pkg/dependency/id.go @@ -38,6 +38,9 @@ func ID(ltype types.LangType, name, version string) string { // UID calculates the hash of the package for the unique ID func UID(filePath string, pkg types.Package) string { + if pkg.Identifier.UID != "" { + return pkg.Identifier.UID + } v := map[string]any{ "filePath": filePath, // To differentiate the hash of the same package but different file path "pkg": pkg, diff --git a/pkg/downloader/download.go b/pkg/downloader/download.go index 7190d3d3d0a3..63b130a667fd 100644 --- a/pkg/downloader/download.go +++ b/pkg/downloader/download.go @@ -1,16 +1,40 @@ package downloader import ( + "cmp" "context" + "crypto/tls" + "errors" "maps" + "net/http" + "net/url" "os" + "strings" + "time" + "github.com/google/go-github/v62/github" getter "github.com/hashicorp/go-getter" + "github.com/samber/lo" "golang.org/x/xerrors" ) +var ErrSkipDownload = errors.New("skip download") + +type Options struct { + Insecure bool + Auth Auth + ETag string + ClientMode getter.ClientMode +} + +type Auth struct { + Username string + Password string + Token string +} + // DownloadToTempDir downloads the configured source to a temp dir. -func DownloadToTempDir(ctx context.Context, url string, insecure bool) (string, error) { +func DownloadToTempDir(ctx context.Context, src string, opts Options) (string, error) { tempDir, err := os.MkdirTemp("", "trivy-download") if err != nil { return "", xerrors.Errorf("failed to create a temp dir: %w", err) @@ -21,7 +45,7 @@ func DownloadToTempDir(ctx context.Context, url string, insecure bool) (string, return "", xerrors.Errorf("unable to get the current dir: %w", err) } - if err = Download(ctx, url, tempDir, pwd, insecure); err != nil { + if _, err = Download(ctx, src, tempDir, pwd, opts); err != nil { return "", xerrors.Errorf("download error: %w", err) } @@ -29,13 +53,13 @@ func DownloadToTempDir(ctx context.Context, url string, insecure bool) (string, } // Download downloads the configured source to the destination. -func Download(ctx context.Context, src, dst, pwd string, insecure bool) error { +func Download(ctx context.Context, src, dst, pwd string, opts Options) (string, error) { // go-getter doesn't allow the dst directory already exists if the src is directory. _ = os.RemoveAll(dst) - var opts []getter.ClientOption - if insecure { - opts = append(opts, getter.WithInsecure()) + var clientOpts []getter.ClientOption + if opts.Insecure { + clientOpts = append(clientOpts, getter.WithInsecure()) } // Clone the global map so that it will not be accessed concurrently. @@ -47,8 +71,16 @@ func Download(ctx context.Context, src, dst, pwd string, insecure bool) error { // Since "httpGetter" is a global pointer and the state is shared, // once it is executed without "WithInsecure()", // it cannot enable WithInsecure() afterwards because its state is preserved. + // Therefore, we need to create a new "HttpGetter" instance every time. // cf. https://github.com/hashicorp/go-getter/blob/5a63fd9c0d5b8da8a6805e8c283f46f0dacb30b3/get.go#L63-L65 - httpGetter := &getter.HttpGetter{Netrc: true} + transport := NewCustomTransport(opts) + httpGetter := &getter.HttpGetter{ + Netrc: true, + Client: &http.Client{ + Transport: transport, + Timeout: time.Minute * 5, + }, + } getters["http"] = httpGetter getters["https"] = httpGetter @@ -59,13 +91,110 @@ func Download(ctx context.Context, src, dst, pwd string, insecure bool) error { Dst: dst, Pwd: pwd, Getters: getters, - Mode: getter.ClientModeAny, - Options: opts, + Mode: lo.Ternary(opts.ClientMode == 0, getter.ClientModeAny, opts.ClientMode), + Options: clientOpts, } if err := client.Get(); err != nil { - return xerrors.Errorf("failed to download: %w", err) + return "", xerrors.Errorf("failed to download %s: %w", src, err) + } + + return transport.newETag, nil +} + +type CustomTransport struct { + auth Auth + cachedETag string + newETag string + insecure bool +} + +func NewCustomTransport(opts Options) *CustomTransport { + return &CustomTransport{ + auth: opts.Auth, + cachedETag: opts.ETag, + insecure: opts.Insecure, + } +} + +func (t *CustomTransport) RoundTrip(req *http.Request) (*http.Response, error) { + if t.cachedETag != "" { + req.Header.Set("If-None-Match", t.cachedETag) + } + if t.auth.Token != "" { + req.Header.Set("Authorization", "Bearer "+t.auth.Token) + } else if t.auth.Username != "" || t.auth.Password != "" { + req.SetBasicAuth(t.auth.Username, t.auth.Password) + } + + var transport http.RoundTripper + if req.URL.Host == "github.com" { + transport = NewGitHubTransport(req.URL, t.insecure, t.auth.Token) + } + if transport == nil { + transport = httpTransport(t.insecure) + } + + res, err := transport.RoundTrip(req) + if err != nil { + return nil, xerrors.Errorf("failed to round trip: %w", err) + } + + switch res.StatusCode { + case http.StatusOK, http.StatusPartialContent: + // Update the ETag + t.newETag = res.Header.Get("ETag") + case http.StatusNotModified: + return nil, ErrSkipDownload + } + + return res, nil +} + +func NewGitHubTransport(u *url.URL, insecure bool, token string) http.RoundTripper { + client := newGitHubClient(insecure, token) + ss := strings.SplitN(u.Path, "/", 4) + if len(ss) < 4 || strings.HasPrefix(ss[3], "archive/") { + // Use the default transport from go-github for authentication + return client.Client().Transport + } + + return &GitHubContentTransport{ + owner: ss[1], + repo: ss[2], + filePath: ss[3], + client: client, } +} + +// GitHubContentTransport is a round tripper for downloading the GitHub content. +type GitHubContentTransport struct { + owner string + repo string + filePath string + client *github.Client +} + +// RoundTrip calls the GitHub API to download the content. +func (t *GitHubContentTransport) RoundTrip(req *http.Request) (*http.Response, error) { + _, res, err := t.client.Repositories.DownloadContents(req.Context(), t.owner, t.repo, t.filePath, nil) + if err != nil { + return nil, xerrors.Errorf("failed to get the file content: %w", err) + } + return res.Response, nil +} + +func newGitHubClient(insecure bool, token string) *github.Client { + client := github.NewClient(&http.Client{Transport: httpTransport(insecure)}) + token = cmp.Or(token, os.Getenv("GITHUB_TOKEN")) + if token != "" { + client = client.WithAuthToken(token) + } + return client +} - return nil +func httpTransport(insecure bool) *http.Transport { + tr := http.DefaultTransport.(*http.Transport).Clone() + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: insecure} + return tr } diff --git a/pkg/downloader/downloader_test.go b/pkg/downloader/downloader_test.go index 0487d7384d41..796f4b96b1d2 100644 --- a/pkg/downloader/downloader_test.go +++ b/pkg/downloader/downloader_test.go @@ -44,7 +44,9 @@ func TestDownload(t *testing.T) { dst := t.TempDir() // Execute the download - err := downloader.Download(context.Background(), server.URL, dst, "", tt.insecure) + _, err := downloader.Download(context.Background(), server.URL, dst, "", downloader.Options{ + Insecure: tt.insecure, + }) if tt.wantErr { assert.Error(t, err) diff --git a/pkg/flag/clean_flags.go b/pkg/flag/clean_flags.go index 7a898c38ad63..11e0e5934e90 100644 --- a/pkg/flag/clean_flags.go +++ b/pkg/flag/clean_flags.go @@ -27,31 +27,39 @@ var ( ConfigName: "clean.checks-bundle", Usage: "remove checks bundle", } + CleanVEXRepo = Flag[bool]{ + Name: "vex-repo", + ConfigName: "clean.vex-repo", + Usage: "remove VEX repositories", + } ) type CleanFlagGroup struct { CleanAll *Flag[bool] + CleanScanCache *Flag[bool] CleanVulnerabilityDB *Flag[bool] CleanJavaDB *Flag[bool] CleanChecksBundle *Flag[bool] - CleanScanCache *Flag[bool] + CleanVEXRepositories *Flag[bool] } type CleanOptions struct { CleanAll bool + CleanScanCache bool CleanVulnerabilityDB bool CleanJavaDB bool CleanChecksBundle bool - CleanScanCache bool + CleanVEXRepositories bool } func NewCleanFlagGroup() *CleanFlagGroup { return &CleanFlagGroup{ CleanAll: CleanAll.Clone(), + CleanScanCache: CleanScanCache.Clone(), CleanVulnerabilityDB: CleanVulnerabilityDB.Clone(), CleanJavaDB: CleanJavaDB.Clone(), CleanChecksBundle: CleanChecksBundle.Clone(), - CleanScanCache: CleanScanCache.Clone(), + CleanVEXRepositories: CleanVEXRepo.Clone(), } } @@ -62,10 +70,11 @@ func (fg *CleanFlagGroup) Name() string { func (fg *CleanFlagGroup) Flags() []Flagger { return []Flagger{ fg.CleanAll, + fg.CleanScanCache, fg.CleanVulnerabilityDB, fg.CleanJavaDB, fg.CleanChecksBundle, - fg.CleanScanCache, + fg.CleanVEXRepositories, } } @@ -80,5 +89,6 @@ func (fg *CleanFlagGroup) ToOptions() (CleanOptions, error) { CleanJavaDB: fg.CleanJavaDB.Value(), CleanChecksBundle: fg.CleanChecksBundle.Value(), CleanScanCache: fg.CleanScanCache.Value(), + CleanVEXRepositories: fg.CleanVEXRepositories.Value(), }, nil } diff --git a/pkg/flag/options.go b/pkg/flag/options.go index b485b74ef677..014da145f262 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -432,15 +432,16 @@ func (o *Options) RegistryOpts() ftypes.RegistryOptions { } // FilterOpts returns options for filtering -func (o *Options) FilterOpts() result.FilterOption { - return result.FilterOption{ +func (o *Options) FilterOpts() result.FilterOptions { + return result.FilterOptions{ Severities: o.Severities, IgnoreStatuses: o.IgnoreStatuses, IncludeNonFailures: o.IncludeNonFailures, IgnoreFile: o.IgnoreFile, PolicyFile: o.IgnorePolicy, IgnoreLicenses: o.IgnoredLicenses, - VEXPath: o.VEXPath, + CacheDir: o.CacheDir, + VEXSources: o.VEXSources, } } diff --git a/pkg/flag/vulnerability_flags.go b/pkg/flag/vulnerability_flags.go index 0e9d8291d059..da306997b5b8 100644 --- a/pkg/flag/vulnerability_flags.go +++ b/pkg/flag/vulnerability_flags.go @@ -5,6 +5,7 @@ import ( dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/vex" ) var ( @@ -19,30 +20,37 @@ var ( Values: dbTypes.Statuses, Usage: "comma-separated list of vulnerability status to ignore", } - VEXFlag = Flag[string]{ + VEXFlag = Flag[[]string]{ Name: "vex", ConfigName: "vulnerability.vex", - Default: "", - Usage: "[EXPERIMENTAL] file path to VEX", + Usage: `[EXPERIMENTAL] VEX sources ("repo" or file path)`, + } + SkipVEXRepoUpdateFlag = Flag[bool]{ + Name: "skip-vex-repo-update", + ConfigName: "vulnerability.skip-vex-repo-update", + Usage: `[EXPERIMENTAL] Skip VEX Repository update`, } ) type VulnerabilityFlagGroup struct { - IgnoreUnfixed *Flag[bool] - IgnoreStatus *Flag[[]string] - VEXPath *Flag[string] + IgnoreUnfixed *Flag[bool] + IgnoreStatus *Flag[[]string] + VEX *Flag[[]string] + SkipVEXRepoUpdate *Flag[bool] } type VulnerabilityOptions struct { - IgnoreStatuses []dbTypes.Status - VEXPath string + IgnoreStatuses []dbTypes.Status + VEXSources []vex.Source + SkipVEXRepoUpdate bool } func NewVulnerabilityFlagGroup() *VulnerabilityFlagGroup { return &VulnerabilityFlagGroup{ - IgnoreUnfixed: IgnoreUnfixedFlag.Clone(), - IgnoreStatus: IgnoreStatusFlag.Clone(), - VEXPath: VEXFlag.Clone(), + IgnoreUnfixed: IgnoreUnfixedFlag.Clone(), + IgnoreStatus: IgnoreStatusFlag.Clone(), + VEX: VEXFlag.Clone(), + SkipVEXRepoUpdate: SkipVEXRepoUpdateFlag.Clone(), } } @@ -54,7 +62,8 @@ func (f *VulnerabilityFlagGroup) Flags() []Flagger { return []Flagger{ f.IgnoreUnfixed, f.IgnoreStatus, - f.VEXPath, + f.VEX, + f.SkipVEXRepoUpdate, } } @@ -88,6 +97,9 @@ func (f *VulnerabilityFlagGroup) ToOptions() (VulnerabilityOptions, error) { return VulnerabilityOptions{ IgnoreStatuses: ignoreStatuses, - VEXPath: f.VEXPath.Value(), + VEXSources: lo.Map(f.VEX.Value(), func(s string, _ int) vex.Source { + return vex.NewSource(s) + }), + SkipVEXRepoUpdate: f.SkipVEXRepoUpdate.Value(), }, nil } diff --git a/pkg/log/logger.go b/pkg/log/logger.go index f46eb46fc87f..1f0b8e32e1a2 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -68,7 +68,12 @@ func Errorf(format string, args ...any) { slog.Default().Error(fmt.Sprintf(forma // Fatal for logging fatal errors func Fatal(msg string, args ...any) { // Fatal errors should be logged to stderr even if the logger is disabled. - New(NewHandler(os.Stderr, &Options{})).Log(context.Background(), LevelFatal, msg, args...) + if h, ok := slog.Default().Handler().(*ColorHandler); ok { + h.out = os.Stderr + } else { + slog.SetDefault(New(NewHandler(os.Stderr, &Options{}))) + } + slog.Default().Log(context.Background(), LevelFatal, msg, args...) os.Exit(1) } diff --git a/pkg/oci/artifact.go b/pkg/oci/artifact.go index 8cd4460ec919..04d62ee15d36 100644 --- a/pkg/oci/artifact.go +++ b/pkg/oci/artifact.go @@ -189,7 +189,7 @@ func (a *Artifact) download(ctx context.Context, layer v1.Layer, fileName, dir s // Decompress the downloaded file if it is compressed and copy it into the dst // NOTE: it's local copying, the insecure option doesn't matter. - if err = downloader.Download(ctx, f.Name(), dir, dir, false); err != nil { + if _, err = downloader.Download(ctx, f.Name(), dir, dir, downloader.Options{}); err != nil { return xerrors.Errorf("download error: %w", err) } diff --git a/pkg/plugin/index.go b/pkg/plugin/index.go index 58beeaa5f9c7..57c6f0260877 100644 --- a/pkg/plugin/index.go +++ b/pkg/plugin/index.go @@ -34,7 +34,8 @@ type Index struct { func (m *Manager) Update(ctx context.Context, opts Options) error { m.logger.InfoContext(ctx, "Updating the plugin index...", log.String("url", m.indexURL)) - if err := downloader.Download(ctx, m.indexURL, filepath.Dir(m.indexPath), "", opts.Insecure); err != nil { + if _, err := downloader.Download(ctx, m.indexURL, filepath.Dir(m.indexPath), "", + downloader.Options{Insecure: opts.Insecure}); err != nil { return xerrors.Errorf("unable to download the plugin index: %w", err) } return nil diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index c0f9bf431c87..799481b98f14 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -23,7 +23,7 @@ import ( const configFile = "plugin.yaml" var ( - pluginsRelativeDir = filepath.Join(".trivy", "plugins") + pluginsDir = "plugins" _defaultManager *Manager ) @@ -58,7 +58,7 @@ type Manager struct { } func NewManager(opts ...ManagerOption) *Manager { - root := filepath.Join(fsutils.HomeDir(), pluginsRelativeDir) + root := filepath.Join(fsutils.TrivyHomeDir(), pluginsDir) m := &Manager{ w: os.Stdout, indexURL: indexURL, @@ -111,7 +111,7 @@ func (m *Manager) Install(ctx context.Context, arg string, opts Options) (Plugin } func (m *Manager) install(ctx context.Context, src string, opts Options) (Plugin, error) { - tempDir, err := downloader.DownloadToTempDir(ctx, src, opts.Insecure) + tempDir, err := downloader.DownloadToTempDir(ctx, src, downloader.Options{Insecure: opts.Insecure}) if err != nil { return Plugin{}, xerrors.Errorf("download failed: %w", err) } diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 56c33644f854..7af8edec0388 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -155,7 +155,7 @@ func (p *Plugin) install(ctx context.Context, dst, pwd string, opts Options) err p.Installed.Platform = lo.FromPtr(platform.Selector) log.DebugContext(ctx, "Downloading the execution file...", log.String("uri", platform.URI)) - if err = downloader.Download(ctx, platform.URI, dst, pwd, opts.Insecure); err != nil { + if _, err = downloader.Download(ctx, platform.URI, dst, pwd, downloader.Options{Insecure: opts.Insecure}); err != nil { return xerrors.Errorf("unable to download the execution file (%s): %w", platform.URI, err) } return nil @@ -165,5 +165,5 @@ func (p *Plugin) Dir() string { if p.dir != "" { return p.dir } - return filepath.Join(fsutils.HomeDir(), pluginsRelativeDir, p.Name) + return filepath.Join(fsutils.TrivyHomeDir(), pluginsDir, p.Name) } diff --git a/pkg/result/filter.go b/pkg/result/filter.go index 7d4ead524ccc..e1f0e632197e 100644 --- a/pkg/result/filter.go +++ b/pkg/result/filter.go @@ -13,8 +13,6 @@ import ( "golang.org/x/xerrors" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy/pkg/sbom/core" - sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/vex" ) @@ -24,31 +22,35 @@ const ( DefaultIgnoreFile = ".trivyignore" ) -type FilterOption struct { +type FilterOptions struct { Severities []dbTypes.Severity IgnoreStatuses []dbTypes.Status IncludeNonFailures bool IgnoreFile string PolicyFile string IgnoreLicenses []string - VEXPath string + CacheDir string + VEXSources []vex.Source } // Filter filters out the report -func Filter(ctx context.Context, report types.Report, opt FilterOption) error { - ignoreConf, err := ParseIgnoreFile(ctx, opt.IgnoreFile) +func Filter(ctx context.Context, report types.Report, opts FilterOptions) error { + ignoreConf, err := ParseIgnoreFile(ctx, opts.IgnoreFile) if err != nil { - return xerrors.Errorf("%s error: %w", opt.IgnoreFile, err) + return xerrors.Errorf("%s error: %w", opts.IgnoreFile, err) } for i := range report.Results { - if err = FilterResult(ctx, &report.Results[i], ignoreConf, opt); err != nil { + if err = FilterResult(ctx, &report.Results[i], ignoreConf, opts); err != nil { return xerrors.Errorf("unable to filter vulnerabilities: %w", err) } } // Filter out vulnerabilities based on the given VEX document. - if err = filterByVEX(report, opt); err != nil { + if err = vex.Filter(ctx, &report, vex.Options{ + CacheDir: opts.CacheDir, + Sources: opts.VEXSources, + }); err != nil { return xerrors.Errorf("VEX error: %w", err) } @@ -56,7 +58,7 @@ func Filter(ctx context.Context, report types.Report, opt FilterOption) error { } // FilterResult filters out the result -func FilterResult(ctx context.Context, result *types.Result, ignoreConf IgnoreConfig, opt FilterOption) error { +func FilterResult(ctx context.Context, result *types.Result, ignoreConf IgnoreConfig, opt FilterOptions) error { // Convert dbTypes.Severity to string severities := lo.Map(opt.Severities, func(s dbTypes.Severity, _ int) string { return s.String() @@ -77,31 +79,6 @@ func FilterResult(ctx context.Context, result *types.Result, ignoreConf IgnoreCo return nil } -// filterByVEX determines whether a detected vulnerability should be filtered out based on the provided VEX document. -// If the VEX document is not nil and the vulnerability is either not affected or fixed according to the VEX statement, -// the vulnerability is filtered out. -func filterByVEX(report types.Report, opt FilterOption) error { - vexDoc, err := vex.New(opt.VEXPath, report) - if err != nil { - return err - } else if vexDoc == nil { - return nil - } - - bom, err := sbomio.NewEncoder(core.Options{Parents: true}).Encode(report) - if err != nil { - return xerrors.Errorf("unable to encode the SBOM: %w", err) - } - - for i, result := range report.Results { - if len(result.Vulnerabilities) == 0 { - continue - } - vexDoc.Filter(&report.Results[i], bom) - } - return nil -} - func filterVulnerabilities(result *types.Result, severities []string, ignoreStatuses []dbTypes.Status, ignoreConfig IgnoreConfig) { uniqVulns := make(map[string]types.DetectedVulnerability) for _, vuln := range result.Vulnerabilities { diff --git a/pkg/result/filter_test.go b/pkg/result/filter_test.go index 5c1eeb6771c3..0298cd0d9582 100644 --- a/pkg/result/filter_test.go +++ b/pkg/result/filter_test.go @@ -15,6 +15,7 @@ import ( ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/result" "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/vex" ) func TestFilter(t *testing.T) { @@ -291,7 +292,7 @@ func TestFilter(t *testing.T) { Type: types.FindingTypeVulnerability, Status: types.FindingStatusNotAffected, Statement: "vulnerable_code_not_in_execute_path", - Source: "OpenVEX", + Source: "testdata/openvex.json", Finding: vuln1, }, }, @@ -1007,9 +1008,17 @@ func TestFilter(t *testing.T) { fakeTime := time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC) ctx := clock.With(context.Background(), fakeTime) - err := result.Filter(ctx, tt.args.report, result.FilterOption{ + var vexSources []vex.Source + if tt.args.vexPath != "" { + vexSources = append(vexSources, vex.Source{ + Type: vex.TypeFile, + FilePath: tt.args.vexPath, + }) + } + + err := result.Filter(ctx, tt.args.report, result.FilterOptions{ Severities: tt.args.severities, - VEXPath: tt.args.vexPath, + VEXSources: vexSources, IgnoreStatuses: tt.args.ignoreStatuses, IgnoreFile: tt.args.ignoreFile, PolicyFile: tt.args.policyFile, diff --git a/pkg/sbom/io/decode.go b/pkg/sbom/io/decode.go index 379e1af32d52..6cfc79a0871c 100644 --- a/pkg/sbom/io/decode.go +++ b/pkg/sbom/io/decode.go @@ -137,11 +137,11 @@ func (m *Decoder) decodeComponents(ctx context.Context, sbom *types.SBOM) error // Third-party SBOMs may contain packages in types other than "Library" if c.Type == core.TypeLibrary || c.PkgIdentifier.PURL != nil { - pkg, err := m.decodeLibrary(ctx, c) + pkg, err := m.decodePackage(ctx, c) if errors.Is(err, ErrUnsupportedType) || errors.Is(err, ErrPURLEmpty) { continue } else if err != nil { - return xerrors.Errorf("failed to decode library: %w", err) + return xerrors.Errorf("failed to decode package: %w", err) } m.pkgs[id] = pkg } @@ -184,7 +184,7 @@ func (m *Decoder) decodeApplication(c *core.Component) *ftypes.Application { return &app } -func (m *Decoder) decodeLibrary(ctx context.Context, c *core.Component) (*ftypes.Package, error) { +func (m *Decoder) decodePackage(ctx context.Context, c *core.Component) (*ftypes.Package, error) { p := (*purl.PackageURL)(c.PkgIdentifier.PURL) if p == nil { m.logger.DebugContext(ctx, "Skipping a component without PURL", diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index 096abd026b86..45c5dca245c6 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -35,6 +35,10 @@ func (e *Encoder) Encode(report types.Report) (*core.BOM, error) { } e.bom = core.NewBOM(e.opts) + if report.BOM != nil { + e.bom.SerialNumber = report.BOM.SerialNumber + e.bom.Version = report.BOM.Version + } e.bom.AddComponent(root) for _, result := range report.Results { diff --git a/pkg/utils/fsutils/fs.go b/pkg/utils/fsutils/fs.go index c15302deed1d..e0518236f9a8 100644 --- a/pkg/utils/fsutils/fs.go +++ b/pkg/utils/fsutils/fs.go @@ -28,6 +28,10 @@ func HomeDir() string { return homeDir } +func TrivyHomeDir() string { + return filepath.Join(HomeDir(), ".trivy") +} + // CopyFile copies the file content from scr to dst func CopyFile(src, dst string) (int64, error) { sourceFileStat, err := os.Stat(src) diff --git a/pkg/vex/csaf.go b/pkg/vex/csaf.go index 35680a8ddfc5..1f9c91fdd001 100644 --- a/pkg/vex/csaf.go +++ b/pkg/vex/csaf.go @@ -12,6 +12,7 @@ import ( type CSAF struct { advisory csaf.Advisory + source string logger *log.Logger } @@ -20,9 +21,10 @@ type relationship struct { SubProducts []*purl.PackageURL } -func newCSAF(advisory csaf.Advisory) VEX { +func newCSAF(advisory csaf.Advisory, source string) *CSAF { return &CSAF{ advisory: advisory, + source: source, logger: log.WithPrefix("vex").With(log.String("format", "CSAF")), } } @@ -43,7 +45,7 @@ func (v *CSAF) NotAffected(vuln types.DetectedVulnerability, product, subProduct if status == "" { return types.ModifiedFinding{}, false } - return types.NewModifiedFinding(vuln, status, v.statement(found), "CSAF VEX"), true + return types.NewModifiedFinding(vuln, status, v.statement(found), v.source), true } func (v *CSAF) match(vuln *csaf.Vulnerability, product, subProduct *core.Component) types.FindingStatus { diff --git a/pkg/vex/cyclonedx.go b/pkg/vex/cyclonedx.go index 7bee16d32c81..771cc71be253 100644 --- a/pkg/vex/cyclonedx.go +++ b/pkg/vex/cyclonedx.go @@ -11,58 +11,48 @@ import ( type CycloneDX struct { sbom *core.BOM - statements []Statement + statements map[string]Statement logger *log.Logger } type Statement struct { - VulnerabilityID string - Affects []string - Status types.FindingStatus - Justification string + Affects []string + Status types.FindingStatus + Justification string } func newCycloneDX(sbom *core.BOM, vex *cdx.BOM) *CycloneDX { - var stmts []Statement + statements := make(map[string]Statement) for _, vuln := range lo.FromPtr(vex.Vulnerabilities) { affects := lo.Map(lo.FromPtr(vuln.Affects), func(item cdx.Affects, index int) string { return item.Ref }) analysis := lo.FromPtr(vuln.Analysis) - stmts = append(stmts, Statement{ - VulnerabilityID: vuln.ID, - Affects: affects, - Status: cdxStatus(analysis.State), - Justification: string(analysis.Justification), - }) + statements[vuln.ID] = Statement{ + Affects: affects, + Status: cdxStatus(analysis.State), + Justification: string(analysis.Justification), + } } return &CycloneDX{ sbom: sbom, - statements: stmts, + statements: statements, logger: log.WithPrefix("vex").With(log.String("format", "CycloneDX")), } } -func (v *CycloneDX) Filter(result *types.Result, _ *core.BOM) { - result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool { - stmt, ok := lo.Find(v.statements, func(item Statement) bool { - return item.VulnerabilityID == vuln.VulnerabilityID - }) - if !ok { - return true - } - if !v.affected(vuln, stmt) { - result.ModifiedFindings = append(result.ModifiedFindings, - types.NewModifiedFinding(vuln, stmt.Status, stmt.Justification, "CycloneDX VEX")) - return false - } - return true - }) -} +func (v *CycloneDX) NotAffected(vuln types.DetectedVulnerability, product, _ *core.Component) (types.ModifiedFinding, bool) { + stmt, ok := v.statements[vuln.VulnerabilityID] + if !ok { + return types.ModifiedFinding{}, false + } -func (v *CycloneDX) affected(vuln types.DetectedVulnerability, stmt Statement) bool { for _, affect := range stmt.Affects { + if stmt.Status != types.FindingStatusNotAffected && stmt.Status != types.FindingStatusFixed { + continue + } + // Affect must be BOM-Link at the moment link, err := cdx.ParseBOMLink(affect) if err != nil { @@ -75,11 +65,11 @@ func (v *CycloneDX) affected(vuln types.DetectedVulnerability, stmt Statement) b log.Int("version", link.Version())) continue } - if vuln.PkgIdentifier.Match(link.Reference()) && (stmt.Status == types.FindingStatusNotAffected || stmt.Status == types.FindingStatusFixed) { - return false + if product.PkgIdentifier.Match(link.Reference()) { + return types.NewModifiedFinding(vuln, stmt.Status, stmt.Justification, "CycloneDX VEX"), true } } - return true + return types.ModifiedFinding{}, false } func cdxStatus(s cdx.ImpactAnalysisState) types.FindingStatus { diff --git a/pkg/vex/document.go b/pkg/vex/document.go new file mode 100644 index 000000000000..7331bc26b93b --- /dev/null +++ b/pkg/vex/document.go @@ -0,0 +1,98 @@ +package vex + +import ( + "encoding/json" + "io" + "os" + + "github.com/csaf-poc/csaf_distribution/v3/csaf" + "github.com/hashicorp/go-multierror" + openvex "github.com/openvex/go-vex/pkg/vex" + "github.com/sirupsen/logrus" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/sbom" + "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" + "github.com/aquasecurity/trivy/pkg/types" +) + +func NewDocument(filePath string, report *types.Report) (VEX, error) { + if filePath == "" { + return nil, xerrors.New("VEX file path is empty") + } + f, err := os.Open(filePath) + if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + var errs error + // Try CycloneDX JSON + if ok, err := sbom.IsCycloneDXJSON(f); err != nil { + errs = multierror.Append(errs, err) + } else if ok { + return decodeCycloneDXJSON(f, report) + } + + // Try OpenVEX + if v, err := decodeOpenVEX(f, filePath); err != nil { + errs = multierror.Append(errs, err) + } else if v != nil { + return v, nil + } + + // Try CSAF + if v, err := decodeCSAF(f, filePath); err != nil { + errs = multierror.Append(errs, err) + } else if v != nil { + return v, nil + } + + return nil, xerrors.Errorf("unable to load VEX: %w", errs) +} + +func decodeCycloneDXJSON(r io.ReadSeeker, report *types.Report) (*CycloneDX, error) { + if _, err := r.Seek(0, io.SeekStart); err != nil { + return nil, xerrors.Errorf("seek error: %w", err) + } + vex, err := cyclonedx.DecodeJSON(r) + if err != nil { + return nil, xerrors.Errorf("json decode error: %w", err) + } + if report.ArtifactType != artifact.TypeCycloneDX { + return nil, xerrors.New("CycloneDX VEX can be used with CycloneDX SBOM") + } + return newCycloneDX(report.BOM, vex), nil +} + +func decodeOpenVEX(r io.ReadSeeker, source string) (*OpenVEX, error) { + // openvex/go-vex outputs log messages by default + logrus.SetOutput(io.Discard) + + if _, err := r.Seek(0, io.SeekStart); err != nil { + return nil, xerrors.Errorf("seek error: %w", err) + } + var openVEX openvex.VEX + if err := json.NewDecoder(r).Decode(&openVEX); err != nil { + return nil, err + } + if openVEX.Context == "" { + return nil, nil + } + return newOpenVEX(openVEX, source), nil +} + +func decodeCSAF(r io.ReadSeeker, source string) (*CSAF, error) { + if _, err := r.Seek(0, io.SeekStart); err != nil { + return nil, xerrors.Errorf("seek error: %w", err) + } + var adv csaf.Advisory + if err := json.NewDecoder(r).Decode(&adv); err != nil { + return nil, err + } + if adv.Vulnerabilities == nil { + return nil, nil + } + return newCSAF(adv, source), nil +} diff --git a/pkg/vex/openvex.go b/pkg/vex/openvex.go index 36ab7808d559..d300de66ff61 100644 --- a/pkg/vex/openvex.go +++ b/pkg/vex/openvex.go @@ -8,12 +8,14 @@ import ( ) type OpenVEX struct { - vex openvex.VEX + vex openvex.VEX + source string } -func newOpenVEX(vex openvex.VEX) VEX { +func newOpenVEX(vex openvex.VEX, source string) *OpenVEX { return &OpenVEX{ - vex: vex, + vex: vex, + source: source, } } @@ -32,7 +34,7 @@ func (v *OpenVEX) NotAffected(vuln types.DetectedVulnerability, product, subComp // cf. https://github.com/openvex/spec/blob/fa5ba0c0afedb008dc5ebad418548cacf16a3ca7/OPENVEX-SPEC.md#the-vex-statement stmt := stmts[len(stmts)-1] if stmt.Status == openvex.StatusNotAffected || stmt.Status == openvex.StatusFixed { - modifiedFindings := types.NewModifiedFinding(vuln, findingStatus(stmt.Status), string(stmt.Justification), "OpenVEX") + modifiedFindings := types.NewModifiedFinding(vuln, findingStatus(stmt.Status), string(stmt.Justification), v.source) return modifiedFindings, true } return types.ModifiedFinding{}, false diff --git a/pkg/vex/repo.go b/pkg/vex/repo.go new file mode 100644 index 000000000000..d7c6717b8e52 --- /dev/null +++ b/pkg/vex/repo.go @@ -0,0 +1,143 @@ +package vex + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "sync" + + "github.com/package-url/packageurl-go" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/vex/repo" + xsync "github.com/aquasecurity/trivy/pkg/x/sync" +) + +var errNoRepository = errors.New("no available VEX repository found") + +// RepositoryIndex wraps the repository index +type RepositoryIndex struct { + Name string + URL string + repo.Index +} + +type RepositorySet struct { + indexes []RepositoryIndex + logOnce *xsync.Map[string, *sync.Once] + logger *log.Logger +} + +func NewRepositorySet(ctx context.Context, cacheDir string) (*RepositorySet, error) { + conf, err := repo.NewManager(cacheDir).Config(ctx) + if err != nil { + return nil, xerrors.Errorf("failed to get VEX repository config: %w", err) + } + + logger := log.WithPrefix("vex") + var indexes []RepositoryIndex + for _, r := range conf.EnabledRepositories() { + index, err := r.Index(ctx) + if errors.Is(err, os.ErrNotExist) { + logger.Warn("VEX repository not found locally, skipping this repository", log.String("repo", r.Name)) + continue + } else if err != nil { + return nil, xerrors.Errorf("failed to get VEX repository index: %w", err) + } + indexes = append(indexes, RepositoryIndex{ + Name: r.Name, + URL: r.URL, + Index: index, + }) + } + if len(indexes) == 0 { + logger.Warn("No available VEX repository found locally") + return nil, errNoRepository + } + + return &RepositorySet{ + indexes: indexes, // In precedence order + logOnce: new(xsync.Map[string, *sync.Once]), + logger: logger, + }, nil +} + +func (rs *RepositorySet) NotAffected(vuln types.DetectedVulnerability, product, subComponent *core.Component) (types.ModifiedFinding, bool) { + if product == nil || product.PkgIdentifier.PURL == nil { + return types.ModifiedFinding{}, false + } + p := *product.PkgIdentifier.PURL + + // Exclude version, qualifiers, and subpath from the package URL except for OCI + // cf. https://github.com/aquasecurity/vex-repo-spec?tab=readme-ov-file#32-indexjson + p.Version = "" + p.Qualifiers = nil + p.Subpath = "" + + if p.Type == packageurl.TypeOCI { + // For OCI artifacts, we consider "repository_url" is part of name. + for _, q := range product.PkgIdentifier.PURL.Qualifiers { + if q.Key == "repository_url" { + p.Qualifiers = packageurl.Qualifiers{q} + break + } + } + } + + pkgID := p.String() // PURL without version, qualifiers, and subpath + for _, index := range rs.indexes { + entry, ok := index.Packages[pkgID] + if !ok { + continue + } + rs.logVEXFound(pkgID, index.Name, index.URL, entry.Location) + + source := fmt.Sprintf("VEX Repository: %s (%s)", index.Name, index.URL) + doc, err := rs.OpenDocument(source, filepath.Dir(index.Path), entry) + if err != nil { + rs.logger.Warn("Failed to open the VEX document", log.String("location", entry.Location), log.Err(err)) + return types.ModifiedFinding{}, false + } + + if m, notAffected := doc.NotAffected(vuln, product, subComponent); notAffected { + return m, notAffected + } + + break // Stop searching for the next VEX document as this repository has higher precedence. + } + return types.ModifiedFinding{}, false +} + +func (rs *RepositorySet) OpenDocument(source, dir string, entry repo.PackageEntry) (VEX, error) { + f, err := os.Open(filepath.Join(dir, entry.Location)) + if err != nil { + return nil, xerrors.Errorf("failed to open the VEX document: %w", err) + } + defer f.Close() + + switch entry.Format { + case "openvex", "": + return decodeOpenVEX(f, source) + case "csaf": + return decodeCSAF(f, source) + default: + return nil, xerrors.Errorf("unsupported VEX format: %s", entry.Format) + } +} + +func (rs *RepositorySet) logVEXFound(pkgID, repoName, repoURL, filePath string) { + once, _ := rs.logOnce.LoadOrStore(pkgID, &sync.Once{}) + once.Do(func() { + rs.logger.Debug("VEX found in the repository", + log.String("package", pkgID), + log.String("repo", repoName), + log.String("repo_url", repoURL), + log.FilePath(filePath), + ) + }) +} diff --git a/pkg/vex/repo/manager.go b/pkg/vex/repo/manager.go new file mode 100644 index 000000000000..b157156bdf74 --- /dev/null +++ b/pkg/vex/repo/manager.go @@ -0,0 +1,189 @@ +package repo + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/samber/lo" + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" + + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +const ( + defaultVEXHubURL = "https://github.com/aquasecurity/vexhub" + vexDir = "vex" + repoDir = "repositories" +) + +type ManagerOption func(indexer *Manager) + +func WithWriter(w io.Writer) ManagerOption { + return func(manager *Manager) { + manager.w = w + } +} + +type Config struct { + Repositories []Repository `json:"repositories"` +} + +func (c *Config) EnabledRepositories() []Repository { + return lo.Filter(c.Repositories, func(r Repository, _ int) bool { + return r.Enabled + }) +} + +type Options struct { + Insecure bool +} + +// Manager manages the repositories +type Manager struct { + w io.Writer + configFile string + cacheDir string +} + +func NewManager(cacheRoot string, opts ...ManagerOption) *Manager { + m := &Manager{ + w: os.Stdout, + configFile: filepath.Join(fsutils.TrivyHomeDir(), vexDir, "repository.yaml"), + cacheDir: filepath.Join(cacheRoot, vexDir), + } + for _, opt := range opts { + opt(m) + } + + return m +} + +func (m *Manager) writeConfig(conf Config) error { + if err := os.MkdirAll(filepath.Dir(m.configFile), 0700); err != nil { + return xerrors.Errorf("failed to mkdir: %w", err) + } + f, err := os.Create(m.configFile) + if err != nil { + return xerrors.Errorf("failed to create a file: %w", err) + } + defer f.Close() + + e := yaml.NewEncoder(f) + e.SetIndent(2) + if err = e.Encode(conf); err != nil { + return xerrors.Errorf("JSON encode error: %w", err) + } + + return nil +} + +func (m *Manager) Config(ctx context.Context) (Config, error) { + if !fsutils.FileExists(m.configFile) { + log.DebugContext(ctx, "No repository config found", log.String("path", m.configFile)) + if err := m.Init(ctx); err != nil { + return Config{}, xerrors.Errorf("unable to initialize the VEX repository config: %w", err) + } + } + + f, err := os.Open(m.configFile) + if err != nil { + return Config{}, xerrors.Errorf("unable to open a file: %w", err) + } + defer f.Close() + + var conf Config + if err = yaml.NewDecoder(f).Decode(&conf); err != nil { + return conf, xerrors.Errorf("unable to decode metadata: %w", err) + } + + for i, repo := range conf.Repositories { + conf.Repositories[i].dir = filepath.Join(m.cacheDir, repoDir, repo.Name) + } + + return conf, nil +} + +func (m *Manager) Init(ctx context.Context) error { + if fsutils.FileExists(m.configFile) { + log.InfoContext(ctx, "The configuration file already exists", log.String("path", m.configFile)) + return nil + } + + err := m.writeConfig(Config{ + Repositories: []Repository{ + { + Name: "default", + URL: defaultVEXHubURL, + Enabled: true, + }, + }, + }) + if err != nil { + return xerrors.Errorf("failed to write the default config: %w", err) + } + log.InfoContext(ctx, "The default repository config has been created", log.FilePath(m.configFile)) + return nil +} + +func (m *Manager) DownloadRepositories(ctx context.Context, names []string, opts Options) error { + conf, err := m.Config(ctx) + if err != nil { + return xerrors.Errorf("unable to read config: %w", err) + } + + repos := lo.Filter(conf.EnabledRepositories(), func(r Repository, _ int) bool { + return len(names) == 0 || slices.Contains(names, r.Name) + }) + if len(repos) == 0 { + log.WarnContext(ctx, "No enabled repositories found in config", log.String("path", m.configFile)) + return nil + } + + for _, repo := range repos { + if err = repo.Update(ctx, opts); err != nil { + return xerrors.Errorf("failed to update the repository: %w", err) + } + } + return nil +} + +// List returns a list of all repositories in the configuration +func (m *Manager) List(ctx context.Context) error { + conf, err := m.Config(ctx) + if err != nil { + return xerrors.Errorf("unable to read config: %w", err) + } + + var output strings.Builder + + output.WriteString(fmt.Sprintf("VEX Repositories (config: %s)\n\n", m.configFile)) + + if len(conf.Repositories) == 0 { + output.WriteString("No repositories configured.\n") + } else { + for _, repo := range conf.Repositories { + status := "Enabled" + if !repo.Enabled { + status = "Disabled" + } + output.WriteString(fmt.Sprintf("- Name: %s\n URL: %s\n Status: %s\n\n", repo.Name, repo.URL, status)) + } + } + + if _, err = io.WriteString(m.w, output.String()); err != nil { + return xerrors.Errorf("failed to write output: %w", err) + } + + return nil +} + +func (m *Manager) Clear() error { + return os.RemoveAll(m.cacheDir) +} diff --git a/pkg/vex/repo/manager_test.go b/pkg/vex/repo/manager_test.go new file mode 100644 index 000000000000..362137bd1804 --- /dev/null +++ b/pkg/vex/repo/manager_test.go @@ -0,0 +1,335 @@ +package repo_test + +import ( + "bytes" + "context" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/vex/repo" +) + +func TestManager_Config(t *testing.T) { + tests := []struct { + name string + setup func(*testing.T, string) + want repo.Config + wantErr string + }{ + { + name: "config file exists", + setup: func(t *testing.T, dir string) { + config := repo.Config{ + Repositories: []repo.Repository{ + { + Name: "test-repo", + URL: "https://example.com/repo", + Enabled: true, + }, + }, + } + configPath := filepath.Join(dir, ".trivy", "vex", "repository.yaml") + testutil.MustWriteYAML(t, configPath, config) + }, + want: repo.Config{ + Repositories: []repo.Repository{ + { + Name: "test-repo", + URL: "https://example.com/repo", + Enabled: true, + }, + }, + }, + }, + { + name: "config file does not exist", + setup: func(t *testing.T, dir string) {}, + want: repo.Config{ + Repositories: []repo.Repository{ + { + Name: "default", + URL: "https://github.com/aquasecurity/vexhub", + Enabled: true, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + t.Setenv("XDG_DATA_HOME", tempDir) + m := repo.NewManager(tempDir) + + tt.setup(t, tempDir) + + got, err := m.Config(context.Background()) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + assert.EqualExportedValues(t, tt.want, got) + }) + } +} + +func TestManager_Init(t *testing.T) { + tests := []struct { + name string + setup func(*testing.T, string) + want repo.Config + wantErr string + }{ + { + name: "successful init", + setup: func(t *testing.T, dir string) {}, + want: repo.Config{ + Repositories: []repo.Repository{ + { + Name: "default", + URL: "https://github.com/aquasecurity/vexhub", + Enabled: true, + }, + }, + }, + }, + { + name: "config already exists", + setup: func(t *testing.T, dir string) { + configPath := filepath.Join(dir, ".trivy", "vex", "repository.yaml") + testutil.MustWriteYAML(t, configPath, repo.Config{}) + }, + want: repo.Config{ + Repositories: []repo.Repository{}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + t.Setenv("XDG_DATA_HOME", tempDir) + m := repo.NewManager(tempDir) + + tt.setup(t, tempDir) + + err := m.Init(context.Background()) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + + configPath := filepath.Join(tempDir, ".trivy", "vex", "repository.yaml") + assert.FileExists(t, configPath) + + var got repo.Config + testutil.MustReadYAML(t, configPath, &got) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestManager_DownloadRepositories(t *testing.T) { + ts := setUpRepository(t) + defer ts.Close() + + tests := []struct { + name string + config repo.Config + location string + names []string + wantErr string + wantDownload bool + }{ + { + name: "successful download", + config: repo.Config{ + Repositories: []repo.Repository{ + { + Name: "test-repo", + URL: ts.URL, + Enabled: true, + }, + }, + }, + location: ts.URL + "/archive.zip", + wantDownload: true, + }, + { + name: "no enabled repositories", + config: repo.Config{ + Repositories: []repo.Repository{ + { + Name: "test-repo", + URL: "https://localhost:10000", // Will not be reached + Enabled: false, + }, + }, + }, + location: ts.URL + "/archive.zip", + wantDownload: false, + }, + { + name: "download specific repository", + config: repo.Config{ + Repositories: []repo.Repository{ + { + Name: "another-repo", + URL: "https://example.com/repo", + Enabled: true, + }, + { + Name: "test-repo", + URL: ts.URL, + Enabled: true, + }, + }, + }, + location: ts.URL + "/archive.zip", + names: []string{"test-repo"}, + wantDownload: true, + }, + { + name: "download error", + config: repo.Config{ + Repositories: []repo.Repository{ + { + Name: "test-repo", + URL: ts.URL, + Enabled: true, + }, + }, + }, + location: ts.URL + "/error", + wantErr: "failed to download the repository", + wantDownload: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + t.Setenv("XDG_DATA_HOME", tempDir) + m := repo.NewManager(tempDir) + + configPath := filepath.Join(tempDir, ".trivy", "vex", "repository.yaml") + testutil.MustWriteYAML(t, configPath, tt.config) + + manifestPath := filepath.Join(tempDir, "vex", "repositories", "test-repo", "vex-repository.json") + manifest.Versions[0].Locations[0].URL = tt.location + testutil.MustWriteJSON(t, manifestPath, manifest) + + err := m.DownloadRepositories(context.Background(), tt.names, repo.Options{}) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + + // Check if the repository was downloaded + if tt.wantDownload { + repoDir := filepath.Join(tempDir, "vex", "repositories", "test-repo") + assert.DirExists(t, repoDir) + assert.FileExists(t, filepath.Join(repoDir, "vex-repository.json")) + assert.FileExists(t, filepath.Join(repoDir, "0.1", "index.json")) + } + }) + } +} + +func TestManager_List(t *testing.T) { + tests := []struct { + name string + config repo.Config + want string + wantErr string + }{ + { + name: "list repositories", + config: repo.Config{ + Repositories: []repo.Repository{ + { + Name: "default", + URL: "https://github.com/aquasecurity/vexhub", + Enabled: true, + }, + { + Name: "custom", + URL: "https://example.com/custom-vex-repo", + Enabled: false, + }, + }, + }, + want: `VEX Repositories (config: %s) + +- Name: default + URL: https://github.com/aquasecurity/vexhub + Status: Enabled + +- Name: custom + URL: https://example.com/custom-vex-repo + Status: Disabled + +`, + }, + { + name: "no repositories", + config: repo.Config{ + Repositories: []repo.Repository{}, + }, + want: `VEX Repositories (config: %s) + +No repositories configured. +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + t.Setenv("XDG_DATA_HOME", tempDir) + configPath := filepath.Join(tempDir, ".trivy", "vex", "repository.yaml") + testutil.MustWriteYAML(t, configPath, tt.config) + + var buf bytes.Buffer + m := repo.NewManager(tempDir, repo.WithWriter(&buf)) + + err := m.List(context.Background()) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + want := fmt.Sprintf(tt.want, configPath) + require.NoError(t, err) + assert.Equal(t, want, buf.String()) + }) + } +} + +func TestManager_Clear(t *testing.T) { + tempDir := t.TempDir() + m := repo.NewManager(tempDir) + + // Create some dummy files + cacheDir := filepath.Join(tempDir, "vex") + require.NoError(t, os.MkdirAll(cacheDir, 0755)) + dummyFile := filepath.Join(cacheDir, "dummy.txt") + require.NoError(t, os.WriteFile(dummyFile, []byte("dummy"), 0644)) + + err := m.Clear() + require.NoError(t, err) + + // Check if the cache directory was removed + _, err = os.Stat(cacheDir) + assert.True(t, os.IsNotExist(err)) +} diff --git a/pkg/vex/repo/repo.go b/pkg/vex/repo/repo.go new file mode 100644 index 000000000000..433b8224c20e --- /dev/null +++ b/pkg/vex/repo/repo.go @@ -0,0 +1,327 @@ +package repo + +import ( + "context" + "encoding/json" + "errors" + "net/url" + "os" + "path" + "path/filepath" + "time" + + "github.com/hashicorp/go-getter" + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/downloader" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +const ( + SchemaVersion = "0.1" + + manifestFile = "vex-repository.json" + indexFile = "index.json" + cacheMetadataFile = "cache.json" +) + +type Manifest struct { + Name string `json:"name"` + Description string `json:"description"` + Versions []Version `json:"versions"` +} + +type Version struct { + SpecVersion string `json:"spec_version"` + Locations []Location `json:"locations"` + UpdateInterval Duration `json:"update_interval"` +} + +// Duration is a wrapper around time.Duration that implements UnmarshalJSON +type Duration struct { + time.Duration +} + +func (d Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(d.String()) +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (d *Duration) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return xerrors.Errorf("duration unmarshal error: %w", err) + } + + var err error + d.Duration, err = time.ParseDuration(s) + if err != nil { + return xerrors.Errorf("duration parse error: %w", err) + } + return nil +} + +type Location struct { + URL string `json:"url"` +} + +type Index struct { + Path string // Path to the index file + UpdatedAt time.Time + Packages map[string]PackageEntry +} + +type PackageEntry struct { + ID string `json:"id"` + Location string `json:"location"` + Format string `json:"format"` +} + +type RawIndex struct { + UpdatedAt time.Time `json:"updated_at"` + Packages []PackageEntry `json:"packages"` +} + +type Repository struct { + Name string + URL string + Enabled bool + Username string + Password string + Token string // For Bearer + + dir string // Root directory for this VEX repository, $CACHE_DIR/vex/repositories/$REPO_NAME/ +} + +type CacheMetadata struct { + UpdatedAt time.Time // Last updated time + ETags map[string]string // Last ETag for each URL +} + +func (r *Repository) Manifest(ctx context.Context, opts Options) (Manifest, error) { + filePath := filepath.Join(r.dir, manifestFile) + if !fsutils.FileExists(filePath) { + if err := r.downloadManifest(ctx, opts); err != nil { + return Manifest{}, xerrors.Errorf("failed to download the repository metadata: %w", err) + } + } + + log.DebugContext(ctx, "Reading the repository metadata...", log.String("repo", r.Name), log.FilePath(filePath)) + f, err := os.Open(filePath) + if err != nil { + return Manifest{}, xerrors.Errorf("failed to open the file: %w", err) + } + defer f.Close() + + var manifest Manifest + if err = json.NewDecoder(f).Decode(&manifest); err != nil { + return Manifest{}, xerrors.Errorf("failed to decode the metadata: %w", err) + } + return manifest, nil +} + +func (r *Repository) Index(ctx context.Context) (Index, error) { + filePath := filepath.Join(r.dir, SchemaVersion, indexFile) + log.DebugContext(ctx, "Reading the repository index...", log.String("repo", r.Name), log.FilePath(filePath)) + + f, err := os.Open(filePath) + if err != nil { + return Index{}, xerrors.Errorf("failed to open the file: %w", err) + } + defer f.Close() + + var raw RawIndex + if err = json.NewDecoder(f).Decode(&raw); err != nil { + return Index{}, xerrors.Errorf("failed to decode the index: %w", err) + } + + return Index{ + Path: filePath, + UpdatedAt: raw.UpdatedAt, + Packages: lo.KeyBy(raw.Packages, func(p PackageEntry) string { return p.ID }), + }, nil +} + +func (r *Repository) downloadManifest(ctx context.Context, opts Options) error { + if err := os.MkdirAll(r.dir, 0700); err != nil { + return xerrors.Errorf("failed to mkdir: %w", err) + } + + u, err := url.Parse(r.URL) + if err != nil { + return xerrors.Errorf("failed to parse the URL: %w", err) + } + + if u.Host == "github.com" { + u.Path = path.Join(u.Path, manifestFile) + } else { + u.Path = path.Join(u.Path, ".well-known", manifestFile) + } + + log.DebugContext(ctx, "Downloading the repository metadata...", log.String("url", u.String()), log.String("dst", r.dir)) + _, err = downloader.Download(ctx, u.String(), filepath.Join(r.dir, manifestFile), ".", downloader.Options{ + Insecure: opts.Insecure, + Auth: downloader.Auth{ + Username: r.Username, + Password: r.Password, + Token: r.Token, + }, + ClientMode: getter.ClientModeFile, + }) + if err != nil { + _ = os.RemoveAll(r.dir) + return xerrors.Errorf("failed to download the repository: %w", err) + } + return nil +} + +func (r *Repository) Update(ctx context.Context, opts Options) error { + manifest, err := r.Manifest(ctx, opts) + if err != nil { + return xerrors.Errorf("failed to get the repository metadata: %w", err) + } + + ver, err := r.selectSupportedVersion(manifest.Versions) + if err != nil { + return xerrors.Errorf("version %s not found", SchemaVersion) + } + + versionDir := filepath.Join(r.dir, SchemaVersion) + if !r.needUpdate(ctx, ver, versionDir) { + log.InfoContext(ctx, "No need to check repository updates", log.String("repo", r.Name)) + return nil + } + + log.InfoContext(ctx, "Updating repository...", log.String("repo", r.Name), log.String("url", r.URL)) + if err = r.download(ctx, ver, versionDir, opts); err != nil { + return xerrors.Errorf("failed to download the repository: %w", err) + } + return err +} + +func (r *Repository) needUpdate(ctx context.Context, ver Version, versionDir string) bool { + if !fsutils.DirExists(versionDir) { + return true + } + + m, err := r.cacheMetadata() + if err != nil { + log.DebugContext(ctx, "Failed to get repository cache metadata", log.String("repo", r.Name), log.Err(err)) + return true + } + + now := clock.Clock(ctx).Now() + log.DebugContext(ctx, "Checking if the repository needs to be updated...", log.String("repo", r.Name), + log.Time("last_update", m.UpdatedAt), log.Duration("update_interval", ver.UpdateInterval.Duration)) + if now.After(m.UpdatedAt.Add(ver.UpdateInterval.Duration)) { + return true + } + return false +} + +func (r *Repository) download(ctx context.Context, ver Version, dst string, opts Options) error { + if len(ver.Locations) == 0 { + return xerrors.Errorf("no locations found for version %s", ver.SpecVersion) + } + if err := os.MkdirAll(dst, 0700); err != nil { + return xerrors.Errorf("failed to mkdir: %w", err) + } + + m, err := r.cacheMetadata() + if err != nil { + return xerrors.Errorf("failed to get the repository cache metadata: %w", err) + } + etags := lo.Ternary(m.ETags == nil, make(map[string]string), m.ETags) + + var errs error + for _, loc := range ver.Locations { + logger := log.With(log.String("repo", r.Name)) + logger.DebugContext(ctx, "Downloading repository to cache dir...", log.String("url", loc.URL), + log.String("dir", dst), log.String("etag", etags[loc.URL])) + etag, err := downloader.Download(ctx, loc.URL, dst, ".", downloader.Options{ + Insecure: opts.Insecure, + Auth: downloader.Auth{ + Username: r.Username, + Password: r.Password, + Token: r.Token, + }, + ETag: etags[loc.URL], + }) + switch { + case errors.Is(err, downloader.ErrSkipDownload): + logger.DebugContext(ctx, "No updates in the repository", log.String("url", r.URL)) + etag = etags[loc.URL] // Keep the old ETag + // Update last updated time so that Trivy will not try to download the same URL soon + case err != nil: + errs = errors.Join(errs, err) + continue // Try the next location + default: + // Successfully downloaded + } + + // Update the cache metadata + etags[loc.URL] = etag + now := clock.Clock(ctx).Now() + if err = r.updateCacheMetadata(ctx, CacheMetadata{ + UpdatedAt: now, + ETags: etags, + }); err != nil { + return xerrors.Errorf("failed to update the repository cache metadata: %w", err) + } + logger.DebugContext(ctx, "Updated repository cache metadata", log.String("etag", etag), + log.Time("updated_at", now)) + return nil + } + if errs != nil { + return xerrors.Errorf("failed to download the repository: %w", errs) + } + return nil +} + +func (r *Repository) cacheMetadata() (CacheMetadata, error) { + filePath := filepath.Join(r.dir, cacheMetadataFile) + if !fsutils.FileExists(filePath) { + return CacheMetadata{}, nil + } + f, err := os.Open(filePath) + if err != nil { + return CacheMetadata{}, xerrors.Errorf("failed to open the file: %w", err) + } + defer f.Close() + + var metadata CacheMetadata + if err = json.NewDecoder(f).Decode(&metadata); err != nil { + return CacheMetadata{}, xerrors.Errorf("failed to decode the cache metadata: %w", err) + } + return metadata, nil +} + +func (r *Repository) selectSupportedVersion(versions []Version) (Version, error) { + for _, ver := range versions { + // Versions should exactly match until the spec version reaches 1.0. + // After reaching 1.0, we can select the latest version that has the same major version. + if ver.SpecVersion == SchemaVersion { + return ver, nil + } + } + return Version{}, xerrors.New("no supported version found") +} + +func (r *Repository) updateCacheMetadata(ctx context.Context, metadata CacheMetadata) error { + filePath := filepath.Join(r.dir, cacheMetadataFile) + log.DebugContext(ctx, "Updating repository cache metadata...", log.FilePath(filePath)) + + f, err := os.Create(filePath) + if err != nil { + return xerrors.Errorf("failed to create the file: %w", err) + } + defer f.Close() + + if err = json.NewEncoder(f).Encode(metadata); err != nil { + return xerrors.Errorf("failed to encode the metadata: %w", err) + } + return nil +} diff --git a/pkg/vex/repo/repo_test.go b/pkg/vex/repo/repo_test.go new file mode 100644 index 000000000000..0118b7391523 --- /dev/null +++ b/pkg/vex/repo/repo_test.go @@ -0,0 +1,366 @@ +package repo_test + +import ( + "archive/zip" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/clock" + "github.com/aquasecurity/trivy/pkg/vex/repo" +) + +var manifest = repo.Manifest{ + Name: "test-repo", + Description: "test repository", + Versions: []repo.Version{ + { + SpecVersion: "0.1", + Locations: []repo.Location{ + { + URL: "https://localhost", + }, + }, + UpdateInterval: repo.Duration{Duration: time.Hour * 24}, + }, + }, +} + +func TestRepository_Manifest(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/.well-known/vex-repository.json" { + err := json.NewEncoder(w).Encode(manifest) + assert.NoError(t, err) + } + http.Error(w, "error", http.StatusInternalServerError) + })) + t.Cleanup(ts.Close) + + tests := []struct { + name string + setup func(*testing.T, string, *repo.Repository) + want repo.Manifest + wantErr string + }{ + { + name: "local manifest exists", + setup: func(t *testing.T, dir string, _ *repo.Repository) { + manifestFile := filepath.Join(dir, "vex", "repositories", "test-repo", "vex-repository.json") + testutil.MustWriteJSON(t, manifestFile, manifest) + }, + want: manifest, + }, + { + name: "fetch from remote", + setup: func(t *testing.T, dir string, r *repo.Repository) { + r.URL = ts.URL + }, + want: manifest, + }, + { + name: "http error", + setup: func(t *testing.T, dir string, r *repo.Repository) { + r.URL = ts.URL + "/error" + }, + wantErr: "failed to download the repository metadata", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir, m := setupManager(t) + conf, err := m.Config(context.Background()) + require.NoError(t, err) + + r := conf.Repositories[0] + tt.setup(t, tempDir, &r) + + got, err := r.Manifest(context.Background(), repo.Options{}) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestRepository_Index(t *testing.T) { + tests := []struct { + name string + setup func(*testing.T, string, *repo.Repository) + want repo.Index + wantErr string + }{ + { + name: "local index exists", + setup: func(t *testing.T, cacheDir string, r *repo.Repository) { + indexData := repo.RawIndex{ + UpdatedAt: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + Packages: []repo.PackageEntry{ + { + ID: "pkg1", + Location: "location1", + Format: "format1", + }, + { + ID: "pkg2", + Location: "location2", + Format: "format2", + }, + }, + } + + indexPath := filepath.Join(cacheDir, "vex", "repositories", r.Name, "0.1", "index.json") + testutil.MustWriteJSON(t, indexPath, indexData) + }, + want: repo.Index{ + UpdatedAt: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + Packages: map[string]repo.PackageEntry{ + "pkg1": { + ID: "pkg1", + Location: "location1", + Format: "format1", + }, + "pkg2": { + ID: "pkg2", + Location: "location2", + Format: "format2", + }, + }, + }, + }, + { + name: "index file not found", + setup: func(*testing.T, string, *repo.Repository) {}, + wantErr: "failed to open the file", + }, + { + name: "invalid JSON in index file", + setup: func(t *testing.T, cacheDir string, r *repo.Repository) { + indexPath := filepath.Join(cacheDir, "vex", "repositories", r.Name, "0.1", "index.json") + testutil.MustWriteFile(t, indexPath, []byte("invalid JSON")) + }, + wantErr: "failed to decode the index", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir, m := setupManager(t) + conf, err := m.Config(context.Background()) + require.NoError(t, err) + + r := conf.Repositories[0] + tt.setup(t, tempDir, &r) + + got, err := r.Index(context.Background()) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + tt.want.Path = filepath.Join(tempDir, "vex", "repositories", r.Name, "0.1", "index.json") + assert.Equal(t, tt.want, got) + }) + } +} + +func TestRepository_Update(t *testing.T) { + ts := setUpRepository(t) + defer ts.Close() + + tests := []struct { + name string + setup func(*testing.T, string, *repo.Repository) + clockTime time.Time + wantErr string + wantCache repo.CacheMetadata + }{ + { + name: "successful update", + setup: func(t *testing.T, cacheDir string, r *repo.Repository) { + setUpManifest(t, cacheDir, ts.URL+"/archive.zip") + }, + clockTime: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + wantCache: repo.CacheMetadata{ + UpdatedAt: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + ETags: map[string]string{ts.URL + "/archive.zip": "new-etag"}, + }, + }, + { + name: "no update needed (within update interval)", + setup: func(t *testing.T, cacheDir string, r *repo.Repository) { + setUpManifest(t, cacheDir, "") // No location as the test server is not used + + repoDir := filepath.Join(cacheDir, "vex", "repositories", r.Name) + testutil.MustMkdirAll(t, filepath.Join(repoDir, "0.1")) + + cacheMetadata := repo.CacheMetadata{ + UpdatedAt: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + ETags: map[string]string{ts.URL + "/archive.zip": "current-etag"}, + } + testutil.MustWriteJSON(t, filepath.Join(repoDir, "cache.json"), cacheMetadata) + }, + clockTime: time.Date(2023, 1, 1, 1, 30, 0, 0, time.UTC), + wantCache: repo.CacheMetadata{ + UpdatedAt: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + ETags: map[string]string{ts.URL + "/archive.zip": "current-etag"}, + }, + }, + { + name: "update needed (update interval passed)", + setup: func(t *testing.T, cacheDir string, r *repo.Repository) { + setUpManifest(t, cacheDir, ts.URL+"/archive.zip") + + repoDir := filepath.Join(cacheDir, "vex", "repositories", r.Name) + testutil.MustMkdirAll(t, filepath.Join(repoDir, "0.1")) + + cacheMetadata := repo.CacheMetadata{ + UpdatedAt: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + ETags: map[string]string{ts.URL + "/archive.zip": "old-etag"}, + } + testutil.MustWriteJSON(t, filepath.Join(repoDir, "cache.json"), cacheMetadata) + }, + clockTime: time.Date(2023, 1, 2, 3, 0, 0, 0, time.UTC), + wantCache: repo.CacheMetadata{ + UpdatedAt: time.Date(2023, 1, 2, 3, 0, 0, 0, time.UTC), + ETags: map[string]string{ts.URL + "/archive.zip": "new-etag"}, + }, + }, + { + name: "no update needed (304 Not Modified)", + setup: func(t *testing.T, cacheDir string, r *repo.Repository) { + setUpManifest(t, cacheDir, ts.URL+"/archive.zip") + + repoDir := filepath.Join(cacheDir, "vex", "repositories", r.Name) + testutil.MustMkdirAll(t, filepath.Join(repoDir, "0.1")) + + cacheMetadata := repo.CacheMetadata{ + UpdatedAt: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + ETags: map[string]string{ts.URL + "/archive.zip": "current-etag"}, + } + testutil.MustWriteJSON(t, filepath.Join(repoDir, "cache.json"), cacheMetadata) + }, + clockTime: time.Date(2023, 1, 2, 3, 0, 0, 0, time.UTC), + wantCache: repo.CacheMetadata{ + UpdatedAt: time.Date(2023, 1, 2, 3, 0, 0, 0, time.UTC), + ETags: map[string]string{ts.URL + "/archive.zip": "current-etag"}, + }, + }, + { + name: "update with no existing cache.json", + setup: func(t *testing.T, cacheDir string, r *repo.Repository) { + setUpManifest(t, cacheDir, ts.URL+"/archive.zip") + + repoDir := filepath.Join(cacheDir, "vex", "repositories", r.Name) + testutil.MustMkdirAll(t, filepath.Join(repoDir, "0.1")) + }, + clockTime: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + wantCache: repo.CacheMetadata{ + UpdatedAt: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + ETags: map[string]string{ts.URL + "/archive.zip": "new-etag"}, + }, + }, + { + name: "manifest not found", + setup: func(*testing.T, string, *repo.Repository) {}, + clockTime: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + wantErr: "failed to get the repository metadata", + }, + { + name: "download error", + setup: func(t *testing.T, cacheDir string, r *repo.Repository) { + setUpManifest(t, cacheDir, ts.URL+"/error") + + repoDir := filepath.Join(cacheDir, "vex", "repositories", r.Name) + testutil.MustMkdirAll(t, filepath.Join(repoDir, "0.1")) + }, + clockTime: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + wantErr: "failed to download the repository", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir, m := setupManager(t) + conf, err := m.Config(context.Background()) + require.NoError(t, err) + + r := conf.Repositories[0] + r.URL = ts.URL + "/vex-repository.json" + tt.setup(t, tempDir, &r) + + ctx := clock.With(context.Background(), tt.clockTime) + err = r.Update(ctx, repo.Options{}) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + require.NoError(t, err) + + cacheFile := filepath.Join(tempDir, "vex", "repositories", r.Name, "cache.json") + var gotCache repo.CacheMetadata + testutil.MustReadJSON(t, cacheFile, &gotCache) + assert.Equal(t, tt.wantCache, gotCache) + }) + } +} + +func setupManager(t *testing.T) (string, *repo.Manager) { + tempDir := t.TempDir() + t.Setenv("XDG_DATA_HOME", "testdata") + return tempDir, repo.NewManager(tempDir) +} + +func setUpManifest(t *testing.T, dir, url string) { + manifest := repo.Manifest{ + Name: "test-repo", + Description: "test repository", + Versions: []repo.Version{ + { + SpecVersion: "0.1", + Locations: []repo.Location{ + { + URL: url, + }, + }, + UpdateInterval: repo.Duration{Duration: time.Hour * 24}, + }, + }, + } + manifestPath := filepath.Join(dir, "vex", "repositories", "test-repo", "vex-repository.json") + testutil.MustWriteJSON(t, manifestPath, manifest) +} + +func setUpRepository(t *testing.T) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/archive.zip": + if r.Header.Get("If-None-Match") == "current-etag" { + w.WriteHeader(http.StatusNotModified) + return + } + w.Header().Set("Content-Type", "application/zip") + w.Header().Set("ETag", "new-etag") + zw := zip.NewWriter(w) + assert.NoError(t, zw.AddFS(os.DirFS("testdata/test-repo"))) + assert.NoError(t, zw.Close()) + case "/error": + w.WriteHeader(http.StatusInternalServerError) + default: + w.WriteHeader(http.StatusNotFound) + } + })) +} diff --git a/pkg/vex/repo/testdata/.trivy/vex/repository.yaml b/pkg/vex/repo/testdata/.trivy/vex/repository.yaml new file mode 100644 index 000000000000..92f1c4b7538d --- /dev/null +++ b/pkg/vex/repo/testdata/.trivy/vex/repository.yaml @@ -0,0 +1,4 @@ +repositories: + - name: "test-repo" + url: "https://localhost" + enabled: true \ No newline at end of file diff --git a/pkg/vex/repo/testdata/test-repo/index.json b/pkg/vex/repo/testdata/test-repo/index.json new file mode 100644 index 000000000000..a2b346441c8a --- /dev/null +++ b/pkg/vex/repo/testdata/test-repo/index.json @@ -0,0 +1,9 @@ +{ + "version": 1, + "packages": [ + { + "ID": "pkg:golang/github.com/aquasecurity/trivy", + "Location": "test" + } + ] +} \ No newline at end of file diff --git a/pkg/vex/repo/testdata/test-repo/vex-repository.json b/pkg/vex/repo/testdata/test-repo/vex-repository.json new file mode 100644 index 000000000000..132e960e553d --- /dev/null +++ b/pkg/vex/repo/testdata/test-repo/vex-repository.json @@ -0,0 +1,16 @@ +{ + "name": "Test Repository", + "description": "Test Repository", + "versions": { + "v0": { + "spec_version": "v0.1", + "locations": [ + { + "url": "Must be filled in tests" + } + ], + "update_interval": "24h" + } + }, + "latest_version": "v0" +} \ No newline at end of file diff --git a/pkg/vex/repo_test.go b/pkg/vex/repo_test.go new file mode 100644 index 000000000000..24d8b1334177 --- /dev/null +++ b/pkg/vex/repo_test.go @@ -0,0 +1,113 @@ +package vex_test + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/vex" +) + +var bashComponent = core.Component{ + Name: bashPackage.Name, + Version: bashPackage.Version, + PkgIdentifier: bashPackage.Identifier, +} + +func TestRepositorySet_NotAffected(t *testing.T) { + tests := []struct { + name string + cacheDir string + configContent string + vuln types.DetectedVulnerability + product core.Component + wantModified types.ModifiedFinding + wantNotAffected bool + }{ + { + name: "single repository - not affected", + cacheDir: "testdata/single-repo", + configContent: ` +repositories: + - name: default + url: https://example.com/vex/default + enabled: true +`, + vuln: vuln3, + product: bashComponent, + wantModified: types.ModifiedFinding{ + Type: types.FindingTypeVulnerability, + Finding: vuln3, + Status: types.FindingStatusNotAffected, + Statement: "vulnerable_code_not_in_execute_path", + Source: "VEX Repository: default (https://example.com/vex/default)", + }, + wantNotAffected: true, + }, + { + name: "multiple repositories - high priority affected", + cacheDir: "testdata/multi-repos", + configContent: ` +repositories: + - name: high-priority + url: https://example.com/vex/high-priority + enabled: true + - name: default + url: https://example.com/vex/default + enabled: true +`, + vuln: vuln3, + product: bashComponent, + wantNotAffected: false, + }, + { + name: "no matching VEX data", + cacheDir: "testdata/single-repo", + configContent: ` +repositories: + - name: default + url: https://example.com/vex/default + enabled: true +`, + vuln: vuln4, + product: bashComponent, + wantNotAffected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a temporary directory for each test + tmpDir := t.TempDir() + + // Set XDG_DATA_HOME to the temporary directory + t.Setenv("XDG_DATA_HOME", tmpDir) + + // Create the vex directory in the temporary directory + vexDir := filepath.Join(tmpDir, ".trivy", "vex") + err := os.MkdirAll(vexDir, 0755) + require.NoError(t, err) + + // Write the config file + configPath := filepath.Join(vexDir, "repository.yaml") + err = os.WriteFile(configPath, []byte(tt.configContent), 0644) + require.NoError(t, err) + + ctx := context.Background() + rs, err := vex.NewRepositorySet(ctx, tt.cacheDir) + require.NoError(t, err) + + modified, notAffected := rs.NotAffected(tt.vuln, &tt.product, nil) + assert.Equal(t, tt.wantNotAffected, notAffected) + if tt.wantNotAffected { + assert.Equal(t, tt.wantModified, modified) + } + }) + } +} diff --git a/pkg/vex/testdata/csaf-relationships.json b/pkg/vex/testdata/csaf-relationships.json index ab58d2bfe492..2e823d17a2c6 100644 --- a/pkg/vex/testdata/csaf-relationships.json +++ b/pkg/vex/testdata/csaf-relationships.json @@ -37,22 +37,22 @@ "branches": [ { "category": "product_version", - "name": "2.9.3-2", + "name": "2.0.0", "product": { - "name": "Argo CD 2.9.3-2", - "product_id": "argo-cd-2.9.3-2-amd64-debian-12", + "name": "go-direct1 v2.0.0", + "product_id": "go-direct1-v2.0.0", "product_identification_helper": { - "purl": "pkg:bitnami/argo-cd@2.9.3-2?arch=amd64\u0026distro=debian-12" + "purl": "pkg:golang/github.com/aquasecurity/go-direct1@2.0.0" } } } ], "category": "product_name", - "name": "Argo CD" + "name": "go-direct1" } ], "category": "vendor", - "name": "VMWare, Inc." + "name": "bar" }, { "branches": [ @@ -60,45 +60,45 @@ "branches": [ { "category": "product_version", - "name": "v0.24.2", + "name": "v4.0.0", "product": { - "name": "client-go v0.24.2", - "product_id": "client-go-v0.24.2", + "name": "go-transitive v4.0.0", + "product_id": "go-transitive-v4.0.0", "product_identification_helper": { - "purl": "pkg:golang/k8s.io/client-go@0.24.2" + "purl": "pkg:golang/github.com/aquasecurity/go-transitive@4.0.0" } } } ], "category": "product_name", - "name": "client-go" + "name": "go-transitive" } ], "category": "vendor", - "name": "k8s.io" + "name": "foo" } ], "relationships": [ { - "product_reference": "client-go-v0.24.2", + "product_reference": "go-transitive-v4.0.0", "category": "default_component_of", - "relates_to_product_reference": "argo-cd-2.9.3-2-amd64-debian-12", + "relates_to_product_reference": "go-direct1-v2.0.0", "full_product_name": { - "product_id": "argo-cd-2.9.3-2-amd64-debian-12-client-go", - "name": "Argo CD uses kubernetes golang library" + "product_id": "go-direct1-v2.0.0-go-transitive-v4.0.0", + "name": "go-direct1 uses go-transitive" } } ] }, "vulnerabilities": [ { - "cve": "CVE-2023-2727", + "cve": "CVE-2024-0001", "flags": [ { "date": "2024-01-04T17:17:25+01:00", "label": "vulnerable_code_cannot_be_controlled_by_adversary", "product_ids": [ - "argo-cd-2.9.3-2-amd64-debian-12-client-go" + "go-direct1-v2.0.0-go-transitive-v4.0.0" ] } ], @@ -111,14 +111,14 @@ ], "product_status": { "known_not_affected": [ - "argo-cd-2.9.3-2-amd64-debian-12-client-go" + "go-direct1-v2.0.0-go-transitive-v4.0.0" ] }, "threats": [ { "category": "impact", "date": "2024-01-04T17:17:25+01:00", - "details": "The asset uses the component as a dependency in the code, but the vulnerability only affects Kubernetes clusters https://github.com/kubernetes/kubernetes/issues/118640" + "details": "vulnerable_code_not_in_execute_path" } ] } diff --git a/pkg/vex/testdata/csaf.json b/pkg/vex/testdata/csaf.json index 28389af24fcc..70afefe70205 100644 --- a/pkg/vex/testdata/csaf.json +++ b/pkg/vex/testdata/csaf.json @@ -45,28 +45,28 @@ "branches": [ { "category": "product_version", - "name": "v0.24.2", + "name": "v4.0.0", "product": { - "name": "client-go v0.24.2", - "product_id": "client-go-v0.24.2", + "name": "go-transitive v4.0.0", + "product_id": "go-transitive-v4.0.0", "product_identification_helper": { - "purl": "pkg:golang/k8s.io/client-go@0.24.2" + "purl": "pkg:golang/github.com/aquasecurity/go-transitive@4.0.0" } } } ], "category": "product_name", - "name": "client-go" + "name": "go-transitive" } ], "category": "vendor", - "name": "k8s.io" + "name": "foo" } ] }, "vulnerabilities": [ { - "cve": "CVE-2023-2727", + "cve": "CVE-2024-0001", "notes": [ { "category": "description", @@ -76,13 +76,13 @@ ], "product_status": { "known_not_affected": [ - "client-go-v0.24.2" + "go-transitive-v4.0.0" ] }, "threats": [ { "category": "impact", - "details": "The asset uses the component as a dependency in the code, but the vulnerability only affects Kubernetes clusters https://github.com/kubernetes/kubernetes/issues/118640" + "details": "vulnerable_code_not_in_execute_path" } ] } diff --git a/pkg/vex/testdata/cyclonedx.json b/pkg/vex/testdata/cyclonedx.json index ccc4396981b5..fb6463600c65 100644 --- a/pkg/vex/testdata/cyclonedx.json +++ b/pkg/vex/testdata/cyclonedx.json @@ -4,10 +4,10 @@ "version": 1, "vulnerabilities": [ { - "id": "CVE-2018-7489", + "id": "CVE-2021-44228", "source": { "name": "NVD", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-9997" + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-44228" }, "analysis": { "state": "not_affected", @@ -15,7 +15,7 @@ }, "affects": [ { - "ref": "urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.8.0" + "ref": "urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:maven/org.springframework.boot/spring-boot@2.6.0" } ] }, diff --git a/pkg/vex/testdata/multi-repos/vex/repositories/default/0.1/bash-vex.json b/pkg/vex/testdata/multi-repos/vex/repositories/default/0.1/bash-vex.json new file mode 100644 index 000000000000..c06426bac376 --- /dev/null +++ b/pkg/vex/testdata/multi-repos/vex/repositories/default/0.1/bash-vex.json @@ -0,0 +1,22 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "https://openvex.dev/docs/public/vex-5d6e2706", + "author": "Example Author", + "role": "Document Creator", + "timestamp": "2023-07-01T00:00:00Z", + "version": 1, + "statements": [ + { + "vulnerability": { + "@id": "CVE-2022-3715" + }, + "products": [ + { + "@id": "pkg:deb/debian/bash@5.3" + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } + ] +} \ No newline at end of file diff --git a/pkg/vex/testdata/multi-repos/vex/repositories/default/0.1/index.json b/pkg/vex/testdata/multi-repos/vex/repositories/default/0.1/index.json new file mode 100644 index 000000000000..4a746cca5382 --- /dev/null +++ b/pkg/vex/testdata/multi-repos/vex/repositories/default/0.1/index.json @@ -0,0 +1,10 @@ +{ + "updated_at": "2024-07-01T00:00:00Z", + "packages": [ + { + "id": "pkg:deb/debian/bash", + "location": "bash-vex.json", + "format": "openvex" + } + ] +} \ No newline at end of file diff --git a/pkg/vex/testdata/multi-repos/vex/repositories/high-priority/0.1/bash-vex.json b/pkg/vex/testdata/multi-repos/vex/repositories/high-priority/0.1/bash-vex.json new file mode 100644 index 000000000000..300ecc8e7c3a --- /dev/null +++ b/pkg/vex/testdata/multi-repos/vex/repositories/high-priority/0.1/bash-vex.json @@ -0,0 +1,21 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "https://openvex.dev/docs/public/vex-5d6e2706", + "author": "Example Author", + "role": "Document Creator", + "timestamp": "2023-07-01T00:00:00Z", + "version": 1, + "statements": [ + { + "vulnerability": { + "@id": "CVE-2022-3715" + }, + "products": [ + { + "@id": "pkg:deb/debian/bash@5.3" + } + ], + "status": "affected" + } + ] +} \ No newline at end of file diff --git a/pkg/vex/testdata/multi-repos/vex/repositories/high-priority/0.1/index.json b/pkg/vex/testdata/multi-repos/vex/repositories/high-priority/0.1/index.json new file mode 100644 index 000000000000..4a746cca5382 --- /dev/null +++ b/pkg/vex/testdata/multi-repos/vex/repositories/high-priority/0.1/index.json @@ -0,0 +1,10 @@ +{ + "updated_at": "2024-07-01T00:00:00Z", + "packages": [ + { + "id": "pkg:deb/debian/bash", + "location": "bash-vex.json", + "format": "openvex" + } + ] +} \ No newline at end of file diff --git a/pkg/vex/testdata/openvex-oci-mismatch.json b/pkg/vex/testdata/openvex-oci-mismatch.json new file mode 100644 index 000000000000..e022d4854abb --- /dev/null +++ b/pkg/vex/testdata/openvex-oci-mismatch.json @@ -0,0 +1,26 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "author": "Aqua Security", + "role": "Project Release Bot", + "timestamp": "2023-01-16T19:07:16.853479631-06:00", + "version": 1, + "statements": [ + { + "vulnerability": { + "name": "CVE-2022-3715" + }, + "products": [ + { + "@id": "pkg:oci/mismatch", + "subcomponents": [ + { + "@id": "pkg:deb/debian/bash" + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } + ] +} diff --git a/pkg/vex/testdata/single-repo/vex/repositories/default/0.1/bash-vex.json b/pkg/vex/testdata/single-repo/vex/repositories/default/0.1/bash-vex.json new file mode 100644 index 000000000000..c06426bac376 --- /dev/null +++ b/pkg/vex/testdata/single-repo/vex/repositories/default/0.1/bash-vex.json @@ -0,0 +1,22 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "https://openvex.dev/docs/public/vex-5d6e2706", + "author": "Example Author", + "role": "Document Creator", + "timestamp": "2023-07-01T00:00:00Z", + "version": 1, + "statements": [ + { + "vulnerability": { + "@id": "CVE-2022-3715" + }, + "products": [ + { + "@id": "pkg:deb/debian/bash@5.3" + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path" + } + ] +} \ No newline at end of file diff --git a/pkg/vex/testdata/single-repo/vex/repositories/default/0.1/index.json b/pkg/vex/testdata/single-repo/vex/repositories/default/0.1/index.json new file mode 100644 index 000000000000..4a746cca5382 --- /dev/null +++ b/pkg/vex/testdata/single-repo/vex/repositories/default/0.1/index.json @@ -0,0 +1,10 @@ +{ + "updated_at": "2024-07-01T00:00:00Z", + "packages": [ + { + "id": "pkg:deb/debian/bash", + "location": "bash-vex.json", + "format": "openvex" + } + ] +} \ No newline at end of file diff --git a/pkg/vex/testdata/single-repo/vex/repositories/default/vex-repository.json b/pkg/vex/testdata/single-repo/vex/repositories/default/vex-repository.json new file mode 100644 index 000000000000..e064c0e1b3cf --- /dev/null +++ b/pkg/vex/testdata/single-repo/vex/repositories/default/vex-repository.json @@ -0,0 +1,15 @@ +{ + "name": "Test VEX Repository", + "description": "VEX Repository for Testing", + "versions": [ + { + "spec_version": "0.1", + "locations": [ + { + "url": "never used" + } + ], + "update_interval": "24h" + } + ] +} \ No newline at end of file diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index f4cf265997a0..de93f542ca00 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -1,114 +1,127 @@ package vex import ( - "encoding/json" - "io" - "os" + "context" + "errors" - "github.com/csaf-poc/csaf_distribution/v3/csaf" - "github.com/hashicorp/go-multierror" - openvex "github.com/openvex/go-vex/pkg/vex" "github.com/samber/lo" - "github.com/sirupsen/logrus" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/sbom" "github.com/aquasecurity/trivy/pkg/sbom/core" - "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/uuid" ) +const ( + TypeFile SourceType = "file" + TypeRepository SourceType = "repo" +) + // VEX represents Vulnerability Exploitability eXchange. It abstracts multiple VEX formats. // Note: This is in the experimental stage and does not yet support many specifications. // The implementation may change significantly. type VEX interface { - Filter(*types.Result, *core.BOM) + NotAffected(vuln types.DetectedVulnerability, product, subComponent *core.Component) (types.ModifiedFinding, bool) } -func New(filePath string, report types.Report) (VEX, error) { - if filePath == "" { - return nil, nil - } - f, err := os.Open(filePath) - if err != nil { - return nil, xerrors.Errorf("file open error: %w", err) - } - defer f.Close() - - var errs error - // Try CycloneDX JSON - if ok, err := sbom.IsCycloneDXJSON(f); err != nil { - errs = multierror.Append(errs, err) - } else if ok { - return decodeCycloneDXJSON(f, report) - } +type Client struct { + VEXes []VEX +} - // Try OpenVEX - if v, err := decodeOpenVEX(f); err != nil { - errs = multierror.Append(errs, err) - } else if v != nil { - return v, nil - } +type Options struct { + CacheDir string + Sources []Source +} - // Try CSAF - if v, err := decodeCSAF(f); err != nil { - errs = multierror.Append(errs, err) - } else if v != nil { - return v, nil - } +type SourceType string - return nil, xerrors.Errorf("unable to load VEX: %w", errs) +type Source struct { + Type SourceType + FilePath string // Used only for the file type } -func decodeCycloneDXJSON(r io.ReadSeeker, report types.Report) (VEX, error) { - if _, err := r.Seek(0, io.SeekStart); err != nil { - return nil, xerrors.Errorf("seek error: %w", err) - } - vex, err := cyclonedx.DecodeJSON(r) - if err != nil { - return nil, xerrors.Errorf("json decode error: %w", err) - } - if report.ArtifactType != artifact.TypeCycloneDX { - return nil, xerrors.New("CycloneDX VEX can be used with CycloneDX SBOM") +func NewSource(src string) Source { + switch src { + case "repository", "repo": + return Source{Type: TypeRepository} + default: + return Source{ + Type: TypeFile, + FilePath: src, + } } - return newCycloneDX(report.BOM, vex), nil } -func decodeOpenVEX(r io.ReadSeeker) (VEX, error) { - // openvex/go-vex outputs log messages by default - logrus.SetOutput(io.Discard) +type NotAffected func(vuln types.DetectedVulnerability, product, subComponent *core.Component) (types.ModifiedFinding, bool) - if _, err := r.Seek(0, io.SeekStart); err != nil { - return nil, xerrors.Errorf("seek error: %w", err) +// Filter determines whether a detected vulnerability should be filtered out based on the provided VEX document. +// If the VEX document is passed and the vulnerability is either not affected or fixed according to the VEX statement, +// the vulnerability is filtered out. +func Filter(ctx context.Context, report *types.Report, opts Options) error { + ctx = log.WithContextPrefix(ctx, "vex") + client, err := New(ctx, report, opts) + if err != nil { + return xerrors.Errorf("VEX error: %w", err) + } else if client == nil { + return nil } - var openVEX openvex.VEX - if err := json.NewDecoder(r).Decode(&openVEX); err != nil { - return nil, err + + bom, err := sbomio.NewEncoder(core.Options{Parents: true}).Encode(*report) + if err != nil { + return xerrors.Errorf("unable to encode the SBOM: %w", err) } - if openVEX.Context == "" { - return nil, nil + + for i, result := range report.Results { + if len(result.Vulnerabilities) == 0 { + continue + } + filterVulnerabilities(&report.Results[i], bom, client.NotAffected) } - return newOpenVEX(openVEX), nil + return nil } -func decodeCSAF(r io.ReadSeeker) (VEX, error) { - if _, err := r.Seek(0, io.SeekStart); err != nil { - return nil, xerrors.Errorf("seek error: %w", err) - } - var adv csaf.Advisory - if err := json.NewDecoder(r).Decode(&adv); err != nil { - return nil, err +func New(ctx context.Context, report *types.Report, opts Options) (*Client, error) { + var vexes []VEX + for _, src := range opts.Sources { + var v VEX + var err error + switch src.Type { + case TypeFile: + v, err = NewDocument(src.FilePath, report) + if err != nil { + return nil, xerrors.Errorf("unable to load VEX: %w", err) + } + case TypeRepository: + v, err = NewRepositorySet(ctx, opts.CacheDir) + if errors.Is(err, errNoRepository) { + continue + } else if err != nil { + return nil, xerrors.Errorf("failed to create a vex repository set: %w", err) + } + default: + log.Warn("Unsupported VEX source", log.String("type", string(src.Type))) + continue + } + vexes = append(vexes, v) } - if adv.Vulnerabilities == nil { + + if len(vexes) == 0 { + log.DebugContext(ctx, "VEX filtering is disabled") return nil, nil } - return newCSAF(adv), nil + return &Client{VEXes: vexes}, nil } -type NotAffected func(vuln types.DetectedVulnerability, product, subComponent *core.Component) (types.ModifiedFinding, bool) +func (c *Client) NotAffected(vuln types.DetectedVulnerability, product, subComponent *core.Component) (types.ModifiedFinding, bool) { + for _, v := range c.VEXes { + if m, notAffected := v.NotAffected(vuln, product, subComponent); notAffected { + return m, true + } + } + return types.ModifiedFinding{}, false +} func filterVulnerabilities(result *types.Result, bom *core.BOM, fn NotAffected) { components := lo.MapEntries(bom.Components(), func(id uuid.UUID, component *core.Component) (string, *core.Component) { @@ -122,16 +135,20 @@ func filterVulnerabilities(result *types.Result, bom *core.BOM, fn NotAffected) return true // Should never reach here } + var modified types.ModifiedFinding notAffectedFn := func(c, leaf *core.Component) bool { - modified, notAffected := fn(vuln, c, leaf) + m, notAffected := fn(vuln, c, leaf) if notAffected { - result.ModifiedFindings = append(result.ModifiedFindings, modified) - return true + modified = m // Take the last modified finding if multiple VEX states "not affected" } - return false + return notAffected } - return reachRoot(c, bom.Components(), bom.Parents(), notAffectedFn) + if !reachRoot(c, bom.Components(), bom.Parents(), notAffectedFn) { + result.ModifiedFindings = append(result.ModifiedFindings, modified) + return false + } + return true }) } diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go index d951b1795908..c76a9961643c 100644 --- a/pkg/vex/vex_test.go +++ b/pkg/vex/vex_test.go @@ -1,9 +1,12 @@ package vex_test import ( + "context" "os" + "path/filepath" "testing" + "github.com/google/go-containerregistry/pkg/v1" "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -16,41 +19,19 @@ import ( "github.com/aquasecurity/trivy/pkg/vex" ) +const ( + vulnerableCodeNotInExecutePath = "vulnerable_code_not_in_execute_path" + codeNotReachable = "code_not_reachable" +) + var ( - ociComponent = core.Component{ - Root: true, - Type: core.TypeContainerImage, - Name: "debian:12", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeOCI, - Name: "debian", - Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", - Qualifiers: packageurl.Qualifiers{ - { - Key: "tag", - Value: "12", - }, - { - Key: "repository_url", - Value: "docker.io/library/debian", - }, - }, - }, - }, - } - fsComponent = core.Component{ - Root: true, - Type: core.TypeFilesystem, - Name: ".", - } - springComponent = core.Component{ - Type: core.TypeLibrary, - Group: "org.springframework.boot", - Name: "spring-boot", + springPackage = ftypes.Package{ + ID: "org.springframework.boot:spring-boot:2.6.0", + Name: "org.springframework.boot:spring-boot", Version: "2.6.0", - PkgIdentifier: ftypes.PkgIdentifier{ - UID: "01", + Identifier: ftypes.PkgIdentifier{ + UID: "01", + BOMRef: "pkg:maven/org.springframework.boot/spring-boot@2.6.0", PURL: &packageurl.PackageURL{ Type: packageurl.TypeMaven, Namespace: "org.springframework.boot", @@ -59,25 +40,26 @@ var ( }, }, } - bashComponent = core.Component{ - Type: core.TypeLibrary, + bashPackage = ftypes.Package{ + ID: "bash@5.3", Name: "bash", Version: "5.3", - PkgIdentifier: ftypes.PkgIdentifier{ + Identifier: ftypes.PkgIdentifier{ UID: "02", PURL: &packageurl.PackageURL{ Type: packageurl.TypeDebian, Namespace: "debian", Name: "bash", - Version: "5.2.15", + Version: "5.3", }, }, } - goModuleComponent = core.Component{ - Type: core.TypeLibrary, - Name: "github.com/aquasecurity/go-module", - Version: "1.0.0", - PkgIdentifier: ftypes.PkgIdentifier{ + goModulePackage = ftypes.Package{ + ID: "github.com/aquasecurity/go-module@1.0.0", + Name: "github.com/aquasecurity/go-module", + Version: "1.0.0", + Relationship: ftypes.RelationshipRoot, + Identifier: ftypes.PkgIdentifier{ UID: "03", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, @@ -87,11 +69,12 @@ var ( }, }, } - goDirectComponent1 = core.Component{ - Type: core.TypeLibrary, - Name: "github.com/aquasecurity/go-direct1", - Version: "2.0.0", - PkgIdentifier: ftypes.PkgIdentifier{ + goDirectPackage1 = ftypes.Package{ + ID: "github.com/aquasecurity/go-direct1@2.0.0", + Name: "github.com/aquasecurity/go-direct1", + Version: "2.0.0", + Relationship: ftypes.RelationshipDirect, + Identifier: ftypes.PkgIdentifier{ UID: "04", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, @@ -101,11 +84,12 @@ var ( }, }, } - goDirectComponent2 = core.Component{ - Type: core.TypeLibrary, - Name: "github.com/aquasecurity/go-direct2", - Version: "3.0.0", - PkgIdentifier: ftypes.PkgIdentifier{ + goDirectPackage2 = ftypes.Package{ + ID: "github.com/aquasecurity/go-direct2@3.0.0", + Name: "github.com/aquasecurity/go-direct2", + Version: "3.0.0", + Relationship: ftypes.RelationshipDirect, + Identifier: ftypes.PkgIdentifier{ UID: "05", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, @@ -115,11 +99,12 @@ var ( }, }, } - goTransitiveComponent = core.Component{ - Type: core.TypeLibrary, - Name: "github.com/aquasecurity/go-transitive", - Version: "4.0.0", - PkgIdentifier: ftypes.PkgIdentifier{ + goTransitivePackage = ftypes.Package{ + ID: "github.com/aquasecurity/go-transitive@4.0.0", + Name: "github.com/aquasecurity/go-transitive", + Version: "4.0.0", + Relationship: ftypes.RelationshipIndirect, + Identifier: ftypes.PkgIdentifier{ UID: "06", PURL: &packageurl.PackageURL{ Type: packageurl.TypeGolang, @@ -129,87 +114,35 @@ var ( }, }, } - argoComponent = core.Component{ - Type: core.TypeLibrary, - Name: "argo-cd", - Version: "2.9.3-2", - PkgIdentifier: ftypes.PkgIdentifier{ - UID: "07", - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeBitnami, - Name: "argo-cd", - Version: "2.9.3-2", - Qualifiers: packageurl.Qualifiers{ - { - Key: "arch", - Value: "amd64", - }, - { - Key: "distro", - Value: "debian-12", - }, - }, - }, - }, - } - clientGoComponent = core.Component{ - Type: core.TypeLibrary, - Name: "k8s.io/client-go", - Version: "0.24.2", - PkgIdentifier: ftypes.PkgIdentifier{ - UID: "08", - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeGolang, - Namespace: "k8s.io", - Name: "client-go", - Version: "0.24.2", - }, - }, - } vuln1 = types.DetectedVulnerability{ VulnerabilityID: "CVE-2021-44228", - PkgName: springComponent.Name, - InstalledVersion: springComponent.Version, - PkgIdentifier: ftypes.PkgIdentifier{ - UID: springComponent.PkgIdentifier.UID, - PURL: springComponent.PkgIdentifier.PURL, - }, + PkgName: springPackage.Name, + InstalledVersion: springPackage.Version, + PkgIdentifier: springPackage.Identifier, } vuln2 = types.DetectedVulnerability{ VulnerabilityID: "CVE-2021-0001", - PkgName: springComponent.Name, - InstalledVersion: springComponent.Version, - PkgIdentifier: ftypes.PkgIdentifier{ - UID: springComponent.PkgIdentifier.UID, - PURL: springComponent.PkgIdentifier.PURL, - }, + PkgName: springPackage.Name, + InstalledVersion: springPackage.Version, + PkgIdentifier: springPackage.Identifier, } vuln3 = types.DetectedVulnerability{ VulnerabilityID: "CVE-2022-3715", - PkgName: bashComponent.Name, - InstalledVersion: bashComponent.Version, - PkgIdentifier: ftypes.PkgIdentifier{ - UID: bashComponent.PkgIdentifier.UID, - PURL: bashComponent.PkgIdentifier.PURL, - }, + PkgName: bashPackage.Name, + InstalledVersion: bashPackage.Version, + PkgIdentifier: bashPackage.Identifier, } vuln4 = types.DetectedVulnerability{ - VulnerabilityID: "CVE-2024-0001", - PkgName: goTransitiveComponent.Name, - InstalledVersion: goTransitiveComponent.Version, - PkgIdentifier: ftypes.PkgIdentifier{ - UID: goTransitiveComponent.PkgIdentifier.UID, - PURL: goTransitiveComponent.PkgIdentifier.PURL, - }, + VulnerabilityID: "CVE-2024-10000", + PkgName: bashPackage.Name, + InstalledVersion: bashPackage.Version, + PkgIdentifier: bashPackage.Identifier, } vuln5 = types.DetectedVulnerability{ - VulnerabilityID: "CVE-2023-2727", - PkgName: clientGoComponent.Name, - InstalledVersion: clientGoComponent.Version, - PkgIdentifier: ftypes.PkgIdentifier{ - UID: clientGoComponent.PkgIdentifier.UID, - PURL: clientGoComponent.PkgIdentifier.PURL, - }, + VulnerabilityID: "CVE-2024-0001", + PkgName: goTransitivePackage.Name, + InstalledVersion: goTransitivePackage.Version, + PkgIdentifier: goTransitivePackage.Identifier, } ) @@ -218,380 +151,500 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func TestVEX_Filter(t *testing.T) { - type fields struct { - filePath string - report types.Report - } +func TestFilter(t *testing.T) { type args struct { - vulns []types.DetectedVulnerability - bom *core.BOM + report *types.Report + opts vex.Options } tests := []struct { name string - fields fields + setup func(t *testing.T, tmpDir string) args args - want []types.DetectedVulnerability + want *types.Report wantErr string }{ { name: "OpenVEX", - fields: fields{ - filePath: "testdata/openvex.json", - }, args: args{ - vulns: []types.DetectedVulnerability{vuln1}, - bom: newTestBOM1(), + // - oci:debian?tag=12 + // - pkg:maven/org.springframework.boot/spring-boot@2.6.0 + report: imageReport([]types.Result{ + springResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{vuln1}, + }), + }), + opts: vex.Options{ + Sources: []vex.Source{ + { + Type: vex.TypeFile, + FilePath: "testdata/openvex.json", + }, + }, + }, }, - want: []types.DetectedVulnerability{}, + want: imageReport([]types.Result{ + springResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{}, + ModifiedFindings: []types.ModifiedFinding{modifiedFinding(vuln1, vulnerableCodeNotInExecutePath, "testdata/openvex.json")}, + }), + }), }, { name: "OpenVEX, multiple statements", - fields: fields{ - filePath: "testdata/openvex-multiple.json", - }, args: args{ - vulns: []types.DetectedVulnerability{ - vuln1, // filtered by VEX - vuln2, + // - oci:debian?tag=12 + // - pkg:maven/org.springframework.boot/spring-boot@2.6.0 + report: imageReport([]types.Result{ + springResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{ + vuln1, // filtered by VEX + vuln2, + }, + }), + }), + opts: vex.Options{ + Sources: []vex.Source{ + { + Type: vex.TypeFile, + FilePath: "testdata/openvex-multiple.json", + }, + }, }, - bom: newTestBOM1(), - }, - want: []types.DetectedVulnerability{ - vuln2, }, + want: imageReport([]types.Result{ + springResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{vuln2}, + ModifiedFindings: []types.ModifiedFinding{modifiedFinding(vuln1, vulnerableCodeNotInExecutePath, "testdata/openvex-multiple.json")}, + }), + }), }, { name: "OpenVEX, subcomponents, oci image", - fields: fields{ - filePath: "testdata/openvex-oci.json", - }, args: args{ - vulns: []types.DetectedVulnerability{ - vuln3, + // - oci:debian?tag=12 + // - pkg:deb/debian/bash@5.3 + report: imageReport([]types.Result{ + bashResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{ + vuln3, // filtered by VEX + }, + }), + }), + opts: vex.Options{ + Sources: []vex.Source{ + { + Type: vex.TypeFile, + FilePath: "testdata/openvex-oci.json", + }, + }, }, - bom: newTestBOM1(), }, - want: []types.DetectedVulnerability{}, + want: imageReport([]types.Result{ + bashResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{}, + ModifiedFindings: []types.ModifiedFinding{modifiedFinding(vuln3, vulnerableCodeNotInExecutePath, "testdata/openvex-oci.json")}, + }), + }), }, { - name: "OpenVEX, subcomponents, wrong oci image", - fields: fields{ - filePath: "testdata/openvex-oci.json", - }, + name: "OpenVEX, subcomponents, mismatched oci image", args: args{ - vulns: []types.DetectedVulnerability{vuln3}, - bom: newTestBOM2(), + report: imageReport(types.Results{ + bashResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{vuln3}, + }), + }), + opts: vex.Options{ + Sources: []vex.Source{ + { + Type: vex.TypeFile, + FilePath: "testdata/openvex-oci-mismatch.json", + }, + }, + }, }, - want: []types.DetectedVulnerability{vuln3}, + want: imageReport([]types.Result{ + bashResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{vuln3}, + }), + }), }, { name: "OpenVEX, single path between product and subcomponent", - fields: fields{ - filePath: "testdata/openvex-nested.json", - }, args: args{ - vulns: []types.DetectedVulnerability{vuln4}, - bom: newTestBOM3(), + report: fsReport([]types.Result{ + goSinglePathResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{ + vuln5, // filtered by VEX + }, + }), + }), + opts: vex.Options{ + Sources: []vex.Source{ + { + Type: vex.TypeFile, + FilePath: "testdata/openvex-nested.json", + }, + }, + }, }, - want: []types.DetectedVulnerability{}, + want: fsReport([]types.Result{ + goSinglePathResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{}, + ModifiedFindings: []types.ModifiedFinding{modifiedFinding(vuln5, vulnerableCodeNotInExecutePath, "testdata/openvex-nested.json")}, + }), + }), }, { name: "OpenVEX, multi paths between product and subcomponent", - fields: fields{ - filePath: "testdata/openvex-nested.json", - }, args: args{ - vulns: []types.DetectedVulnerability{vuln4}, - bom: newTestBOM4(), + report: fsReport([]types.Result{ + goMultiPathResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{ + vuln5, + }, + }), + }), + opts: vex.Options{ + Sources: []vex.Source{ + { + Type: vex.TypeFile, + FilePath: "testdata/openvex-nested.json", + }, + }, + }, }, - want: []types.DetectedVulnerability{vuln4}, // Will not be filtered because of multi paths + want: fsReport([]types.Result{ + goMultiPathResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{vuln5}, // Will not be filtered because of multi paths + }), + }), }, { name: "CycloneDX SBOM with CycloneDX VEX", - fields: fields{ - filePath: "testdata/cyclonedx.json", - report: types.Report{ + args: args{ + report: &types.Report{ ArtifactType: artifact.TypeCycloneDX, BOM: &core.BOM{ SerialNumber: "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", Version: 1, }, - }, - }, - args: args{ - vulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2018-7489", - PkgName: "jackson-databind", - InstalledVersion: "2.8.0", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "com.fasterxml.jackson.core", - Name: "jackson-databind", - Version: "2.8.0", - }, - }, - }, - { - VulnerabilityID: "CVE-2018-7490", - PkgName: "jackson-databind", - InstalledVersion: "2.8.0", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "com.fasterxml.jackson.core", - Name: "jackson-databind", - Version: "2.8.0", - }, - }, + Results: []types.Result{ + springResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{vuln1}, + }), }, - { - VulnerabilityID: "CVE-2022-27943", - PkgID: "libstdc++6@12.3.0-1ubuntu1~22.04", - PkgName: "libstdc++6", - InstalledVersion: "12.3.0-1ubuntu1~22.04", - PkgIdentifier: ftypes.PkgIdentifier{ - BOMRef: "pkg:deb/ubuntu/libstdc%2B%2B6@12.3.0-1ubuntu1~22.04?distro=ubuntu-22.04&arch=amd64", - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeDebian, - Namespace: "ubuntu", - Name: "libstdc++6", - Version: "12.3.0-1ubuntu1~22.04", - Qualifiers: []packageurl.Qualifier{ - { - Key: "arch", - Value: "amd64", - }, - { - Key: "distro", - Value: "ubuntu-22.04", - }, - }, - }, + }, + opts: vex.Options{ + Sources: []vex.Source{ + { + Type: vex.TypeFile, + FilePath: "testdata/cyclonedx.json", }, }, }, }, - want: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2018-7490", - PkgName: "jackson-databind", - InstalledVersion: "2.8.0", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "com.fasterxml.jackson.core", - Name: "jackson-databind", - Version: "2.8.0", - }, - }, + want: &types.Report{ + ArtifactType: artifact.TypeCycloneDX, + BOM: &core.BOM{ + SerialNumber: "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", + Version: 1, + }, + Results: []types.Result{ + springResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{}, + ModifiedFindings: []types.ModifiedFinding{modifiedFinding(vuln1, codeNotReachable, "CycloneDX VEX")}, + }), }, }, }, { name: "CycloneDX VEX wrong URN", - fields: fields{ - filePath: "testdata/cyclonedx.json", - report: types.Report{ + args: args{ + report: &types.Report{ ArtifactType: artifact.TypeCycloneDX, BOM: &core.BOM{ SerialNumber: "urn:uuid:wrong", Version: 1, }, + Results: []types.Result{ + springResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{vuln1}, + }), + }, }, - }, - args: args{ - vulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2018-7489", - PkgName: "jackson-databind", - InstalledVersion: "2.8.0", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "com.fasterxml.jackson.core", - Name: "jackson-databind", - Version: "2.8.0", - }, + opts: vex.Options{ + Sources: []vex.Source{ + { + Type: vex.TypeFile, + FilePath: "testdata/cyclonedx.json", }, }, }, }, - want: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2018-7489", - PkgName: "jackson-databind", - InstalledVersion: "2.8.0", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "com.fasterxml.jackson.core", - Name: "jackson-databind", - Version: "2.8.0", - }, - }, + want: &types.Report{ + ArtifactType: artifact.TypeCycloneDX, + BOM: &core.BOM{ + SerialNumber: "urn:uuid:wrong", + Version: 1, + }, + Results: []types.Result{ + springResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{vuln1}, + }), }, }, }, { name: "CSAF, not affected", - fields: fields{ - filePath: "testdata/csaf.json", - }, args: args{ - bom: newTestBOM5(), - vulns: []types.DetectedVulnerability{vuln5}, + report: imageReport([]types.Result{ + goSinglePathResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{ + vuln5, // filtered by VEX + }, + }), + }), + opts: vex.Options{ + Sources: []vex.Source{ + { + Type: vex.TypeFile, + FilePath: "testdata/csaf.json", + }, + }, + }, }, - want: []types.DetectedVulnerability{}, + want: imageReport([]types.Result{ + goSinglePathResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{}, + ModifiedFindings: []types.ModifiedFinding{modifiedFinding(vuln5, vulnerableCodeNotInExecutePath, "testdata/csaf.json")}, + }), + }), }, { name: "CSAF with relationships, not affected", - fields: fields{ - filePath: "testdata/csaf-relationships.json", - }, args: args{ - bom: newTestBOM5(), - vulns: []types.DetectedVulnerability{vuln5}, + report: imageReport([]types.Result{ + goSinglePathResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{ + vuln5, // filtered by VEX + }, + }), + }), + opts: vex.Options{ + Sources: []vex.Source{ + { + Type: vex.TypeFile, + FilePath: "testdata/csaf-relationships.json", + }, + }, + }, }, - want: []types.DetectedVulnerability{}, + want: imageReport([]types.Result{ + goSinglePathResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{}, + ModifiedFindings: []types.ModifiedFinding{modifiedFinding(vuln5, vulnerableCodeNotInExecutePath, "testdata/csaf-relationships.json")}, + }), + }), }, { name: "CSAF with relationships, affected", - fields: fields{ - filePath: "testdata/csaf-relationships.json", + args: args{ + report: imageReport([]types.Result{ + goMultiPathResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{ + vuln5, + }, + }), + }), + opts: vex.Options{ + Sources: []vex.Source{ + { + Type: vex.TypeFile, + FilePath: "testdata/csaf-relationships.json", + }, + }, + }, + }, + want: imageReport([]types.Result{ + goMultiPathResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{vuln5}, // Will not be filtered because of multi paths + }), + }), + }, + { + name: "VEX Repository", + setup: func(t *testing.T, tmpDir string) { + // Create repository.yaml + vexDir := filepath.Join(tmpDir, ".trivy", "vex") + require.NoError(t, os.MkdirAll(vexDir, 0755)) + + configPath := filepath.Join(vexDir, "repository.yaml") + configContent := ` +repositories: + - name: default + url: https://example.com/vex/default + enabled: true` + require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0644)) }, args: args{ - bom: newTestBOM6(), - vulns: []types.DetectedVulnerability{vuln5}, + report: imageReport([]types.Result{ + bashResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{ + vuln3, // filtered by VEX + }, + }), + }), + opts: vex.Options{ + CacheDir: "testdata/single-repo", + Sources: []vex.Source{{Type: vex.TypeRepository}}, + }, }, - want: []types.DetectedVulnerability{vuln5}, + want: imageReport([]types.Result{ + bashResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{}, + ModifiedFindings: []types.ModifiedFinding{ + modifiedFinding(vuln3, "vulnerable_code_not_in_execute_path", "VEX Repository: default (https://example.com/vex/default)"), + }, + }), + }), }, { name: "unknown format", - fields: fields{ - filePath: "testdata/unknown.json", + args: args{ + report: &types.Report{}, + opts: vex.Options{ + Sources: []vex.Source{ + { + Type: vex.TypeFile, + FilePath: "testdata/unknown.json", + }, + }, + }, }, - args: args{}, wantErr: "unable to load VEX", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - v, err := vex.New(tt.fields.filePath, tt.fields.report) + tmpDir := t.TempDir() + t.Setenv("XDG_DATA_HOME", tmpDir) + if tt.setup != nil { + tt.setup(t, tmpDir) + } + err := vex.Filter(context.Background(), tt.args.report, tt.args.opts) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) - - got := &types.Result{ - Vulnerabilities: tt.args.vulns, - } - v.Filter(got, tt.args.bom) - assert.Equal(t, tt.want, got.Vulnerabilities) + assert.Equal(t, tt.want, tt.args.report) }) } } -func newTestBOM1() *core.BOM { - // - oci:debian?tag=12 - // - pkg:maven/org.springframework.boot/spring-boot@2.6.0 - // - pkg:deb/debian/bash@5.3 - bom := core.NewBOM(core.Options{Parents: true}) - bom.AddComponent(&ociComponent) - bom.AddComponent(&springComponent) - bom.AddComponent(&bashComponent) - bom.AddRelationship(&ociComponent, &springComponent, core.RelationshipContains) - bom.AddRelationship(&ociComponent, &bashComponent, core.RelationshipContains) - return bom -} - -func newTestBOM2() *core.BOM { - bom := core.NewBOM(core.Options{}) - bom.AddComponent(&core.Component{ - Root: true, - Type: core.TypeContainerImage, - Name: "ubuntu:24.04", - PkgIdentifier: ftypes.PkgIdentifier{ - PURL: &packageurl.PackageURL{ - Type: packageurl.TypeOCI, - Name: "ubuntu", - Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", - Qualifiers: packageurl.Qualifiers{ - { - Key: "tag", - Value: "24.04", - }, - { - Key: "repository_url", - Value: "docker.io/library/ubuntu", - }, - }, +func imageReport(results types.Results) *types.Report { + return &types.Report{ + ArtifactName: "debian:12", + ArtifactType: artifact.TypeContainerImage, + Metadata: types.Metadata{ + RepoDigests: []string{ + "debian:@sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", + }, + ImageConfig: v1.ConfigFile{ + Architecture: "amd64", }, }, - }) - return bom + Results: results, + } } -func newTestBOM3() *core.BOM { - // - filesystem - // - pkg:golang/github.com/aquasecurity/go-module@1.0.0 - // - pkg:golang/github.com/aquasecurity/go-direct1@2.0.0 - // - pkg:golang/github.com/aquasecurity/go-transitive@4.0.0 - bom := core.NewBOM(core.Options{Parents: true}) - bom.AddComponent(&fsComponent) - bom.AddComponent(&goModuleComponent) - bom.AddComponent(&goDirectComponent1) - bom.AddComponent(&goTransitiveComponent) - bom.AddRelationship(&fsComponent, &goModuleComponent, core.RelationshipContains) - bom.AddRelationship(&goModuleComponent, &goDirectComponent1, core.RelationshipDependsOn) - bom.AddRelationship(&goDirectComponent1, &goTransitiveComponent, core.RelationshipDependsOn) - return bom +func fsReport(results types.Results) *types.Report { + return &types.Report{ + ArtifactName: ".", + ArtifactType: artifact.TypeFilesystem, + Results: results, + } +} + +func springResult(result types.Result) types.Result { + result.Type = ftypes.Jar + result.Class = types.ClassLangPkg + result.Packages = []ftypes.Package{springPackage} + return result +} + +// bashResult wraps the result with the bash package +func bashResult(result types.Result) types.Result { + result.Type = ftypes.Debian + result.Class = types.ClassOSPkg + result.Packages = []ftypes.Package{bashPackage} + return result } -func newTestBOM4() *core.BOM { - // - filesystem - // - pkg:golang/github.com/aquasecurity/go-module@2.0.0 - // - pkg:golang/github.com/aquasecurity/go-direct1@3.0.0 - // - pkg:golang/github.com/aquasecurity/go-transitive@5.0.0 - // - pkg:golang/github.com/aquasecurity/go-direct2@4.0.0 - // - pkg:golang/github.com/aquasecurity/go-transitive@5.0.0 - bom := core.NewBOM(core.Options{Parents: true}) - bom.AddComponent(&fsComponent) - bom.AddComponent(&goModuleComponent) - bom.AddComponent(&goDirectComponent1) - bom.AddComponent(&goDirectComponent2) - bom.AddComponent(&goTransitiveComponent) - bom.AddRelationship(&fsComponent, &goModuleComponent, core.RelationshipContains) - bom.AddRelationship(&goModuleComponent, &goDirectComponent1, core.RelationshipDependsOn) - bom.AddRelationship(&goModuleComponent, &goDirectComponent2, core.RelationshipDependsOn) - bom.AddRelationship(&goDirectComponent1, &goTransitiveComponent, core.RelationshipDependsOn) - bom.AddRelationship(&goDirectComponent2, &goTransitiveComponent, core.RelationshipDependsOn) - return bom +func goSinglePathResult(result types.Result) types.Result { + result.Type = ftypes.GoModule + result.Class = types.ClassLangPkg + + // - pkg:golang/github.com/aquasecurity/go-module@1.0.0 + // - pkg:golang/github.com/aquasecurity/go-direct1@2.0.0 + // - pkg:golang/github.com/aquasecurity/go-transitive@4.0.0 + goModule := clonePackage(goModulePackage) + goDirect1 := clonePackage(goDirectPackage1) + goTransitive := clonePackage(goTransitivePackage) + + goModule.DependsOn = []string{goDirect1.ID} + goDirect1.DependsOn = []string{goTransitive.ID} + result.Packages = []ftypes.Package{ + goModule, + goDirect1, + goTransitive, + } + return result } -func newTestBOM5() *core.BOM { - // - oci:debian?tag=12 - // - pkg:bitnami/argo-cd@2.9.3-2?arch=amd64&distro=debian-12 - // - pkg:golang/k8s.io/client-go@0.24.2 - bom := core.NewBOM(core.Options{Parents: true}) - bom.AddComponent(&ociComponent) - bom.AddComponent(&argoComponent) - bom.AddComponent(&clientGoComponent) - bom.AddRelationship(&ociComponent, &argoComponent, core.RelationshipContains) - bom.AddRelationship(&argoComponent, &clientGoComponent, core.RelationshipDependsOn) - return bom +func goMultiPathResult(result types.Result) types.Result { + result.Type = ftypes.GoModule + result.Class = types.ClassLangPkg + + // - pkg:golang/github.com/aquasecurity/go-module@2.0.0 + // - pkg:golang/github.com/aquasecurity/go-direct1@3.0.0 + // - pkg:golang/github.com/aquasecurity/go-transitive@5.0.0 + // - pkg:golang/github.com/aquasecurity/go-direct2@4.0.0 + // - pkg:golang/github.com/aquasecurity/go-transitive@5.0.0 + goModule := clonePackage(goModulePackage) + goDirect1 := clonePackage(goDirectPackage1) + goDirect2 := clonePackage(goDirectPackage2) + goTransitive := clonePackage(goTransitivePackage) + + goModule.DependsOn = []string{ + goDirect1.ID, + goDirect2.ID, + } + goDirect1.DependsOn = []string{goTransitive.ID} + goDirect2.DependsOn = []string{goTransitive.ID} + result.Packages = []ftypes.Package{ + goModule, + goDirect1, + goDirect2, + goTransitive, + } + return result +} + +func modifiedFinding(vuln types.DetectedVulnerability, statement, source string) types.ModifiedFinding { + return types.ModifiedFinding{ + Type: types.FindingTypeVulnerability, + Status: types.FindingStatusNotAffected, + Statement: statement, + Source: source, + Finding: vuln, + } } -func newTestBOM6() *core.BOM { - // - oci:debian?tag=12 - // - pkg:golang/k8s.io/client-go@0.24.2 - bom := core.NewBOM(core.Options{Parents: true}) - bom.AddComponent(&ociComponent) - bom.AddComponent(&clientGoComponent) - bom.AddRelationship(&ociComponent, &clientGoComponent, core.RelationshipContains) - return bom +func clonePackage(p ftypes.Package) ftypes.Package { + n := p + n.DependsOn = []string{} + return n } From 8d5ba3f5e72e97f2cb2dc59c09540f7811701cea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:32:05 +0400 Subject: [PATCH 252/352] chore(deps): bump the common group across 1 directory with 17 updates (#7230) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 44 +++++++++++++-------------- go.sum | 96 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/go.mod b/go.mod index de5fed7f1b4e..99b33b20a62e 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.4 require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 github.com/BurntSushi/toml v1.4.0 github.com/CycloneDX/cyclonedx-go v0.9.0 @@ -30,9 +30,9 @@ require ( github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b github.com/aws/aws-sdk-go-v2 v1.30.3 - github.com/aws/aws-sdk-go-v2/config v1.27.26 - github.com/aws/aws-sdk-go-v2/credentials v1.17.26 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.170.0 + github.com/aws/aws-sdk-go-v2/config v1.27.27 + github.com/aws/aws-sdk-go-v2/credentials v1.17.27 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.172.0 github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3 github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect @@ -41,9 +41,9 @@ require ( github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cenkalti/backoff/v4 v4.3.0 github.com/cheggaaa/pb/v3 v3.1.5 - github.com/containerd/containerd v1.7.19 + github.com/containerd/containerd v1.7.20 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 - github.com/docker/docker v27.1.0+incompatible + github.com/docker/docker v27.1.1+incompatible github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.17.0 github.com/go-git/go-git/v5 v5.12.0 @@ -51,7 +51,7 @@ require ( github.com/go-openapi/strfmt v0.23.0 github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/go-containerregistry v0.20.0 + github.com/google/go-containerregistry v0.20.1 github.com/google/go-github/v62 v62.0.0 github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/uuid v1.6.0 @@ -86,16 +86,16 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 - github.com/moby/buildkit v0.15.0 + github.com/moby/buildkit v0.15.1 github.com/open-policy-agent/opa v0.66.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/openvex/go-vex v0.2.5 - github.com/owenrumney/go-sarif/v2 v2.3.2 - github.com/owenrumney/squealer v1.2.2 + github.com/owenrumney/go-sarif/v2 v2.3.3 + github.com/owenrumney/squealer v1.2.3 github.com/package-url/packageurl-go v0.1.3 github.com/quasilyte/go-ruleguard/dsl v0.3.22 - github.com/samber/lo v1.44.0 + github.com/samber/lo v1.46.0 github.com/secure-systems-lab/go-securesystemslib v0.8.0 github.com/sigstore/rekor v1.3.6 github.com/sirupsen/logrus v1.9.3 @@ -112,7 +112,7 @@ require ( github.com/twitchtv/twirp v8.1.3+incompatible github.com/xeipuuv/gojsonschema v1.2.0 github.com/xlab/treeprint v1.2.0 - github.com/zclconf/go-cty v1.14.4 + github.com/zclconf/go-cty v1.15.0 github.com/zclconf/go-cty-yaml v1.0.3 go.etcd.io/bbolt v1.3.10 golang.org/x/crypto v0.25.0 @@ -122,18 +122,18 @@ require ( golang.org/x/sync v0.7.0 golang.org/x/term v0.22.0 golang.org/x/text v0.16.0 - golang.org/x/vuln v1.1.2 + golang.org/x/vuln v1.1.3 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.15.2 - k8s.io/api v0.30.2 + helm.sh/helm/v3 v3.15.3 + k8s.io/api v0.30.3 k8s.io/utils v0.0.0-20231127182322-b307cd553661 - modernc.org/sqlite v1.30.1 + modernc.org/sqlite v1.31.1 sigs.k8s.io/yaml v1.4.0 ) -require github.com/magefile/mage v1.14.0 +require github.com/magefile/mage v1.15.0 require ( cloud.google.com/go v0.112.1 // indirect @@ -143,7 +143,7 @@ require ( dario.cat/mergo v1.0.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect @@ -178,7 +178,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect @@ -364,7 +364,7 @@ require ( golang.org/x/sys v0.22.0 // indirect golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/tools v0.23.0 // indirect google.golang.org/api v0.172.0 // indirect google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect @@ -376,7 +376,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.30.0 // indirect - k8s.io/apimachinery v0.30.2 // indirect + k8s.io/apimachinery v0.30.3 // indirect k8s.io/apiserver v0.30.0 // indirect k8s.io/cli-runtime v0.30.2 // indirect k8s.io/client-go v0.30.2 // indirect @@ -385,7 +385,7 @@ require ( k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/kubectl v0.30.1 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect - modernc.org/libc v1.52.1 // indirect + modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.8.0 // indirect modernc.org/strutil v1.2.0 // indirect diff --git a/go.sum b/go.sum index b8d36339bfe0..08ac8d17a66a 100644 --- a/go.sum +++ b/go.sum @@ -616,12 +616,12 @@ github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= @@ -791,10 +791,10 @@ github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= -github.com/aws/aws-sdk-go-v2/config v1.27.26 h1:T1kAefbKuNum/AbShMsZEro6eRkeOT8YILfE9wyjAYQ= -github.com/aws/aws-sdk-go-v2/config v1.27.26/go.mod h1:ivWHkAWFrw/nxty5Fku7soTIVdqZaZ7dw+tc5iGW3GA= -github.com/aws/aws-sdk-go-v2/credentials v1.17.26 h1:tsm8g/nJxi8+/7XyJJcP2dLrnK/5rkFp6+i2nhmz5fk= -github.com/aws/aws-sdk-go-v2/credentials v1.17.26/go.mod h1:3vAM49zkIa3q8WT6o9Ve5Z0vdByDMwmdScO0zvThTgI= +github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= +github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= @@ -805,8 +805,8 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.170.0 h1:zPwhEYn3Y83mnnr9QG+i6NTiAbVbcJe6RpCSJKHIQNE= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.170.0/go.mod h1:9KdiRVKTZyPRTlbX3i41FxTV+5OatZ7xOJCN4lleX7g= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.172.0 h1:lJjLKG92RyKIIYujVvulR3JpVjr3yxaU34nwXCq8K2o= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.172.0/go.mod h1:o6QDjdVKpP5EF0dp/VlvqckzuSDATr1rLdHt3A5m0YY= github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3 h1:+v2hv29pWaVDASIScHuUhDC93nqJGVlGf6cujrJMHZE= github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3/go.mod h1:RhaP7Wil0+uuuhiE4FzOOEFZwkmFAk1ZflXzK+O3ptU= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= @@ -815,8 +815,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrx github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALwfMWpd64tONS/NE= github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 h1:Fv1vD2L65Jnp5QRsdiM64JvUM4Xe+E0JyVsRQKv6IeA= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.3/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= @@ -934,8 +934,8 @@ github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.7.19 h1:/xQ4XRJ0tamDkdzrrBAUy/LE5nCcxFKdBm4EcPrSMEE= -github.com/containerd/containerd v1.7.19/go.mod h1:h4FtNYUUMB4Phr6v+xG89RYKj9XccvbNSCKjdufCrkc= +github.com/containerd/containerd v1.7.20 h1:Sl6jQYk3TRavaU83h66QMbI2Nqg9Jm6qzwX57Vsn1SQ= +github.com/containerd/containerd v1.7.20/go.mod h1:52GsS5CwquuqPuLncsXwG0t2CiUce+KsNHJZQJvAgR0= github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA= github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -1074,8 +1074,8 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v27.1.0+incompatible h1:rEHVQc4GZ0MIQKifQPHSFGV/dVgaZafgRf8fCPtDYBs= -github.com/docker/docker v27.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= @@ -1355,8 +1355,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= -github.com/google/go-containerregistry v0.20.0 h1:wRqHpOeVh3DnenOrPy9xDOLdnLatiGuuNRVelR2gSbg= -github.com/google/go-containerregistry v0.20.0/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0= +github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -1615,8 +1615,8 @@ github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6w github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= -github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= -github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -1697,8 +1697,8 @@ github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQ 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/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.15.0 h1:vnZLThPr9JU6SvItctKoa6NfgPZ8oUApg/TCOaa/SVs= -github.com/moby/buildkit v0.15.0/go.mod h1:oN9S+8I7wF26vrqn9NuAF6dFSyGTfXvtiu9o1NlnnH4= +github.com/moby/buildkit v0.15.1 h1:J6wrew7hphKqlq1wuu6yaUb/1Ra7gEzDAovylGztAKM= +github.com/moby/buildkit v0.15.1/go.mod h1:Yis8ZMUJTHX9XhH9zVyK2igqSHV3sxi3UN0uztZocZk= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= @@ -1803,10 +1803,10 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ= github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88IO/0Wo= github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= -github.com/owenrumney/go-sarif/v2 v2.3.2 h1:yptG4K76SnLydTFHUecZotPR9uhBvnJLjE7cPltvROU= -github.com/owenrumney/go-sarif/v2 v2.3.2/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= -github.com/owenrumney/squealer v1.2.2 h1:zsnZSwkWi8Y2lgwmg77b565vlHQovlvBrSBzmAs3oiE= -github.com/owenrumney/squealer v1.2.2/go.mod h1:pDCW33bWJ2kDOuz7+2BSXDgY38qusVX0MtjPCSFtdSo= +github.com/owenrumney/go-sarif/v2 v2.3.3 h1:ubWDJcF5i3L/EIOER+ZyQ03IfplbSU1BLOE26uKQIIU= +github.com/owenrumney/go-sarif/v2 v2.3.3/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= +github.com/owenrumney/squealer v1.2.3 h1:7v2BGNReEHYGyopOpjnurbnowk5WWagpN/u9KEu0uUU= +github.com/owenrumney/squealer v1.2.3/go.mod h1:F3PF/UaTAzaexT/cvvMYCSRHLRPBCiUcPClz3SZ6618= github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -1909,8 +1909,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/samber/lo v1.44.0 h1:5il56KxRE+GHsm1IR+sZ/6J42NODigFiqCWpSc2dybA= -github.com/samber/lo v1.44.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= +github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -2097,8 +2097,8 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPS github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= -github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= github.com/zclconf/go-cty-yaml v1.0.3 h1:og/eOQ7lvA/WWhHGFETVWNduJM7Rjsv2RRpx1sdFMLc= @@ -2636,10 +2636,10 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= -golang.org/x/vuln v1.1.2 h1:UkLxe+kAMcrNBpGrFbU0Mc5l7cX97P2nhy21wx5+Qbk= -golang.org/x/vuln v1.1.2/go.mod h1:2o3fRKD8Uz9AraAL3lwd/grWBv+t+SeJnPcqBUJrY24= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/vuln v1.1.3 h1:NPGnvPOTgnjBc9HTaUx+nj+EaUYxl5SJOWqaDYGaFYw= +golang.org/x/vuln v1.1.3/go.mod h1:7Le6Fadm5FOqE9C926BCD0g12NWyhg7cxV4BwcPFuNY= 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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2988,8 +2988,8 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.15.2 h1:/3XINUFinJOBjQplGnjw92eLGpgXXp1L8chWPkCkDuw= -helm.sh/helm/v3 v3.15.2/go.mod h1:FzSIP8jDQaa6WAVg9F+OkKz7J0ZmAga4MABtTbsb9WQ= +helm.sh/helm/v3 v3.15.3 h1:HcZDaVFe9uHa6hpsR54mJjYyRy4uz/pc6csg27nxFOc= +helm.sh/helm/v3 v3.15.3/go.mod h1:FzSIP8jDQaa6WAVg9F+OkKz7J0ZmAga4MABtTbsb9WQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -3001,15 +3001,15 @@ honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= -k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= +k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ= +k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04= k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= -k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc= +k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= @@ -3050,16 +3050,16 @@ lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk= -modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= +modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccgo/v4 v4.17.10 h1:6wrtRozgrhCxieCeJh85QsxkX/2FFrT9hdaWPlbn4Zo= -modernc.org/ccgo/v4 v4.17.10/go.mod h1:0NBHgsqTTpm9cA5z2ccErvGZmtntSM9qD2kFAs6pjXM= +modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= +modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= @@ -3075,8 +3075,8 @@ modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= -modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M= -modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= @@ -3093,8 +3093,8 @@ modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk= -modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU= +modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= +modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= From d76febaee107c645e864da0f4d74a8f6ae4ad232 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 25 Jul 2024 20:54:59 +0500 Subject: [PATCH 253/352] fix(dotnet): show `nuget package dir not found` log only when checking `nuget` packages (#7194) Co-authored-by: knqyf263 --- pkg/fanal/analyzer/language/dotnet/nuget/nuget.go | 6 ++++++ pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go | 2 +- pkg/fanal/analyzer/language/dotnet/nuget/nuspec.go | 6 ------ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go index fa0cc486def1..b7400048edac 100644 --- a/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go @@ -17,6 +17,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) @@ -39,6 +40,7 @@ type nugetLibraryAnalyzer struct { lockParser language.Parser configParser language.Parser licenseParser nuspecParser + logger *log.Logger } func newNugetLibraryAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { @@ -46,12 +48,16 @@ func newNugetLibraryAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, lockParser: lock.NewParser(), configParser: config.NewParser(), licenseParser: newNuspecParser(), + logger: log.WithPrefix("nuget"), }, nil } func (a *nugetLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { var apps []types.Application foundLicenses := make(map[string][]string) + if a.licenseParser.packagesDir == "" { + a.logger.Debug("The nuget packages directory couldn't be found. License search disabled") + } // We saved only config and lock files in the FS, // so we need to parse all saved files diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go index 52e10ad5622d..ea105cc99e91 100644 --- a/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuget_test.go @@ -12,7 +12,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" ) -func Test_nugetibraryAnalyzer_Analyze(t *testing.T) { +func Test_nugetLibraryAnalyzer_Analyze(t *testing.T) { tests := []struct { name string dir string diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuspec.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuspec.go index 6f25c91e565a..98def24ab8d2 100644 --- a/pkg/fanal/analyzer/language/dotnet/nuget/nuspec.go +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuspec.go @@ -9,7 +9,6 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) @@ -30,13 +29,10 @@ type License struct { } type nuspecParser struct { - logger *log.Logger packagesDir string // global packages folder - https: //learn.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders } func newNuspecParser() nuspecParser { - logger := log.WithPrefix("nuget") - // cf. https: //learn.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders packagesDir := os.Getenv("NUGET_PACKAGES") if packagesDir == "" { @@ -44,12 +40,10 @@ func newNuspecParser() nuspecParser { } if !fsutils.DirExists(packagesDir) { - logger.Debug("The nuget packages directory couldn't be found. License search disabled") return nuspecParser{} } return nuspecParser{ - logger: logger, packagesDir: packagesDir, } } From f35f4a5e83720f59e18ad3f8f90a3dc092e2117e Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 29 Jul 2024 10:51:38 +0600 Subject: [PATCH 254/352] docs: show VEX cli pages + update config file page for VEX flags (#7244) --- docs/docs/references/configuration/config-file.md | 10 ++++++++++ mkdocs.yml | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index d90ae7b26384..535a1b9e657e 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -276,6 +276,16 @@ vulnerability: # Default is empty ignore-status: - end_of_life + + # Same as '--vex' + # Default is empty + vex: + - path/to/vex/file + - repo + + # Same as '--skip-vex-repo-update' + # Default is false + skip-vex-repo-update: true ``` ## License Options diff --git a/mkdocs.yml b/mkdocs.yml index 984332633203..f61ccdf7161d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -180,6 +180,12 @@ nav: - SBOM: docs/references/configuration/cli/trivy_sbom.md - Server: docs/references/configuration/cli/trivy_server.md - Version: docs/references/configuration/cli/trivy_version.md + - VEX: + - VEX: docs/references/configuration/cli/trivy_vex.md + - VEX Download: docs/references/configuration/cli/trivy_vex_repo_download.md + - VEX Init: docs/references/configuration/cli/trivy_vex_repo_init.md + - VEX List: docs/references/configuration/cli/trivy_vex_repo_list.md + - VEX Repo: docs/references/configuration/cli/trivy_vex_repo.md - VM: docs/references/configuration/cli/trivy_vm.md - Config file: docs/references/configuration/config-file.md - Modes: From 5c37361600d922db27dd594b2a80c010a19b3a6e Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Mon, 29 Jul 2024 10:18:59 +0400 Subject: [PATCH 255/352] feat(vuln): add `--pkg-relationships` (#7237) Signed-off-by: knqyf263 --- .../configuration/cli/trivy_filesystem.md | 3 +- .../configuration/cli/trivy_image.md | 3 +- .../configuration/cli/trivy_kubernetes.md | 3 +- .../configuration/cli/trivy_repository.md | 3 +- .../configuration/cli/trivy_rootfs.md | 3 +- .../configuration/cli/trivy_sbom.md | 3 +- .../references/configuration/cli/trivy_vm.md | 3 +- docs/docs/scanner/vulnerability.md | 42 ++++++- pkg/commands/app.go | 37 +++--- pkg/commands/artifact/run.go | 31 +++-- pkg/fanal/types/package.go | 28 ++++- pkg/flag/options.go | 18 +++ pkg/flag/package_flags.go | 91 ++++++++++++++ pkg/flag/package_flags_test.go | 84 +++++++++++++ pkg/flag/report_flags.go | 19 --- pkg/flag/report_flags_test.go | 24 ---- pkg/flag/scan_flags.go | 82 ++++++------ pkg/log/logger.go | 7 ++ pkg/rpc/client/client.go | 1 + pkg/rpc/server/server.go | 39 ++++-- pkg/scanner/local/scan.go | 31 ++++- pkg/scanner/local/scan_test.go | 71 ++++++----- pkg/types/scan.go | 1 + pkg/x/strings/strings.go | 19 ++- rpc/scanner/service.pb.go | 118 ++++++++++-------- rpc/scanner/service.proto | 1 + rpc/scanner/service.twirp.go | 85 ++++++------- 27 files changed, 576 insertions(+), 274 deletions(-) create mode 100644 pkg/flag/package_flags.go create mode 100644 pkg/flag/package_flags_test.go diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 86ac2c2f8918..e442c590ccc5 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -63,7 +63,8 @@ trivy filesystem [flags] PATH --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. - --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) + --pkg-relationships strings list of package relationships (unknown,root,direct,indirect) (default [unknown,root,direct,indirect]) + --pkg-types strings list of package types (os,library) (default [os,library]) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 62e731e14126..cf0e7591b726 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -81,7 +81,8 @@ trivy image [flags] IMAGE_NAME --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. - --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) + --pkg-relationships strings list of package relationships (unknown,root,direct,indirect) (default [unknown,root,direct,indirect]) + --pkg-types strings list of package types (os,library) (default [os,library]) --platform string set platform in the form os/arch if image is multi-platform capable --podman-host string unix podman socket path to use for podman scanning --redis-ca string redis ca file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 2f8539dfeb47..0dea6c93a9fb 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -78,7 +78,8 @@ trivy kubernetes [flags] [CONTEXT] --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. - --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) + --pkg-relationships strings list of package relationships (unknown,root,direct,indirect) (default [unknown,root,direct,indirect]) + --pkg-types strings list of package types (os,library) (default [os,library]) --qps float specify the maximum QPS to the master from this client (default 5) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 661381b76ebf..62364d9ba62c 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -63,7 +63,8 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. - --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) + --pkg-relationships strings list of package relationships (unknown,root,direct,indirect) (default [unknown,root,direct,indirect]) + --pkg-types strings list of package types (os,library) (default [os,library]) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 01ed2ce062c6..e7cec39be469 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -65,7 +65,8 @@ trivy rootfs [flags] ROOTDIR --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. - --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) + --pkg-relationships strings list of package relationships (unknown,root,direct,indirect) (default [unknown,root,direct,indirect]) + --pkg-types strings list of package types (os,library) (default [os,library]) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index 0c7508c854e1..6fa70d15ca16 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -43,7 +43,8 @@ trivy sbom [flags] SBOM_PATH --offline-scan do not issue API requests to identify dependencies -o, --output string output file name --output-plugin-arg string [EXPERIMENTAL] output plugin arguments - --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) + --pkg-relationships strings list of package relationships (unknown,root,direct,indirect) (default [unknown,root,direct,indirect]) + --pkg-types strings list of package types (os,library) (default [os,library]) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index c250b9bcf06b..b878fc070277 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -56,7 +56,8 @@ trivy vm [flags] VM_IMAGE -o, --output string output file name --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) - --pkg-types strings comma-separated list of package types (os,library) (default [os,library]) + --pkg-relationships strings list of package relationships (unknown,root,direct,indirect) (default [unknown,root,direct,indirect]) + --pkg-types strings list of package types (os,library) (default [os,library]) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend --redis-key string redis key file location, if using redis as cache backend diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md index ba612ee06b28..9df2b43d8b80 100644 --- a/docs/docs/scanner/vulnerability.md +++ b/docs/docs/scanner/vulnerability.md @@ -202,7 +202,8 @@ Currently, specifying a username and password is not supported. This section describes vulnerability-specific configuration. Other common options are documented [here](../configuration/index.md). -### Enabling a subset of package types +### Enabling a Subset of Package Types + It's possible to only enable certain package types if you prefer. You can do so by passing the `--pkg-types` option. This flag takes a comma-separated list of package types. @@ -268,6 +269,45 @@ Total: 7 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 3, CRITICAL: 2) +!!! info + This flag filters the packages themselves, so it also affects the `--list-all-pkgs` option and SBOM generation. + +### Filtering by Package Relationships + + +Trivy supports filtering vulnerabilities based on the relationship of packages within a project. +This is achieved through the `--pkg-relationships` flag. +This feature allows you to focus on vulnerabilities in specific types of dependencies, such as only those in direct dependencies. + +In Trivy, there are four types of package relationships: + +1. `root`: The root package being scanned +2. `direct`: Direct dependencies of the root package +3. `indirect`: Transitive dependencies +4. `unknown`: Packages whose relationship cannot be determined + +The available relationships may vary depending on the ecosystem. +To see which relationships are supported for a particular project, you can use the JSON output format and check the `Relationship` field: + +``` +$ trivy repo -f json --list-all-pkgs /path/to/project +``` + +To scan only the root package and its direct dependencies, you can use the flag as follows: + +``` +$ trivy repo --pkg-relationships root,direct /path/to/project +``` + +By default, all relationships are included in the scan. + +!!! info + This flag filters the packages themselves, so it also affects the `--list-all-pkgs` option and SBOM generation. + +!!! warning + As it may not provide a complete package list, `--pkg-relationships` cannot be used with `--dependency-tree`, `--vex` or SBOM generation. + + [^1]: https://github.com/GoogleContainerTools/distroless [nvd-CVE-2023-0464]: https://nvd.nist.gov/vuln/detail/CVE-2023-0464 diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 4977db6e7ba4..a3bd7b78e6f1 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -243,9 +243,6 @@ func NewRootCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { - scanFlagGroup := flag.NewScanFlagGroup() - scanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' - reportFlagGroup := flag.NewReportFlagGroup() report := flag.ReportFormatFlag.Clone() report.Default = "summary" // override the default value as the summary is preferred for the compliance report @@ -256,27 +253,28 @@ func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { compliance.Values = []string{types.ComplianceDockerCIS160} reportFlagGroup.Compliance = compliance // override usage as the accepted values differ for each subcommand. - misconfFlagGroup := flag.NewMisconfFlagGroup() - misconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' - misconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' - imageFlags := &flag.Flags{ GlobalFlagGroup: globalFlags, CacheFlagGroup: flag.NewCacheFlagGroup(), DBFlagGroup: flag.NewDBFlagGroup(), ImageFlagGroup: flag.NewImageFlagGroup(), // container image specific LicenseFlagGroup: flag.NewLicenseFlagGroup(), - MisconfFlagGroup: misconfFlagGroup, + MisconfFlagGroup: flag.NewMisconfFlagGroup(), ModuleFlagGroup: flag.NewModuleFlagGroup(), + PackageFlagGroup: flag.NewPackageFlagGroup(), RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode RegistryFlagGroup: flag.NewRegistryFlagGroup(), RegoFlagGroup: flag.NewRegoFlagGroup(), ReportFlagGroup: reportFlagGroup, - ScanFlagGroup: scanFlagGroup, + ScanFlagGroup: flag.NewScanFlagGroup(), SecretFlagGroup: flag.NewSecretFlagGroup(), VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), } + imageFlags.PackageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + imageFlags.MisconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' + imageFlags.MisconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' + cmd := &cobra.Command{ Use: "image [flags] IMAGE_NAME", Aliases: []string{"i"}, @@ -342,6 +340,7 @@ func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { LicenseFlagGroup: flag.NewLicenseFlagGroup(), MisconfFlagGroup: flag.NewMisconfFlagGroup(), ModuleFlagGroup: flag.NewModuleFlagGroup(), + PackageFlagGroup: flag.NewPackageFlagGroup(), RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode RegistryFlagGroup: flag.NewRegistryFlagGroup(), RegoFlagGroup: flag.NewRegoFlagGroup(), @@ -400,6 +399,7 @@ func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { LicenseFlagGroup: flag.NewLicenseFlagGroup(), MisconfFlagGroup: flag.NewMisconfFlagGroup(), ModuleFlagGroup: flag.NewModuleFlagGroup(), + PackageFlagGroup: flag.NewPackageFlagGroup(), RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode RegistryFlagGroup: flag.NewRegistryFlagGroup(), RegoFlagGroup: flag.NewRegoFlagGroup(), @@ -411,7 +411,7 @@ func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { rootfsFlags.ReportFlagGroup.ReportFormat = nil // TODO: support --report summary rootfsFlags.ReportFlagGroup.Compliance = nil // disable '--compliance' rootfsFlags.ReportFlagGroup.ReportFormat = nil // disable '--report' - rootfsFlags.ScanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + rootfsFlags.PackageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' rootfsFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default cmd := &cobra.Command{ @@ -460,6 +460,7 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { LicenseFlagGroup: flag.NewLicenseFlagGroup(), MisconfFlagGroup: flag.NewMisconfFlagGroup(), ModuleFlagGroup: flag.NewModuleFlagGroup(), + PackageFlagGroup: flag.NewPackageFlagGroup(), RegistryFlagGroup: flag.NewRegistryFlagGroup(), RegoFlagGroup: flag.NewRegoFlagGroup(), RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode @@ -516,7 +517,6 @@ func NewConvertCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { ScanFlagGroup: &flag.ScanFlagGroup{}, ReportFlagGroup: flag.NewReportFlagGroup(), } - convertFlags.ReportFlagGroup.PkgTypes = nil // disable '--pkg-types' cmd := &cobra.Command{ Use: "convert [flags] RESULT_JSON", @@ -685,7 +685,6 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { configFlags.ReportFlagGroup.ListAllPkgs = nil // disable '--list-all-pkgs' configFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' configFlags.ReportFlagGroup.ShowSuppressed = nil // disable '--show-suppressed' - configFlags.ReportFlagGroup.PkgTypes = nil // disable '--pkg-types' configFlags.ReportFlagGroup.ReportFormat.Usage = "specify a compliance report format for the output" // @TODO: support --report summary for non compliance reports configFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) @@ -960,7 +959,6 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { }) scanners.Default = scanners.Values scanFlags.Scanners = scanners - scanFlags.IncludeDevDeps = nil // disable '--include-dev-deps' // required only SourceFlag imageFlags := &flag.ImageFlagGroup{ImageSources: flag.SourceFlag.Clone()} @@ -997,6 +995,7 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { ImageFlagGroup: imageFlags, K8sFlagGroup: flag.NewK8sFlagGroup(), // kubernetes-specific flags MisconfFlagGroup: misconfFlagGroup, + PackageFlagGroup: flag.NewPackageFlagGroup(), RegoFlagGroup: flag.NewRegoFlagGroup(), ReportFlagGroup: reportFlagGroup, ScanFlagGroup: scanFlags, @@ -1004,6 +1003,8 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { RegistryFlagGroup: flag.NewRegistryFlagGroup(), VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), } + k8sFlags.PackageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + cmd := &cobra.Command{ Use: "kubernetes [flags] [CONTEXT]", Aliases: []string{"k8s"}, @@ -1055,6 +1056,7 @@ func NewVMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { DBFlagGroup: flag.NewDBFlagGroup(), MisconfFlagGroup: flag.NewMisconfFlagGroup(), ModuleFlagGroup: flag.NewModuleFlagGroup(), + PackageFlagGroup: flag.NewPackageFlagGroup(), RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode ReportFlagGroup: flag.NewReportFlagGroup(), ScanFlagGroup: flag.NewScanFlagGroup(), @@ -1069,7 +1071,7 @@ func NewVMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { }, } vmFlags.ReportFlagGroup.ReportFormat = nil // disable '--report' - vmFlags.ScanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' + vmFlags.PackageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' vmFlags.MisconfFlagGroup.CloudformationParamVars = nil // disable '--cf-params' vmFlags.MisconfFlagGroup.TerraformTFVars = nil // disable '--tf-vars' @@ -1128,9 +1130,8 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { types.VulnerabilityScanner, }) scanFlagGroup := flag.NewScanFlagGroup() - scanFlagGroup.Scanners = scanners // allow only 'vuln' and 'license' options for '--scanners' - scanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' - scanFlagGroup.Parallel = nil // disable '--parallel' + scanFlagGroup.Scanners = scanners // allow only 'vuln' and 'license' options for '--scanners' + scanFlagGroup.Parallel = nil // disable '--parallel' licenseFlagGroup := flag.NewLicenseFlagGroup() // License full-scan and confidence-level are for file content only @@ -1141,6 +1142,7 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { GlobalFlagGroup: globalFlags, CacheFlagGroup: flag.NewCacheFlagGroup(), DBFlagGroup: flag.NewDBFlagGroup(), + PackageFlagGroup: flag.NewPackageFlagGroup(), RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode ReportFlagGroup: reportFlagGroup, ScanFlagGroup: scanFlagGroup, @@ -1150,6 +1152,7 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } sbomFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default + sbomFlags.PackageFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' cmd := &cobra.Command{ Use: "sbom [flags] SBOM_PATH", diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 043109cc0fd5..e49829e352fb 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -479,6 +479,7 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan scanOptions := types.ScanOptions{ PkgTypes: opts.PkgTypes, + PkgRelationships: opts.PkgRelationships, Scanners: opts.Scanners, ImageConfigScanners: opts.ImageConfigScanners, // this is valid only for 'image' subcommand ScanRemovedPackages: opts.ScanRemovedPkgs, // this is valid only for 'image' subcommand @@ -488,18 +489,24 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan } if len(opts.ImageConfigScanners) != 0 { - log.Info("Container image config scanners", log.Any("scanners", opts.ImageConfigScanners)) + log.WithPrefix(log.PrefixContainerImage).Info("Container image config scanners", log.Any("scanners", opts.ImageConfigScanners)) + } + + if opts.Scanners.Enabled(types.SBOMScanner) { + logger := log.WithPrefix(log.PrefixPackage) + logger.Debug("Package types", log.Any("types", scanOptions.PkgTypes)) + logger.Debug("Package relationships", log.Any("relationships", scanOptions.PkgRelationships)) } if opts.Scanners.Enabled(types.VulnerabilityScanner) { - log.Info("Vulnerability scanning is enabled") - log.Debug("Package types", log.Any("types", scanOptions.PkgTypes)) + log.WithPrefix(log.PrefixVulnerability).Info("Vulnerability scanning is enabled") } // ScannerOption is filled only when config scanning is enabled. var configScannerOptions misconf.ScannerOption if opts.Scanners.Enabled(types.MisconfigScanner) || opts.ImageConfigScanners.Enabled(types.MisconfigScanner) { - log.Info("Misconfiguration scanning is enabled") + logger := log.WithPrefix(log.PrefixMisconfiguration) + logger.Info("Misconfiguration scanning is enabled") var downloadedPolicyPaths []string var disableEmbedded bool @@ -507,10 +514,10 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan downloadedPolicyPaths, err := operation.InitBuiltinPolicies(context.Background(), opts.CacheDir, opts.Quiet, opts.SkipCheckUpdate, opts.MisconfOptions.ChecksBundleRepository, opts.RegistryOpts()) if err != nil { if !opts.SkipCheckUpdate { - log.Error("Falling back to embedded checks", log.Err(err)) + logger.Error("Falling back to embedded checks", log.Err(err)) } } else { - log.Debug("Policies successfully loaded from disk") + logger.Debug("Policies successfully loaded from disk") disableEmbedded = true } configScannerOptions = misconf.ScannerOption{ @@ -537,19 +544,21 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan // Do not load config file for secret scanning if opts.Scanners.Enabled(types.SecretScanner) { - log.Info("Secret scanning is enabled") - log.Info("If your scanning is slow, please try '--scanners vuln' to disable secret scanning") + logger := log.WithPrefix(log.PrefixSecret) + logger.Info("Secret scanning is enabled") + logger.Info("If your scanning is slow, please try '--scanners vuln' to disable secret scanning") // e.g. https://aquasecurity.github.io/trivy/latest/docs/scanner/secret/#recommendation - log.Infof("Please see also %s for faster secret detection", doc.URL("/docs/scanner/secret/", "recommendation")) + logger.Info(fmt.Sprintf("Please see also %s for faster secret detection", doc.URL("/docs/scanner/secret/", "recommendation"))) } else { opts.SecretConfigPath = "" } if opts.Scanners.Enabled(types.LicenseScanner) { + logger := log.WithPrefix(log.PrefixLicense) if opts.LicenseFull { - log.Info("Full license scanning is enabled") + logger.Info("Full license scanning is enabled") } else { - log.Info("License scanning is enabled") + logger.Info("License scanning is enabled") } } diff --git a/pkg/fanal/types/package.go b/pkg/fanal/types/package.go index a0734651355d..822291e61c29 100644 --- a/pkg/fanal/types/package.go +++ b/pkg/fanal/types/package.go @@ -20,11 +20,29 @@ const ( RelationshipIndirect ) -var relationshipNames = [...]string{ - "unknown", - "root", - "direct", - "indirect", +var ( + Relationships = []Relationship{ + RelationshipUnknown, + RelationshipRoot, + RelationshipDirect, + RelationshipIndirect, + } + + relationshipNames = [...]string{ + "unknown", + "root", + "direct", + "indirect", + } +) + +func NewRelationship(s string) (Relationship, error) { + for i, name := range relationshipNames { + if s == name { + return Relationship(i), nil + } + } + return RelationshipUnknown, xerrors.Errorf("invalid relationship (%s)", s) } func (r Relationship) String() string { diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 014da145f262..3f48b75ac9b4 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -320,6 +320,7 @@ type Flags struct { LicenseFlagGroup *LicenseFlagGroup MisconfFlagGroup *MisconfFlagGroup ModuleFlagGroup *ModuleFlagGroup + PackageFlagGroup *PackageFlagGroup RemoteFlagGroup *RemoteFlagGroup RegistryFlagGroup *RegistryFlagGroup RegoFlagGroup *RegoFlagGroup @@ -343,6 +344,7 @@ type Options struct { LicenseOptions MisconfOptions ModuleOptions + PackageOptions RegistryOptions RegoOptions RemoteOptions @@ -370,6 +372,12 @@ func (o *Options) Align(f *Flags) error { o.enableSBOM() } + if f.PackageFlagGroup != nil && f.PackageFlagGroup.PkgRelationships != nil && + slices.Compare(o.PkgRelationships, ftypes.Relationships) != 0 && + (o.DependencyTree || slices.Contains(types.SupportedSBOMFormats, o.Format) || len(o.VEXSources) != 0) { + return xerrors.Errorf("'--pkg-relationships' cannot be used with '--dependency-tree', '--vex' or SBOM formats") + } + if o.Compliance.Spec.ID != "" { if viper.IsSet(ScannersFlag.ConfigName) { log.Info(`The option to change scanners is disabled for scanning with the "--compliance" flag. Default scanners used.`) @@ -568,6 +576,9 @@ func (f *Flags) groups() []FlagGroup { if f.K8sFlagGroup != nil { groups = append(groups, f.K8sFlagGroup) } + if f.PackageFlagGroup != nil { + groups = append(groups, f.PackageFlagGroup) + } if f.RemoteFlagGroup != nil { groups = append(groups, f.RemoteFlagGroup) } @@ -707,6 +718,13 @@ func (f *Flags) ToOptions(args []string) (Options, error) { } } + if f.PackageFlagGroup != nil { + opts.PackageOptions, err = f.PackageFlagGroup.ToOptions() + if err != nil { + return Options{}, xerrors.Errorf("package flag error: %w", err) + } + } + if f.RegoFlagGroup != nil { opts.RegoOptions, err = f.RegoFlagGroup.ToOptions() if err != nil { diff --git a/pkg/flag/package_flags.go b/pkg/flag/package_flags.go new file mode 100644 index 000000000000..17abcbfe81c8 --- /dev/null +++ b/pkg/flag/package_flags.go @@ -0,0 +1,91 @@ +package flag + +import ( + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + xstrings "github.com/aquasecurity/trivy/pkg/x/strings" +) + +var ( + IncludeDevDepsFlag = Flag[bool]{ + Name: "include-dev-deps", + ConfigName: "pkg.include-dev-deps", + Usage: "include development dependencies in the report (supported: npm, yarn)", + } + PkgTypesFlag = Flag[[]string]{ + Name: "pkg-types", + ConfigName: "pkg.types", + Default: types.PkgTypes, + Values: types.PkgTypes, + Usage: "list of package types", + Aliases: []Alias{ + { + Name: "vuln-type", + ConfigName: "vulnerability.type", + Deprecated: true, // --vuln-type was renamed to --pkg-types + }, + }, + } + PkgRelationshipsFlag = Flag[[]string]{ + Name: "pkg-relationships", + ConfigName: "pkg.relationships", + Default: xstrings.ToStringSlice(ftypes.Relationships), + Values: xstrings.ToStringSlice(ftypes.Relationships), + Usage: "list of package relationships", + } +) + +// PackageFlagGroup composes common package flag structs. +// These flags affect both SBOM and vulnerability scanning. +type PackageFlagGroup struct { + IncludeDevDeps *Flag[bool] + PkgTypes *Flag[[]string] + PkgRelationships *Flag[[]string] +} + +type PackageOptions struct { + IncludeDevDeps bool + PkgTypes []string + PkgRelationships []ftypes.Relationship +} + +func NewPackageFlagGroup() *PackageFlagGroup { + return &PackageFlagGroup{ + IncludeDevDeps: IncludeDevDepsFlag.Clone(), + PkgTypes: PkgTypesFlag.Clone(), + PkgRelationships: PkgRelationshipsFlag.Clone(), + } +} + +func (f *PackageFlagGroup) Name() string { + return "Package" +} + +func (f *PackageFlagGroup) Flags() []Flagger { + return []Flagger{ + f.IncludeDevDeps, + f.PkgTypes, + f.PkgRelationships, + } +} + +func (f *PackageFlagGroup) ToOptions() (PackageOptions, error) { + if err := parseFlags(f); err != nil { + return PackageOptions{}, err + } + + var relationships []ftypes.Relationship + for _, r := range f.PkgRelationships.Value() { + relationship, err := ftypes.NewRelationship(r) + if err != nil { + return PackageOptions{}, err + } + relationships = append(relationships, relationship) + } + + return PackageOptions{ + IncludeDevDeps: f.IncludeDevDeps.Value(), + PkgTypes: f.PkgTypes.Value(), + PkgRelationships: relationships, + }, nil +} diff --git a/pkg/flag/package_flags_test.go b/pkg/flag/package_flags_test.go new file mode 100644 index 000000000000..b79a149e3891 --- /dev/null +++ b/pkg/flag/package_flags_test.go @@ -0,0 +1,84 @@ +package flag_test + +import ( + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestPackageFlagGroup_ToOptions(t *testing.T) { + type fields struct { + pkgTypes string + pkgRelationships string + } + tests := []struct { + name string + fields fields + want flag.PackageOptions + wantLogs []string + }{ + { + name: "happy default (without flags)", + fields: fields{}, + want: flag.PackageOptions{}, + }, + { + name: "happy path for OS packages", + fields: fields{ + pkgTypes: "os", + }, + want: flag.PackageOptions{ + PkgTypes: []string{ + types.PkgTypeOS, + }, + }, + }, + { + name: "happy path for library packages", + fields: fields{ + pkgTypes: "library", + }, + want: flag.PackageOptions{ + PkgTypes: []string{ + types.PkgTypeLibrary, + }, + }, + }, + { + name: "root and indirect relationships", + fields: fields{ + pkgRelationships: "root,indirect", + }, + want: flag.PackageOptions{ + PkgRelationships: []ftypes.Relationship{ + ftypes.RelationshipRoot, + ftypes.RelationshipIndirect, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Cleanup(viper.Reset) + + setValue(flag.PkgTypesFlag.ConfigName, tt.fields.pkgTypes) + setValue(flag.PkgRelationshipsFlag.ConfigName, tt.fields.pkgRelationships) + + // Assert options + f := &flag.PackageFlagGroup{ + PkgTypes: flag.PkgTypesFlag.Clone(), + PkgRelationships: flag.PkgRelationshipsFlag.Clone(), + } + + got, err := f.ToOptions() + require.NoError(t, err) + assert.EqualExportedValuesf(t, tt.want, got, "PackageFlagGroup") + }) + } +} diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go index b82cc13e706b..ce833cc1b13e 100644 --- a/pkg/flag/report_flags.go +++ b/pkg/flag/report_flags.go @@ -106,20 +106,6 @@ var ( ConfigName: "scan.show-suppressed", Usage: "[EXPERIMENTAL] show suppressed vulnerabilities", } - PkgTypesFlag = Flag[[]string]{ - Name: "pkg-types", - ConfigName: "pkg-types", - Default: types.PkgTypes, - Values: types.PkgTypes, - Usage: "comma-separated list of package types", - Aliases: []Alias{ - { - Name: "vuln-type", - ConfigName: "vulnerability.type", - Deprecated: true, // --vuln-type was renamed to --pkg-types - }, - }, - } ) // ReportFlagGroup composes common printer flag structs @@ -139,7 +125,6 @@ type ReportFlagGroup struct { Severity *Flag[[]string] Compliance *Flag[string] ShowSuppressed *Flag[bool] - PkgTypes *Flag[[]string] } type ReportOptions struct { @@ -157,7 +142,6 @@ type ReportOptions struct { Severities []dbTypes.Severity Compliance spec.ComplianceSpec ShowSuppressed bool - PkgTypes []string } func NewReportFlagGroup() *ReportFlagGroup { @@ -176,7 +160,6 @@ func NewReportFlagGroup() *ReportFlagGroup { Severity: SeverityFlag.Clone(), Compliance: ComplianceFlag.Clone(), ShowSuppressed: ShowSuppressedFlag.Clone(), - PkgTypes: PkgTypesFlag.Clone(), } } @@ -200,7 +183,6 @@ func (f *ReportFlagGroup) Flags() []Flagger { f.Severity, f.Compliance, f.ShowSuppressed, - f.PkgTypes, } } @@ -270,7 +252,6 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { Severities: toSeverity(f.Severity.Value()), Compliance: cs, ShowSuppressed: f.ShowSuppressed.Value(), - PkgTypes: f.PkgTypes.Value(), }, nil } diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go index 108d95e22a70..9440bf373905 100644 --- a/pkg/flag/report_flags_test.go +++ b/pkg/flag/report_flags_test.go @@ -160,28 +160,6 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { Severities: []dbTypes.Severity{dbTypes.SeverityLow}, }, }, - { - name: "happy path for OS packages", - fields: fields{ - pkgTypes: "os", - }, - want: flag.ReportOptions{ - PkgTypes: []string{ - types.PkgTypeOS, - }, - }, - }, - { - name: "happy path for library packages", - fields: fields{ - pkgTypes: "library", - }, - want: flag.ReportOptions{ - PkgTypes: []string{ - types.PkgTypeLibrary, - }, - }, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -206,7 +184,6 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { setValue(flag.OutputPluginArgFlag.ConfigName, tt.fields.outputPluginArgs) setValue(flag.SeverityFlag.ConfigName, tt.fields.severities) setValue(flag.ComplianceFlag.ConfigName, tt.fields.compliance) - setValue(flag.PkgTypesFlag.ConfigName, tt.fields.pkgTypes) // Assert options f := &flag.ReportFlagGroup{ @@ -222,7 +199,6 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { OutputPluginArg: flag.OutputPluginArgFlag.Clone(), Severity: flag.SeverityFlag.Clone(), Compliance: flag.ComplianceFlag.Clone(), - PkgTypes: flag.PkgTypesFlag.Clone(), } got, err := f.ToOptions() diff --git a/pkg/flag/scan_flags.go b/pkg/flag/scan_flags.go index 07f14a08551a..b413fce01fe9 100644 --- a/pkg/flag/scan_flags.go +++ b/pkg/flag/scan_flags.go @@ -96,51 +96,43 @@ var ( Default: "https://rekor.sigstore.dev", Usage: "[EXPERIMENTAL] address of rekor STL server", } - IncludeDevDepsFlag = Flag[bool]{ - Name: "include-dev-deps", - ConfigName: "scan.include-dev-deps", - Usage: "include development dependencies in the report (supported: npm, yarn)", - } ) type ScanFlagGroup struct { - SkipDirs *Flag[[]string] - SkipFiles *Flag[[]string] - OfflineScan *Flag[bool] - Scanners *Flag[[]string] - FilePatterns *Flag[[]string] - Slow *Flag[bool] // deprecated - Parallel *Flag[int] - SBOMSources *Flag[[]string] - RekorURL *Flag[string] - IncludeDevDeps *Flag[bool] + SkipDirs *Flag[[]string] + SkipFiles *Flag[[]string] + OfflineScan *Flag[bool] + Scanners *Flag[[]string] + FilePatterns *Flag[[]string] + Slow *Flag[bool] // deprecated + Parallel *Flag[int] + SBOMSources *Flag[[]string] + RekorURL *Flag[string] } type ScanOptions struct { - Target string - SkipDirs []string - SkipFiles []string - OfflineScan bool - Scanners types.Scanners - FilePatterns []string - Parallel int - SBOMSources []string - RekorURL string - IncludeDevDeps bool + Target string + SkipDirs []string + SkipFiles []string + OfflineScan bool + Scanners types.Scanners + FilePatterns []string + Parallel int + SBOMSources []string + RekorURL string } func NewScanFlagGroup() *ScanFlagGroup { return &ScanFlagGroup{ - SkipDirs: SkipDirsFlag.Clone(), - SkipFiles: SkipFilesFlag.Clone(), - OfflineScan: OfflineScanFlag.Clone(), - Scanners: ScannersFlag.Clone(), - FilePatterns: FilePatternsFlag.Clone(), - Parallel: ParallelFlag.Clone(), - SBOMSources: SBOMSourcesFlag.Clone(), - RekorURL: RekorURLFlag.Clone(), - IncludeDevDeps: IncludeDevDepsFlag.Clone(), - Slow: SlowFlag.Clone(), + SkipDirs: SkipDirsFlag.Clone(), + SkipFiles: SkipFilesFlag.Clone(), + OfflineScan: OfflineScanFlag.Clone(), + Scanners: ScannersFlag.Clone(), + FilePatterns: FilePatternsFlag.Clone(), + Parallel: ParallelFlag.Clone(), + SBOMSources: SBOMSourcesFlag.Clone(), + RekorURL: RekorURLFlag.Clone(), + Slow: SlowFlag.Clone(), } } @@ -159,7 +151,6 @@ func (f *ScanFlagGroup) Flags() []Flagger { f.Parallel, f.SBOMSources, f.RekorURL, - f.IncludeDevDeps, } } @@ -180,15 +171,14 @@ func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { } return ScanOptions{ - Target: target, - SkipDirs: f.SkipDirs.Value(), - SkipFiles: f.SkipFiles.Value(), - OfflineScan: f.OfflineScan.Value(), - Scanners: xstrings.ToTSlice[types.Scanner](f.Scanners.Value()), - FilePatterns: f.FilePatterns.Value(), - Parallel: parallel, - SBOMSources: f.SBOMSources.Value(), - RekorURL: f.RekorURL.Value(), - IncludeDevDeps: f.IncludeDevDeps.Value(), + Target: target, + SkipDirs: f.SkipDirs.Value(), + SkipFiles: f.SkipFiles.Value(), + OfflineScan: f.OfflineScan.Value(), + Scanners: xstrings.ToTSlice[types.Scanner](f.Scanners.Value()), + FilePatterns: f.FilePatterns.Value(), + Parallel: parallel, + SBOMSources: f.SBOMSources.Value(), + RekorURL: f.RekorURL.Value(), }, nil } diff --git a/pkg/log/logger.go b/pkg/log/logger.go index 1f0b8e32e1a2..6eb8bde70dc2 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -17,6 +17,13 @@ const ( LevelWarn = slog.LevelWarn LevelError = slog.LevelError LevelFatal = slog.Level(12) + + PrefixContainerImage = "image" + PrefixPackage = "pkg" + PrefixVulnerability = "vuln" + PrefixMisconfiguration = "misconfig" + PrefixSecret = "secret" + PrefixLicense = "license" ) // Logger is an alias of slog.Logger diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index d1ac1d8d829f..9e13db8ded48 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -83,6 +83,7 @@ func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys BlobIds: blobKeys, Options: &rpc.ScanOptions{ PkgTypes: opts.PkgTypes, + PkgRelationships: xstrings.ToStringSlice(opts.PkgRelationships), Scanners: xstrings.ToStringSlice(opts.Scanners), LicenseCategories: licenseCategories, IncludeDevDeps: opts.IncludeDevDeps, diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index b0e58f87c0e9..bf4fa2ff5092 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -44,27 +44,44 @@ func teeError(err error) error { // Scan scans and return response func (s *ScanServer) Scan(ctx context.Context, in *rpcScanner.ScanRequest) (*rpcScanner.ScanResponse, error) { - scanners := lo.Map(in.Options.Scanners, func(s string, index int) types.Scanner { + options := s.ToOptions(in.Options) + results, os, err := s.localScanner.Scan(ctx, in.Target, in.ArtifactId, in.BlobIds, options) + if err != nil { + return nil, teeError(xerrors.Errorf("failed scan, %s: %w", in.Target, err)) + } + + return rpc.ConvertToRPCScanResponse(results, os), nil +} + +func (s *ScanServer) ToOptions(in *rpcScanner.ScanOptions) types.ScanOptions { + pkgRelationships := lo.FilterMap(in.PkgRelationships, func(r string, index int) (ftypes.Relationship, bool) { + rel, err := ftypes.NewRelationship(r) + if err != nil { + log.Warnf("Invalid relationship: %s", r) + return ftypes.RelationshipUnknown, false + } + return rel, true + }) + if len(pkgRelationships) == 0 { + pkgRelationships = ftypes.Relationships // For backward compatibility + } + + scanners := lo.Map(in.Scanners, func(s string, index int) types.Scanner { return types.Scanner(s) }) - licenseCategories := lo.MapEntries(in.Options.LicenseCategories, + licenseCategories := lo.MapEntries(in.LicenseCategories, func(k string, v *rpcScanner.Licenses) (ftypes.LicenseCategory, []string) { return ftypes.LicenseCategory(k), v.Names }) - options := types.ScanOptions{ - PkgTypes: in.Options.PkgTypes, + return types.ScanOptions{ + PkgTypes: in.PkgTypes, + PkgRelationships: pkgRelationships, Scanners: scanners, - IncludeDevDeps: in.Options.IncludeDevDeps, + IncludeDevDeps: in.IncludeDevDeps, LicenseCategories: licenseCategories, } - results, os, err := s.localScanner.Scan(ctx, in.Target, in.ArtifactId, in.BlobIds, options) - if err != nil { - return nil, teeError(xerrors.Errorf("failed scan, %s: %w", in.Target, err)) - } - - return rpc.ConvertToRPCScanResponse(results, os), nil } // CacheServer implements the cache diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 7526ff48dd4c..3b0c719721e2 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -108,9 +108,8 @@ func (s Scanner) Scan(ctx context.Context, targetName, artifactKey string, blobK func (s Scanner) ScanTarget(ctx context.Context, target types.ScanTarget, options types.ScanOptions) (types.Results, ftypes.OS, error) { var results types.Results - // By default, we need to remove dev dependencies from the result - // IncludeDevDeps option allows you not to remove them - excludeDevDeps(target.Applications, options.IncludeDevDeps) + // Filter packages according to the options + excludePackages(&target, options) // Add packages if needed and scan packages for vulnerabilities vulnResults, eosl, err := s.scanVulnerabilities(ctx, target, options) @@ -395,6 +394,32 @@ func ShouldScanMisconfigOrRbac(scanners types.Scanners) bool { return scanners.AnyEnabled(types.MisconfigScanner, types.RBACScanner) } +func excludePackages(target *types.ScanTarget, options types.ScanOptions) { + // Filter packages by relationship + filterPkgByRelationship(target, options) + + // By default, development packages are removed from the result + // '--include-dev-deps' option allows including them + excludeDevDeps(target.Applications, options.IncludeDevDeps) +} + +func filterPkgByRelationship(target *types.ScanTarget, options types.ScanOptions) { + if slices.Compare(options.PkgRelationships, ftypes.Relationships) == 0 { + return // No need to filter + } + + filter := func(pkgs []ftypes.Package) []ftypes.Package { + return lo.Filter(pkgs, func(pkg ftypes.Package, index int) bool { + return slices.Contains(options.PkgRelationships, pkg.Relationship) + }) + } + + target.Packages = filter(target.Packages) + for i, app := range target.Applications { + target.Applications[i].Packages = filter(app.Packages) + } +} + // excludeDevDeps removes development dependencies from the list of applications func excludeDevDeps(apps []ftypes.Application, include bool) { if include { diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go index a80a4bd281c8..7d60697fa23a 100644 --- a/pkg/scanner/local/scan_test.go +++ b/pkg/scanner/local/scan_test.go @@ -65,8 +65,17 @@ var ( Licenses: []string{"MIT"}, } laravelPkg = ftypes.Package{ - Name: "laravel/framework", - Version: "6.0.0", + Name: "laravel/framework", + Version: "6.0.0", + Relationship: ftypes.RelationshipDirect, + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + } + guzzlePkg = ftypes.Package{ + Name: "guzzlehttp/guzzle", + Version: "7.9.2", + Relationship: ftypes.RelationshipIndirect, Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, @@ -98,7 +107,8 @@ func TestScanner_Scan(t *testing.T) { types.PkgTypeOS, types.PkgTypeLibrary, }, - Scanners: types.Scanners{types.VulnerabilityScanner}, + PkgRelationships: ftypes.Relationships, + Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, @@ -198,7 +208,8 @@ func TestScanner_Scan(t *testing.T) { target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - Scanners: types.Scanners{types.LicenseScanner}, + PkgRelationships: ftypes.Relationships, + Scanners: types.Scanners{types.LicenseScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, @@ -298,7 +309,8 @@ func TestScanner_Scan(t *testing.T) { types.PkgTypeOS, types.PkgTypeLibrary, }, - Scanners: types.Scanners{types.VulnerabilityScanner}, + PkgRelationships: ftypes.Relationships, + Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, @@ -377,8 +389,9 @@ func TestScanner_Scan(t *testing.T) { target: "./result.cdx", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - PkgTypes: []string{types.PkgTypeLibrary}, - Scanners: types.Scanners{types.VulnerabilityScanner}, + PkgTypes: []string{types.PkgTypeLibrary}, + PkgRelationships: ftypes.Relationships, + Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, @@ -477,7 +490,8 @@ func TestScanner_Scan(t *testing.T) { types.PkgTypeOS, types.PkgTypeLibrary, }, - Scanners: types.Scanners{types.VulnerabilityScanner}, + PkgRelationships: ftypes.Relationships, + Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, @@ -558,7 +572,8 @@ func TestScanner_Scan(t *testing.T) { types.PkgTypeOS, types.PkgTypeLibrary, }, - Scanners: types.Scanners{types.VulnerabilityScanner}, + PkgRelationships: ftypes.Relationships, + Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, @@ -630,7 +645,8 @@ func TestScanner_Scan(t *testing.T) { types.PkgTypeOS, types.PkgTypeLibrary, }, - Scanners: types.Scanners{types.VulnerabilityScanner}, + PkgRelationships: ftypes.Relationships, + Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, @@ -645,12 +661,17 @@ func TestScanner_Scan(t *testing.T) { wantResults: nil, }, { - name: "happy path with only language-specific package detection", + name: "happy path with only language-specific package detection, excluding direct packages", args: args{ target: "alpine:latest", layerIDs: []string{"sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33"}, options: types.ScanOptions{ PkgTypes: []string{types.PkgTypeLibrary}, + PkgRelationships: []ftypes.Relationship{ + ftypes.RelationshipUnknown, + ftypes.RelationshipRoot, + ftypes.RelationshipIndirect, + }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, @@ -680,7 +701,8 @@ func TestScanner_Scan(t *testing.T) { Type: "composer", FilePath: "/app/composer-lock.json", Packages: []ftypes.Package{ - laravelPkg, + laravelPkg, // will be excluded + guzzlePkg, }, }, }, @@ -721,19 +743,7 @@ func TestScanner_Scan(t *testing.T) { Target: "/app/composer-lock.json", Class: types.ClassLangPkg, Type: ftypes.Composer, - Packages: ftypes.Packages{laravelPkg}, - Vulnerabilities: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2021-21263", - PkgName: laravelPkg.Name, - InstalledVersion: laravelPkg.Version, - FixedVersion: "8.22.1, 7.30.3, 6.20.12", - Status: dbTypes.StatusFixed, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, - }, + Packages: ftypes.Packages{guzzlePkg}, }, }, wantOS: ftypes.OS{ @@ -900,7 +910,8 @@ func TestScanner_Scan(t *testing.T) { types.PkgTypeOS, types.PkgTypeLibrary, }, - Scanners: types.Scanners{types.VulnerabilityScanner}, + PkgRelationships: ftypes.Relationships, + Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, @@ -920,8 +931,9 @@ func TestScanner_Scan(t *testing.T) { target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - PkgTypes: []string{types.PkgTypeLibrary}, - Scanners: types.Scanners{types.VulnerabilityScanner}, + PkgTypes: []string{types.PkgTypeLibrary}, + PkgRelationships: ftypes.Relationships, + Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/sad.yaml"}, @@ -1103,8 +1115,7 @@ func TestScanner_Scan(t *testing.T) { s := NewScanner(applier, ospkg.NewScanner(), langpkg.NewScanner(), vulnerability.NewClient(db.Config{})) gotResults, gotOS, err := s.Scan(context.Background(), tt.args.target, "", tt.args.layerIDs, tt.args.options) if tt.wantErr != "" { - require.Error(t, err, tt.name) - require.Contains(t, err.Error(), tt.wantErr, tt.name) + require.ErrorContains(t, err, tt.wantErr, tt.name) return } diff --git a/pkg/types/scan.go b/pkg/types/scan.go index 2151cb9ccd24..09b90e745c58 100644 --- a/pkg/types/scan.go +++ b/pkg/types/scan.go @@ -23,6 +23,7 @@ type ScanTarget struct { // ScanOptions holds the attributes for scanning vulnerabilities type ScanOptions struct { PkgTypes []string + PkgRelationships []types.Relationship Scanners Scanners ImageConfigScanners Scanners // Scanners for container image configuration ScanRemovedPackages bool diff --git a/pkg/x/strings/strings.go b/pkg/x/strings/strings.go index 78c87231605a..43a852f64008 100644 --- a/pkg/x/strings/strings.go +++ b/pkg/x/strings/strings.go @@ -1,17 +1,28 @@ package strings -import "github.com/samber/lo" +import ( + "fmt" + + "github.com/samber/lo" +) type String interface { ~string } -func ToStringSlice[T String](ss []T) []string { - if ss == nil { +func ToStringSlice[T any](ss []T) []string { + if len(ss) == 0 { return nil } return lo.Map(ss, func(s T, _ int) string { - return string(s) + switch v := any(s).(type) { + case string: + return v + case fmt.Stringer: + return v.String() + default: + return fmt.Sprint(v) + } }) } diff --git a/rpc/scanner/service.pb.go b/rpc/scanner/service.pb.go index 2f03d4727662..1e061d5764e1 100644 --- a/rpc/scanner/service.pb.go +++ b/rpc/scanner/service.pb.go @@ -150,6 +150,7 @@ type ScanOptions struct { Scanners []string `protobuf:"bytes,2,rep,name=scanners,proto3" json:"scanners,omitempty"` LicenseCategories map[string]*Licenses `protobuf:"bytes,4,rep,name=license_categories,json=licenseCategories,proto3" json:"license_categories,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` IncludeDevDeps bool `protobuf:"varint,5,opt,name=include_dev_deps,json=includeDevDeps,proto3" json:"include_dev_deps,omitempty"` + PkgRelationships []string `protobuf:"bytes,6,rep,name=pkg_relationships,json=pkgRelationships,proto3" json:"pkg_relationships,omitempty"` } func (x *ScanOptions) Reset() { @@ -212,6 +213,13 @@ func (x *ScanOptions) GetIncludeDevDeps() bool { return false } +func (x *ScanOptions) GetPkgRelationships() []string { + if x != nil { + return x.PkgRelationships + } + return nil +} + type ScanResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -398,7 +406,7 @@ var file_rpc_scanner_service_proto_rawDesc = []byte{ 0x53, 0x63, 0x61, 0x6e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x20, 0x0a, 0x08, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xbd, 0x02, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x4f, + 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xea, 0x02, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6b, 0x67, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18, @@ -411,59 +419,61 @@ var file_rpc_scanner_service_proto_rawDesc = []byte{ 0x79, 0x52, 0x11, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x65, 0x76, 0x5f, 0x64, 0x65, 0x70, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, - 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, 0x65, 0x76, 0x44, 0x65, 0x70, 0x73, 0x1a, 0x60, - 0x0a, 0x16, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, - 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x63, - 0x65, 0x6e, 0x73, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x64, 0x0a, 0x0c, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x4f, 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x32, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0xd5, 0x03, 0x0a, - 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, - 0x45, 0x0a, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, - 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, - 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x54, 0x0a, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, - 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, - 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, - 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, - 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x10, 0x63, 0x75, 0x73, - 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x52, 0x0f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x12, 0x35, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x52, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x6c, 0x69, 0x63, - 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x72, - 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, - 0x74, 0x65, 0x64, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, - 0x6e, 0x73, 0x65, 0x73, 0x32, 0x50, 0x0a, 0x07, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, - 0x45, 0x0a, 0x04, 0x53, 0x63, 0x61, 0x6e, 0x12, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, - 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, - 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, - 0x79, 0x2f, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, - 0x6e, 0x65, 0x72, 0x3b, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, 0x65, 0x76, 0x44, 0x65, 0x70, 0x73, 0x12, 0x2b, + 0x0a, 0x11, 0x70, 0x6b, 0x67, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, + 0x69, 0x70, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x70, 0x6b, 0x67, 0x52, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x1a, 0x60, 0x0a, 0x16, 0x4c, + 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, + 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, + 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, + 0x03, 0x10, 0x04, 0x22, 0x64, 0x0a, 0x0c, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, + 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x32, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, + 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0xd5, 0x03, 0x0a, 0x06, 0x52, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x45, 0x0a, 0x0f, + 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, + 0x74, 0x79, 0x52, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, + 0x69, 0x65, 0x73, 0x12, 0x54, 0x0a, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, + 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, + 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x61, + 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x08, 0x70, 0x61, + 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0f, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, + 0x35, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x73, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, + 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, + 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, + 0x73, 0x32, 0x50, 0x0a, 0x07, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x04, + 0x53, 0x63, 0x61, 0x6e, 0x12, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, + 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, + 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, + 0x3b, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/rpc/scanner/service.proto b/rpc/scanner/service.proto index 8fc1cdbd46ac..c7ad3748280b 100644 --- a/rpc/scanner/service.proto +++ b/rpc/scanner/service.proto @@ -27,6 +27,7 @@ message ScanOptions { repeated string scanners = 2; map license_categories = 4; bool include_dev_deps = 5; + repeated string pkg_relationships = 6; reserved 3; // deleted 'list_all_packages' } diff --git a/rpc/scanner/service.twirp.go b/rpc/scanner/service.twirp.go index c877fbe34523..83a834536f35 100644 --- a/rpc/scanner/service.twirp.go +++ b/rpc/scanner/service.twirp.go @@ -1094,46 +1094,47 @@ func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) } var twirpFileDescriptor0 = []byte{ - // 650 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xcd, 0x6e, 0xdb, 0x38, - 0x10, 0x86, 0x7f, 0x62, 0xcb, 0xe3, 0xc5, 0xc6, 0x21, 0x76, 0x03, 0xc5, 0xd9, 0x6c, 0x0d, 0x1f, - 0x0a, 0x9f, 0xec, 0xc6, 0x69, 0xd1, 0xbf, 0x5b, 0x93, 0xb4, 0x48, 0xd1, 0x22, 0x01, 0x1d, 0xf4, - 0xd0, 0x8b, 0x4b, 0x53, 0x13, 0x95, 0xb0, 0x2c, 0x29, 0x1c, 0xca, 0x80, 0x5f, 0xa5, 0xef, 0xd2, - 0xc7, 0xe8, 0xfb, 0x14, 0x22, 0x25, 0x23, 0x76, 0x92, 0x9e, 0xc4, 0x99, 0xf9, 0xe6, 0x9b, 0x0f, - 0x9c, 0x4f, 0x84, 0x03, 0x9d, 0xca, 0x11, 0x49, 0x11, 0xc7, 0xa8, 0x47, 0x84, 0x7a, 0xa9, 0x24, - 0x0e, 0x53, 0x9d, 0x98, 0x84, 0x75, 0x8c, 0x56, 0xcb, 0xd5, 0xb0, 0x28, 0x0e, 0x97, 0xc7, 0x5d, - 0x3f, 0x07, 0xcb, 0x64, 0xb1, 0x48, 0xe2, 0x4d, 0x6c, 0xff, 0x47, 0x05, 0xda, 0x13, 0x29, 0x62, - 0x8e, 0xb7, 0x19, 0x92, 0x61, 0xfb, 0xd0, 0x30, 0x42, 0x87, 0x68, 0xfc, 0x4a, 0xaf, 0x32, 0x68, - 0xf1, 0x22, 0x62, 0x4f, 0xa0, 0x2d, 0xb4, 0x51, 0x37, 0x42, 0x9a, 0xa9, 0x0a, 0xfc, 0xaa, 0x2d, - 0x42, 0x99, 0xba, 0x08, 0xd8, 0x01, 0x78, 0xb3, 0x28, 0x99, 0x4d, 0x55, 0x40, 0x7e, 0xad, 0x57, - 0x1b, 0xb4, 0x78, 0x33, 0x8f, 0x2f, 0x02, 0x62, 0x2f, 0xa1, 0x99, 0xa4, 0x46, 0x25, 0x31, 0xf9, - 0xf5, 0x5e, 0x65, 0xd0, 0x1e, 0x1f, 0x0d, 0xb7, 0x15, 0x0e, 0x73, 0x0d, 0x97, 0x0e, 0xc4, 0x4b, - 0x74, 0xbf, 0x07, 0xde, 0x27, 0x25, 0x31, 0x26, 0x24, 0xf6, 0x0f, 0xec, 0xc4, 0x62, 0x81, 0xe4, - 0x57, 0x2c, 0xb9, 0x0b, 0xfa, 0x3f, 0xab, 0x4e, 0x7e, 0xd1, 0xca, 0x0e, 0xa1, 0x95, 0xce, 0xc3, - 0xa9, 0x59, 0xa5, 0x6b, 0xa4, 0x97, 0xce, 0xc3, 0xeb, 0x3c, 0x66, 0x5d, 0xf0, 0x8a, 0x89, 0xe4, - 0x57, 0x5d, 0xad, 0x8c, 0x99, 0x04, 0x16, 0xb9, 0x51, 0x53, 0x29, 0x0c, 0x86, 0x89, 0x56, 0x98, - 0xcb, 0xad, 0x0d, 0xda, 0xe3, 0xe7, 0x7f, 0x94, 0x3b, 0x2c, 0x24, 0x9e, 0xae, 0xdb, 0xce, 0x63, - 0xa3, 0x57, 0x7c, 0x2f, 0xda, 0xce, 0xb3, 0x01, 0x74, 0x54, 0x2c, 0xa3, 0x2c, 0xc0, 0x69, 0x80, - 0xcb, 0x69, 0x80, 0x29, 0xf9, 0x3b, 0xbd, 0xca, 0xc0, 0xe3, 0x7f, 0x17, 0xf9, 0x33, 0x5c, 0x9e, - 0x61, 0x4a, 0xdd, 0x6f, 0xb0, 0xff, 0x30, 0x2d, 0xeb, 0x40, 0x6d, 0x8e, 0xab, 0x62, 0x3b, 0xf9, - 0x91, 0x3d, 0x83, 0x9d, 0xa5, 0x88, 0x32, 0xb4, 0x4b, 0x69, 0x8f, 0xbb, 0xf7, 0xd5, 0x96, 0x97, - 0xc8, 0x1d, 0xf0, 0x4d, 0xf5, 0x55, 0xe5, 0x63, 0xdd, 0xab, 0x75, 0xea, 0xfd, 0x00, 0xfe, 0x72, - 0xdb, 0xa7, 0x34, 0x89, 0x09, 0x59, 0x0f, 0xaa, 0x09, 0x59, 0xf2, 0xf6, 0xb8, 0x53, 0x10, 0x39, - 0xdf, 0x0c, 0x2f, 0x27, 0xbc, 0x9a, 0x10, 0x1b, 0x43, 0x53, 0x23, 0x65, 0x91, 0x71, 0x6b, 0x6e, - 0x8f, 0xfd, 0xfb, 0xf3, 0xb8, 0x05, 0xf0, 0x12, 0xd8, 0xff, 0x55, 0x83, 0x86, 0xcb, 0x3d, 0xea, - 0xaf, 0x73, 0xd8, 0x5d, 0x66, 0x51, 0x8c, 0x5a, 0xcc, 0x54, 0xa4, 0x4c, 0x7e, 0xf9, 0x55, 0x4b, - 0x7f, 0xb8, 0xa9, 0xe2, 0xcb, 0x1d, 0xd0, 0x8a, 0x6f, 0xf7, 0xb0, 0x6b, 0xd8, 0x5b, 0x28, 0x92, - 0x49, 0x7c, 0xa3, 0xc2, 0x4c, 0x8b, 0xd2, 0x74, 0x39, 0xd1, 0xd3, 0x4d, 0xa2, 0x33, 0x34, 0x28, - 0x0d, 0x06, 0x9f, 0xb7, 0xe0, 0xfc, 0x3e, 0x41, 0xee, 0x3d, 0x19, 0x09, 0x22, 0xbf, 0x61, 0x35, - 0xbb, 0x80, 0x31, 0xa8, 0xe7, 0x3e, 0xf3, 0x6b, 0x36, 0x69, 0xcf, 0xec, 0x18, 0xbc, 0x54, 0xc8, - 0xb9, 0x08, 0x31, 0xdf, 0x6c, 0x3e, 0xf6, 0xdf, 0xcd, 0xb1, 0x57, 0xae, 0xca, 0xd7, 0x30, 0xf6, - 0x01, 0x3a, 0x32, 0x23, 0x93, 0x2c, 0xa6, 0x1a, 0x29, 0xc9, 0xb4, 0x44, 0xf2, 0x9b, 0xb6, 0xf5, - 0xbf, 0xcd, 0xd6, 0x53, 0x8b, 0xe2, 0x05, 0x88, 0xef, 0xca, 0x8d, 0x98, 0xd8, 0x0b, 0x68, 0x12, - 0x4a, 0x8d, 0x86, 0x7c, 0xef, 0xa1, 0xab, 0x9b, 0xd8, 0xe2, 0x7b, 0x15, 0x07, 0x2a, 0x0e, 0x79, - 0x89, 0x65, 0xaf, 0xc1, 0x2b, 0x9c, 0x4a, 0x7e, 0xcb, 0xf6, 0x1d, 0x3d, 0x7c, 0x53, 0x85, 0x8b, - 0xf8, 0x1a, 0x3e, 0xbe, 0x82, 0xe6, 0xc4, 0x6d, 0x9d, 0x9d, 0x43, 0x3d, 0x3f, 0xb2, 0x47, 0x7e, - 0xed, 0xe2, 0x79, 0xe9, 0xfe, 0xff, 0x58, 0xd9, 0xf9, 0xef, 0xdd, 0xc9, 0xd7, 0xe3, 0x50, 0x99, - 0xef, 0xd9, 0x2c, 0x1f, 0x3e, 0x12, 0xb7, 0x99, 0x20, 0x94, 0x99, 0x56, 0x66, 0x35, 0xb2, 0x8d, - 0xa3, 0x3b, 0xaf, 0xde, 0xdb, 0xe2, 0x3b, 0x6b, 0xd8, 0xa7, 0xec, 0xe4, 0x77, 0x00, 0x00, 0x00, - 0xff, 0xff, 0x50, 0x58, 0x22, 0xc7, 0x13, 0x05, 0x00, 0x00, + // 671 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xdd, 0x6e, 0xd3, 0x4c, + 0x10, 0x55, 0x7e, 0x9a, 0x38, 0x93, 0x4f, 0x5f, 0xd3, 0x15, 0x54, 0x6e, 0x4a, 0x21, 0xca, 0x05, + 0x8a, 0x84, 0x94, 0xd0, 0x14, 0xc4, 0xdf, 0x1d, 0x6d, 0x41, 0x45, 0xa0, 0x56, 0x9b, 0x8a, 0x0b, + 0x6e, 0xc2, 0x66, 0x3d, 0x75, 0x57, 0x71, 0x6c, 0x77, 0x67, 0x1d, 0x29, 0xaf, 0xc2, 0x7b, 0xf1, + 0x12, 0x3c, 0x05, 0xda, 0xb5, 0x13, 0x35, 0x69, 0xcb, 0x95, 0x77, 0x66, 0xce, 0xcc, 0x39, 0xde, + 0x39, 0x5a, 0xd8, 0xd3, 0xa9, 0x1c, 0x90, 0x14, 0x71, 0x8c, 0x7a, 0x40, 0xa8, 0xe7, 0x4a, 0x62, + 0x3f, 0xd5, 0x89, 0x49, 0x58, 0xcb, 0x68, 0x35, 0x5f, 0xf4, 0x8b, 0x62, 0x7f, 0x7e, 0xd8, 0xf6, + 0x2d, 0x58, 0x26, 0xb3, 0x59, 0x12, 0xaf, 0x63, 0xbb, 0xbf, 0x4a, 0xd0, 0x1c, 0x49, 0x11, 0x73, + 0xbc, 0xc9, 0x90, 0x0c, 0xdb, 0x85, 0x9a, 0x11, 0x3a, 0x44, 0xe3, 0x97, 0x3a, 0xa5, 0x5e, 0x83, + 0x17, 0x11, 0x7b, 0x06, 0x4d, 0xa1, 0x8d, 0xba, 0x12, 0xd2, 0x8c, 0x55, 0xe0, 0x97, 0x5d, 0x11, + 0x96, 0xa9, 0xb3, 0x80, 0xed, 0x81, 0x37, 0x89, 0x92, 0xc9, 0x58, 0x05, 0xe4, 0x57, 0x3a, 0x95, + 0x5e, 0x83, 0xd7, 0x6d, 0x7c, 0x16, 0x10, 0x7b, 0x03, 0xf5, 0x24, 0x35, 0x2a, 0x89, 0xc9, 0xaf, + 0x76, 0x4a, 0xbd, 0xe6, 0xf0, 0xa0, 0xbf, 0xa9, 0xb0, 0x6f, 0x35, 0x9c, 0xe7, 0x20, 0xbe, 0x44, + 0x77, 0x3b, 0xe0, 0x7d, 0x55, 0x12, 0x63, 0x42, 0x62, 0x8f, 0x60, 0x2b, 0x16, 0x33, 0x24, 0xbf, + 0xe4, 0x86, 0xe7, 0x41, 0xf7, 0x4f, 0x39, 0x97, 0x5f, 0xb4, 0xb2, 0x7d, 0x68, 0xa4, 0xd3, 0x70, + 0x6c, 0x16, 0xe9, 0x0a, 0xe9, 0xa5, 0xd3, 0xf0, 0xd2, 0xc6, 0xac, 0x0d, 0x5e, 0xc1, 0x48, 0x7e, + 0x39, 0xaf, 0x2d, 0x63, 0x26, 0x81, 0x45, 0x39, 0xd5, 0x58, 0x0a, 0x83, 0x61, 0xa2, 0x15, 0x5a, + 0xb9, 0x95, 0x5e, 0x73, 0xf8, 0xea, 0x9f, 0x72, 0xfb, 0x85, 0xc4, 0xe3, 0x55, 0xdb, 0x69, 0x6c, + 0xf4, 0x82, 0xef, 0x44, 0x9b, 0x79, 0xd6, 0x83, 0x96, 0x8a, 0x65, 0x94, 0x05, 0x38, 0x0e, 0x70, + 0x3e, 0x0e, 0x30, 0x25, 0x7f, 0xab, 0x53, 0xea, 0x79, 0xfc, 0xff, 0x22, 0x7f, 0x82, 0xf3, 0x13, + 0x4c, 0x89, 0xbd, 0x80, 0x1d, 0xfb, 0x1f, 0x1a, 0x23, 0xe1, 0x48, 0xae, 0x55, 0x4a, 0x7e, 0xcd, + 0x69, 0x6e, 0xa5, 0xd3, 0x90, 0xdf, 0xce, 0xb7, 0x7f, 0xc2, 0xee, 0xfd, 0x1a, 0x58, 0x0b, 0x2a, + 0x53, 0x5c, 0x14, 0xab, 0xb4, 0x47, 0xf6, 0x12, 0xb6, 0xe6, 0x22, 0xca, 0xd0, 0x6d, 0xb0, 0x39, + 0x6c, 0xdf, 0xfd, 0xb5, 0xe5, 0x8d, 0xf3, 0x1c, 0xf8, 0xbe, 0xfc, 0xb6, 0xf4, 0xa5, 0xea, 0x55, + 0x5a, 0xd5, 0x6e, 0x00, 0xff, 0xe5, 0x56, 0xa1, 0x34, 0x89, 0x09, 0x59, 0x07, 0xca, 0x09, 0xb9, + 0xe1, 0xcd, 0x61, 0xab, 0x18, 0x94, 0x9b, 0xac, 0x7f, 0x3e, 0xe2, 0xe5, 0x84, 0xd8, 0x10, 0xea, + 0x1a, 0x29, 0x8b, 0x4c, 0xee, 0x89, 0xe6, 0xd0, 0xbf, 0xcb, 0xc7, 0x1d, 0x80, 0x2f, 0x81, 0xdd, + 0xdf, 0x15, 0xa8, 0xe5, 0xb9, 0x07, 0xcd, 0x78, 0x0a, 0xdb, 0xf3, 0x2c, 0x8a, 0x51, 0x8b, 0x89, + 0x8a, 0x94, 0xb1, 0x9b, 0x2a, 0xbb, 0xf1, 0xfb, 0xeb, 0x2a, 0xbe, 0xdf, 0x02, 0x2d, 0xf8, 0x66, + 0x0f, 0xbb, 0x84, 0x9d, 0x99, 0x22, 0x99, 0xc4, 0x57, 0x2a, 0xcc, 0xb4, 0x58, 0x3a, 0xd4, 0x0e, + 0x7a, 0xbe, 0x3e, 0xe8, 0x04, 0x0d, 0x4a, 0x83, 0xc1, 0xb7, 0x0d, 0x38, 0xbf, 0x3b, 0xc0, 0x1a, + 0x55, 0x46, 0x82, 0xec, 0xba, 0xac, 0xe6, 0x3c, 0x60, 0x0c, 0xaa, 0xd6, 0x94, 0x7e, 0xc5, 0x25, + 0xdd, 0x99, 0x1d, 0x82, 0x97, 0x0a, 0x39, 0x15, 0x21, 0x5a, 0x1b, 0x58, 0xda, 0xc7, 0xeb, 0xb4, + 0x17, 0x79, 0x95, 0xaf, 0x60, 0xec, 0x33, 0xb4, 0x64, 0x46, 0x26, 0x99, 0x8d, 0x35, 0x52, 0x92, + 0x69, 0x89, 0xe4, 0xd7, 0x5d, 0xeb, 0x93, 0xf5, 0xd6, 0x63, 0x87, 0xe2, 0x05, 0x88, 0x6f, 0xcb, + 0xb5, 0x98, 0xd8, 0x6b, 0xa8, 0x13, 0x4a, 0x8d, 0x86, 0x7c, 0xef, 0xbe, 0xab, 0x1b, 0xb9, 0xe2, + 0x27, 0x15, 0x07, 0x2a, 0x0e, 0xf9, 0x12, 0xcb, 0xde, 0x81, 0x57, 0xd8, 0x9a, 0xfc, 0x86, 0xeb, + 0x3b, 0xb8, 0xff, 0xa6, 0x0a, 0x17, 0xf1, 0x15, 0x7c, 0x78, 0x01, 0xf5, 0x51, 0xbe, 0x75, 0x76, + 0x0a, 0x55, 0x7b, 0x64, 0x0f, 0xbc, 0x03, 0xc5, 0x5b, 0xd4, 0x7e, 0xfa, 0x50, 0x39, 0xf7, 0xdf, + 0xc7, 0xa3, 0x1f, 0x87, 0xa1, 0x32, 0xd7, 0xd9, 0xc4, 0x92, 0x0f, 0xc4, 0x4d, 0x26, 0x08, 0x65, + 0xa6, 0x95, 0x59, 0x0c, 0x5c, 0xe3, 0xe0, 0xd6, 0x13, 0xf9, 0xa1, 0xf8, 0x4e, 0x6a, 0xee, 0xdd, + 0x3b, 0xfa, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x66, 0x86, 0x78, 0x40, 0x05, 0x00, 0x00, } From 805592d7ec3d4bc55982ab51e8533d5a254e8853 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Mon, 29 Jul 2024 13:17:10 +0400 Subject: [PATCH 256/352] chore: show VEX notice for OSS maintainers in CI environments (#7246) Signed-off-by: knqyf263 --- pkg/report/table/table_test.go | 1 + pkg/report/table/vulnerability.go | 34 ++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index b66828241634..d52dda0dc232 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -73,6 +73,7 @@ Total: 1 (MEDIUM: 0, HIGH: 1) }, } + t.Setenv("TRIVY_DISABLE_VEX_NOTICE", "1") for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { tableWritten := bytes.Buffer{} diff --git a/pkg/report/table/vulnerability.go b/pkg/report/table/vulnerability.go index 03435fd92b96..450349ed5fcb 100644 --- a/pkg/report/table/vulnerability.go +++ b/pkg/report/table/vulnerability.go @@ -3,12 +3,14 @@ package table import ( "bytes" "fmt" + "os" "path/filepath" "slices" "sort" "strings" "sync" + "github.com/fatih/color" "github.com/samber/lo" "github.com/xlab/treeprint" @@ -18,11 +20,29 @@ import ( ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/version/doc" ) -var showSuppressedOnce = sync.OnceFunc(func() { - log.Info(`Some vulnerabilities have been ignored/suppressed. Use the "--show-suppressed" flag to display them.`) -}) +const ( + vexNotice = ` +For OSS Maintainers: VEX Notice +-------------------------------- +If you're an OSS maintainer and Trivy has detected vulnerabilities in your project that you believe are not actually exploitable, consider issuing a VEX (Vulnerability Exploitability eXchange) statement. +VEX allows you to communicate the actual status of vulnerabilities in your project, improving security transparency and reducing false positives for your users. +Learn more and start using VEX: %s + +To disable this notice, set the TRIVY_DISABLE_VEX_NOTICE environment variable. + +` + envDisableNotice = "TRIVY_DISABLE_VEX_NOTICE" +) + +var ( + showVEXNoticeOnce = &sync.Once{} + showSuppressedOnce = sync.OnceFunc(func() { + log.Info(`Some vulnerabilities have been ignored/suppressed. Use the "--show-suppressed" flag to display them.`) + }) +) type vulnerabilityRenderer struct { w *bytes.Buffer @@ -73,6 +93,14 @@ func (r *vulnerabilityRenderer) Render() string { } func (r *vulnerabilityRenderer) renderDetectedVulnerabilities() { + // Show VEX notice only on CI + showVEXNoticeOnce.Do(func() { + if os.Getenv(envDisableNotice) != "" || os.Getenv("CI") == "" { + return + } + _, _ = color.New(color.FgCyan).Fprintf(r.w, vexNotice, doc.URL("docs/supply-chain/vex/repo", "publishing-vex-documents")) + }) + tw := newTableWriter(r.w, r.isTerminal) r.setHeaders(tw) r.setVulnerabilityRows(tw, r.result.Vulnerabilities) From 4e54a7e84c33c1be80c52c6db78c634bc3911715 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 30 Jul 2024 00:47:50 +0600 Subject: [PATCH 257/352] fix(java): avoid panic if deps from `pom` in `it` dir are not found (#7245) --- pkg/fanal/analyzer/language/java/pom/pom.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fanal/analyzer/language/java/pom/pom.go b/pkg/fanal/analyzer/language/java/pom/pom.go index 2980b469107c..382a41dc0f1c 100644 --- a/pkg/fanal/analyzer/language/java/pom/pom.go +++ b/pkg/fanal/analyzer/language/java/pom/pom.go @@ -32,7 +32,7 @@ func (a pomAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (* } // Mark integration test pom files for `maven-invoker-plugin` as Dev to skip them by default. - if isIntegrationTestDir(filePath) { + if isIntegrationTestDir(filePath) && res != nil { for i := range res.Applications { for j := range res.Applications[i].Packages { res.Applications[i].Packages[j].Dev = true From 54bb8bdfb934d114b5570005853bf4bc0d40c609 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:31:39 +0600 Subject: [PATCH 258/352] fix(nodejs): detect direct dependencies when using `latest` version for files `yarn.lock` + `package.json` (#7110) --- pkg/dependency/parser/nodejs/yarn/parse.go | 27 ++++++--- .../parser/nodejs/yarn/parse_test.go | 20 +------ .../yarn/testdata/latest-version/package.json | 8 +++ .../yarn/testdata/latest-version/yarn.lock | 20 +++++++ .../analyzer/language/nodejs/yarn/yarn.go | 46 ++++++++++------ .../language/nodejs/yarn/yarn_test.go | 55 +++++++++++++++++++ 6 files changed, 132 insertions(+), 44 deletions(-) create mode 100644 pkg/fanal/analyzer/language/nodejs/yarn/testdata/latest-version/package.json create mode 100644 pkg/fanal/analyzer/language/nodejs/yarn/testdata/latest-version/yarn.lock diff --git a/pkg/dependency/parser/nodejs/yarn/parse.go b/pkg/dependency/parser/nodejs/yarn/parse.go index 813554730bea..fbe77913a1e4 100644 --- a/pkg/dependency/parser/nodejs/yarn/parse.go +++ b/pkg/dependency/parser/nodejs/yarn/parse.go @@ -5,6 +5,7 @@ import ( "bytes" "io" "regexp" + "sort" "strings" "github.com/samber/lo" @@ -127,7 +128,7 @@ func ignoreProtocol(protocol string) bool { return false } -func parseResults(patternIDs map[string]string, dependsOn map[string][]string) (deps []ftypes.Dependency) { +func parseResults(patternIDs map[string]string, dependsOn map[string][]string) (deps ftypes.Dependencies) { // find dependencies by patterns for pkgID, depPatterns := range dependsOn { depIDs := lo.Map(depPatterns, func(pattern string, index int) string { @@ -269,14 +270,20 @@ func parseDependency(line string) (string, error) { } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, map[string][]string, error) { lineNumber := 1 - var pkgs []ftypes.Package + var pkgs ftypes.Packages - // patternIDs holds mapping between patterns and library IDs + // patternIDs holds mapping between patterns and package IDs // e.g. ajv@^6.5.5 => ajv@6.10.0 + // This is needed to update dependencies from `DependsOn`. patternIDs := make(map[string]string) + // patternIDs holds mapping between package ID and patterns + // e.g. `@babel/helper-regex@7.4.4` => [`@babel/helper-regex@^7.0.0`, `@babel/helper-regex@^7.4.4`] + // This is needed to compare package patterns with patterns from package.json files in `fanal` package. + pkgIDPatterns := make(map[string][]string) + scanner := bufio.NewScanner(r) scanner.Split(p.scanBlocks) dependsOn := make(map[string][]string) @@ -285,7 +292,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc lib, deps, newLine, err := p.parseBlock(block, lineNumber) lineNumber = newLine + 2 if err != nil { - return nil, nil, err + return nil, nil, nil, err } else if lib.Name == "" { continue } @@ -298,6 +305,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc Locations: []ftypes.Location{lib.Location}, }) + pkgIDPatterns[pkgID] = lib.Patterns for _, pattern := range lib.Patterns { // e.g. // combined-stream@^1.0.6 => combined-stream@1.0.8 @@ -310,13 +318,16 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc } if err := scanner.Err(); err != nil { - return nil, nil, xerrors.Errorf("failed to scan yarn.lock, got scanner error: %s", err.Error()) + return nil, nil, nil, xerrors.Errorf("failed to scan yarn.lock, got scanner error: %s", err.Error()) } - // Replace dependency patterns with library IDs + // Replace dependency patterns with package IDs // e.g. ajv@^6.5.5 => ajv@6.10.0 deps := parseResults(patternIDs, dependsOn) - return pkgs, deps, nil + + sort.Sort(pkgs) + sort.Sort(deps) + return pkgs, deps, pkgIDPatterns, nil } func packageID(name, version string) string { diff --git a/pkg/dependency/parser/nodejs/yarn/parse_test.go b/pkg/dependency/parser/nodejs/yarn/parse_test.go index 275514351954..e5122882636d 100644 --- a/pkg/dependency/parser/nodejs/yarn/parse_test.go +++ b/pkg/dependency/parser/nodejs/yarn/parse_test.go @@ -301,32 +301,14 @@ func TestParse(t *testing.T) { f, err := os.Open(tt.file) require.NoError(t, err) - got, deps, err := NewParser().Parse(f) + got, deps, _, err := NewParser().Parse(f) require.NoError(t, err) - sortPkgs(got) - sortPkgs(tt.want) assert.Equal(t, tt.want, got) if tt.wantDeps != nil { - sortDeps(deps) - sortDeps(tt.wantDeps) assert.Equal(t, tt.wantDeps, deps) } }) } } - -func sortPkgs(pkgs ftypes.Packages) { - sort.Sort(pkgs) - for _, pkg := range pkgs { - sort.Sort(pkg.Locations) - } -} - -func sortDeps(deps ftypes.Dependencies) { - sort.Sort(deps) - for _, dep := range deps { - sort.Strings(dep.DependsOn) - } -} diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/latest-version/package.json b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/latest-version/package.json new file mode 100644 index 000000000000..211be0da7db9 --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/latest-version/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "debug": "latest" + }, + "devDependencies" : { + "js-tokens": "^9.0.0" + } +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/testdata/latest-version/yarn.lock b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/latest-version/yarn.lock new file mode 100644 index 000000000000..9f7b3d06857c --- /dev/null +++ b/pkg/fanal/analyzer/language/nodejs/yarn/testdata/latest-version/yarn.lock @@ -0,0 +1,20 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +debug@latest: + version "4.3.5" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +js-tokens@^9.0.0: + version "9.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz" + integrity sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go index 687d147a63bc..984f72983ec9 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go @@ -10,6 +10,7 @@ import ( "path" "path/filepath" "regexp" + "slices" "sort" "strings" @@ -17,6 +18,7 @@ import ( "github.com/samber/lo" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/yarn" "github.com/aquasecurity/trivy/pkg/detector/library/compare/npm" @@ -42,7 +44,6 @@ var fragmentRegexp = regexp.MustCompile(`(\S+):(@?.*?)(@(.*?)|)$`) type yarnAnalyzer struct { logger *log.Logger packageJsonParser *packagejson.Parser - lockParser language.Parser comparer npm.Comparer license *license.License } @@ -51,12 +52,21 @@ func newYarnAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error return &yarnAnalyzer{ logger: log.WithPrefix("yarn"), packageJsonParser: packagejson.NewParser(), - lockParser: yarn.NewParser(), comparer: npm.Comparer{}, license: license.NewLicense(opt.LicenseScannerOption.ClassifierConfidenceLevel), }, nil } +type parserWithPatterns struct { + patterns map[string][]string +} + +func (p *parserWithPatterns) Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, error) { + pkgs, deps, patterns, err := yarn.NewParser().Parse(r) + p.patterns = patterns + return pkgs, deps, err +} + func (a yarnAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { var apps []types.Application @@ -65,8 +75,9 @@ func (a yarnAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis } err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, d fs.DirEntry, r io.Reader) error { + parser := &parserWithPatterns{} // Parse yarn.lock - app, err := a.parseYarnLock(filePath, r) + app, err := language.Parse(types.Yarn, filePath, r, parser) if err != nil { return xerrors.Errorf("parse error: %w", err) } else if app == nil { @@ -79,7 +90,7 @@ func (a yarnAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis } // Parse package.json alongside yarn.lock to find direct deps and mark dev deps - if err = a.analyzeDependencies(input.FS, path.Dir(filePath), app); err != nil { + if err = a.analyzeDependencies(input.FS, path.Dir(filePath), app, parser.patterns); err != nil { a.logger.Warn("Unable to parse package.json to remove dev dependencies", log.FilePath(path.Join(path.Dir(filePath), types.NpmPkg)), log.Err(err)) } @@ -147,13 +158,9 @@ func (a yarnAnalyzer) Version() int { return version } -func (a yarnAnalyzer) parseYarnLock(filePath string, r io.Reader) (*types.Application, error) { - return language.Parse(types.Yarn, filePath, r, a.lockParser) -} - // analyzeDependencies analyzes the package.json file next to yarn.lock, // distinguishing between direct and transitive dependencies as well as production and development dependencies. -func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.Application) error { +func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.Application, patterns map[string][]string) error { packageJsonPath := path.Join(dir, types.NpmPkg) directDeps, directDevDeps, err := a.parsePackageJsonDependencies(fsys, packageJsonPath) if errors.Is(err, fs.ErrNotExist) { @@ -170,13 +177,13 @@ func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.App }) // Walk prod dependencies - pkgs, err := a.walkDependencies(app.Packages, pkgIDs, directDeps, false) + pkgs, err := a.walkDependencies(app.Packages, pkgIDs, directDeps, patterns, false) if err != nil { return xerrors.Errorf("unable to walk dependencies: %w", err) } // Walk dev dependencies - devPkgs, err := a.walkDependencies(app.Packages, pkgIDs, directDevDeps, true) + devPkgs, err := a.walkDependencies(app.Packages, pkgIDs, directDevDeps, patterns, true) if err != nil { return xerrors.Errorf("unable to walk dependencies: %w", err) } @@ -194,7 +201,7 @@ func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.App } func (a yarnAnalyzer) walkDependencies(pkgs []types.Package, pkgIDs map[string]types.Package, - directDeps map[string]string, dev bool) (map[string]types.Package, error) { + directDeps map[string]string, patterns map[string][]string, dev bool) (map[string]types.Package, error) { // Identify direct dependencies directPkgs := make(map[string]types.Package) @@ -211,11 +218,16 @@ func (a yarnAnalyzer) walkDependencies(pkgs []types.Package, pkgIDs map[string]t constraint = m[4] } - // npm has own comparer to compare versions - if match, err := a.comparer.MatchVersion(pkg.Version, constraint); err != nil { - return nil, xerrors.Errorf("unable to match version for %s", pkg.Name) - } else if !match { - continue + // Try to find an exact match to the pattern. + // In some cases, patterns from yarn.lock and package.json may not match (e.g., yarn v2 uses the allowed version for ID). + // Therefore, if the patterns don't match - compare versions. + if pkgPatterns, found := patterns[pkg.ID]; !found || !slices.Contains(pkgPatterns, dependency.ID(types.Yarn, pkg.Name, constraint)) { + // npm has own comparer to compare versions + if match, err := a.comparer.MatchVersion(pkg.Version, constraint); err != nil { + return nil, xerrors.Errorf("unable to match version for %s", pkg.Name) + } else if !match { + continue + } } // Mark as a direct dependency diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go index 201629335b7a..6684f0fa4740 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn_test.go @@ -354,6 +354,61 @@ func Test_yarnLibraryAnalyzer_Analyze(t *testing.T) { }, }, }, + { + name: "package uses `latest` version", + dir: "testdata/latest-version", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Yarn, + FilePath: "yarn.lock", + Packages: types.Packages{ + { + ID: "debug@4.3.5", + Name: "debug", + Version: "4.3.5", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 10, + }, + }, + DependsOn: []string{ + "ms@2.1.2", + }, + }, + { + ID: "js-tokens@9.0.0", + Name: "js-tokens", + Version: "9.0.0", + Relationship: types.RelationshipDirect, + Dev: true, + Locations: []types.Location{ + { + StartLine: 12, + EndLine: 15, + }, + }, + }, + { + ID: "ms@2.1.2", + Name: "ms", + Version: "2.1.2", + Indirect: true, + Relationship: types.RelationshipIndirect, + Locations: []types.Location{ + { + StartLine: 17, + EndLine: 20, + }, + }, + }, + }, + }, + }, + }, + }, { name: "happy path with alias rewrite", dir: "testdata/alias", From f198cf8973013264d27693ea43c144695db0e362 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:53:54 +0600 Subject: [PATCH 259/352] refactor(flag): return error if both `--download-db-only` and `--download-java-db-only` are specified (#7259) --- pkg/flag/db_flags.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/flag/db_flags.go b/pkg/flag/db_flags.go index 37685b104204..352a0094be7c 100644 --- a/pkg/flag/db_flags.go +++ b/pkg/flag/db_flags.go @@ -137,6 +137,9 @@ func (f *DBFlagGroup) ToOptions() (DBOptions, error) { downloadDBOnly := f.DownloadDBOnly.Value() downloadJavaDBOnly := f.DownloadJavaDBOnly.Value() + if downloadDBOnly && downloadJavaDBOnly { + return DBOptions{}, xerrors.New("--download-db-only and --download-java-db-only options can not be specified both") + } if downloadDBOnly && skipDBUpdate { return DBOptions{}, xerrors.New("--skip-db-update and --download-db-only options can not be specified both") } From 4a2f492c6e685ff577fb96a7006cd0c43755baf4 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 30 Jul 2024 13:05:00 +0600 Subject: [PATCH 260/352] feat(sbom): add image labels into `SPDX` and `CycloneDX` reports (#7257) Co-authored-by: Teppei Fukuda --- pkg/sbom/core/bom.go | 11 ++++++----- pkg/sbom/cyclonedx/marshal_test.go | 9 +++++++++ pkg/sbom/io/encode.go | 9 +++++++++ pkg/sbom/io/encode_test.go | 12 ++++++++++++ pkg/sbom/spdx/marshal_test.go | 6 ++++++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/pkg/sbom/core/bom.go b/pkg/sbom/core/bom.go index 51875bff8738..c0a082d013b5 100644 --- a/pkg/sbom/core/bom.go +++ b/pkg/sbom/core/bom.go @@ -25,11 +25,12 @@ const ( PropertyClass = "Class" // Image properties - PropertySize = "Size" - PropertyImageID = "ImageID" - PropertyRepoDigest = "RepoDigest" - PropertyDiffID = "DiffID" - PropertyRepoTag = "RepoTag" + PropertySize = "Size" + PropertyImageID = "ImageID" + PropertyRepoDigest = "RepoDigest" + PropertyDiffID = "DiffID" + PropertyRepoTag = "RepoTag" + PropertyLabelsPrefix = "Labels" // Package properties PropertyPkgID = "PkgID" diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go index 9dc28a2ab812..e778b803619c 100644 --- a/pkg/sbom/cyclonedx/marshal_test.go +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -105,6 +105,11 @@ func TestMarshaler_MarshalReport(t *testing.T) { RepoDigests: []string{"rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177"}, ImageConfig: v1.ConfigFile{ Architecture: "arm64", + Config: v1.Config{ + Labels: map[string]string{ + "vendor": "aquasecurity", + }, + }, }, }, Results: types.Results{ @@ -301,6 +306,10 @@ func TestMarshaler_MarshalReport(t *testing.T) { Name: "aquasecurity:trivy:ImageID", Value: "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", }, + { + Name: "aquasecurity:trivy:Labels:vendor", + Value: "aquasecurity", + }, { Name: "aquasecurity:trivy:RepoDigest", Value: "rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177", diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go index 45c5dca245c6..0be0bf361280 100644 --- a/pkg/sbom/io/encode.go +++ b/pkg/sbom/io/encode.go @@ -73,6 +73,15 @@ func (e *Encoder) rootComponent(r types.Report) (*core.Component, error) { Value: r.Metadata.ImageID, }) + // Save image labels as properties with `Labels:` prefix. + // e.g. `LABEL vendor="aquasecurity"` => `Labels:vendor` -> `aquasecurity` + for label, value := range r.Metadata.ImageConfig.Config.Labels { + props = append(props, core.Property{ + Name: core.PropertyLabelsPrefix + ":" + label, + Value: value, + }) + } + p, err := purl.New(purl.TypeOCI, r.Metadata, ftypes.Package{}) if err != nil { return nil, xerrors.Errorf("failed to new package url for oci: %w", err) diff --git a/pkg/sbom/io/encode_test.go b/pkg/sbom/io/encode_test.go index 80783827cee7..52fbed415933 100644 --- a/pkg/sbom/io/encode_test.go +++ b/pkg/sbom/io/encode_test.go @@ -3,6 +3,7 @@ package io_test import ( "testing" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -43,6 +44,13 @@ func TestEncoder_Encode(t *testing.T) { RepoDigests: []string{ "debian@sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", }, + ImageConfig: v1.ConfigFile{ + Config: v1.Config{ + Labels: map[string]string{ + "vendor": "aquasecurity", + }, + }, + }, }, Results: []types.Result{ { @@ -185,6 +193,10 @@ func TestEncoder_Encode(t *testing.T) { BOMRef: "pkg:oci/debian@sha256%3A4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90?repository_url=index.docker.io%2Flibrary%2Fdebian", }, Properties: []core.Property{ + { + Name: "Labels:vendor", + Value: "aquasecurity", + }, { Name: core.PropertyRepoDigest, Value: "debian@sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", diff --git a/pkg/sbom/spdx/marshal_test.go b/pkg/sbom/spdx/marshal_test.go index 4ed35b7fc08c..3cd034803ffc 100644 --- a/pkg/sbom/spdx/marshal_test.go +++ b/pkg/sbom/spdx/marshal_test.go @@ -49,6 +49,11 @@ func TestMarshaler_Marshal(t *testing.T) { RepoDigests: []string{"rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177"}, ImageConfig: v1.ConfigFile{ Architecture: "arm64", + Config: v1.Config{ + Labels: map[string]string{ + "vendor": "aquasecurity", + }, + }, }, }, Results: types.Results{ @@ -199,6 +204,7 @@ func TestMarshaler_Marshal(t *testing.T) { PackageAttributionTexts: []string{ "DiffID: sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a", "ImageID: sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", + "Labels:vendor: aquasecurity", "RepoDigest: rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177", "RepoTag: rails:latest", "SchemaVersion: 2", From c2fd2e0d89567a0ccd996dda8790f3c3305ea6f7 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Tue, 30 Jul 2024 12:02:20 +0400 Subject: [PATCH 261/352] feat(vex): retrieve VEX attestations from OCI registries (#7249) Signed-off-by: knqyf263 --- .../configuration/cli/trivy_filesystem.md | 2 +- .../configuration/cli/trivy_image.md | 2 +- .../configuration/cli/trivy_kubernetes.md | 2 +- .../configuration/cli/trivy_repository.md | 2 +- .../configuration/cli/trivy_rootfs.md | 2 +- .../configuration/cli/trivy_sbom.md | 2 +- .../references/configuration/cli/trivy_vm.md | 2 +- docs/docs/supply-chain/vex/index.md | 4 +- docs/docs/supply-chain/vex/oci.md | 121 ++ go.mod | 39 +- go.sum | 1427 +++-------------- mkdocs.yml | 1 + pkg/fanal/image/image_test.go | 32 +- pkg/flag/vulnerability_flags.go | 2 +- pkg/remote/remote_test.go | 32 +- pkg/vex/oci.go | 55 + pkg/vex/oci_test.go | 146 ++ pkg/vex/vex.go | 11 + pkg/vex/vex_test.go | 61 +- 19 files changed, 701 insertions(+), 1244 deletions(-) create mode 100644 docs/docs/supply-chain/vex/oci.md create mode 100644 pkg/vex/oci.go create mode 100644 pkg/vex/oci_test.go diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index e442c590ccc5..99f1df23c069 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -91,7 +91,7 @@ trivy filesystem [flags] PATH --token-header string specify a header name for token in client/server mode (default "Trivy-Token") --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. - --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) + --vex strings [EXPERIMENTAL] VEX sources ("repo", "oci" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index cf0e7591b726..e7f2e3c70ea1 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -111,7 +111,7 @@ trivy image [flags] IMAGE_NAME --token-header string specify a header name for token in client/server mode (default "Trivy-Token") --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. - --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) + --vex strings [EXPERIMENTAL] VEX sources ("repo", "oci" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 0dea6c93a9fb..6c98a35d9020 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -105,7 +105,7 @@ trivy kubernetes [flags] [CONTEXT] --tolerations strings specify node-collector job tolerations (example: key1=value1:NoExecute,key2=value2:NoSchedule) --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. - --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) + --vex strings [EXPERIMENTAL] VEX sources ("repo", "oci" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 62364d9ba62c..ba9aa4983d22 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -91,7 +91,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --token-header string specify a header name for token in client/server mode (default "Trivy-Token") --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. - --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) + --vex strings [EXPERIMENTAL] VEX sources ("repo", "oci" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index e7cec39be469..39ccef2dd07a 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -92,7 +92,7 @@ trivy rootfs [flags] ROOTDIR --token-header string specify a header name for token in client/server mode (default "Trivy-Token") --trace enable more verbose trace output for custom queries --username strings username. Comma-separated usernames allowed. - --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) + --vex strings [EXPERIMENTAL] VEX sources ("repo", "oci" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index 6fa70d15ca16..d6d1bf07e1c6 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -63,7 +63,7 @@ trivy sbom [flags] SBOM_PATH -t, --template string output template --token string for authentication in client/server mode --token-header string specify a header name for token in client/server mode (default "Trivy-Token") - --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) + --vex strings [EXPERIMENTAL] VEX sources ("repo", "oci" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index b878fc070277..110cc0a3ecb7 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -78,7 +78,7 @@ trivy vm [flags] VM_IMAGE --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --token string for authentication in client/server mode --token-header string specify a header name for token in client/server mode (default "Trivy-Token") - --vex strings [EXPERIMENTAL] VEX sources ("repo" or file path) + --vex strings [EXPERIMENTAL] VEX sources ("repo", "oci" or file path) ``` ### Options inherited from parent commands diff --git a/docs/docs/supply-chain/vex/index.md b/docs/docs/supply-chain/vex/index.md index 4f8d59abe126..d42d8d57de29 100644 --- a/docs/docs/supply-chain/vex/index.md +++ b/docs/docs/supply-chain/vex/index.md @@ -12,6 +12,7 @@ Trivy currently supports two methods for utilizing VEX: 1. [VEX Repository](./repo.md) 2. [Local VEX Files](./file.md) +3. [VEX Attestation](./oci.md) ### Enabling VEX To enable VEX, use the `--vex` option. @@ -19,12 +20,13 @@ You can specify the method to use: - To enable the VEX Repository: `--vex repo` - To use a local VEX file: `--vex /path/to/vex-document.json` +- To enable VEX attestation discovery in OCI registry: `--vex oci` ```bash $ trivy image ghcr.io/aquasecurity/trivy:0.52.0 --vex repo ``` -You can enable both methods simultaneously. +You can enable these methods simultaneously. The order of specification determines the priority: - `--vex repo --vex /path/to/vex-document.json`: VEX Repository has priority diff --git a/docs/docs/supply-chain/vex/oci.md b/docs/docs/supply-chain/vex/oci.md new file mode 100644 index 000000000000..a8b33bfe45cb --- /dev/null +++ b/docs/docs/supply-chain/vex/oci.md @@ -0,0 +1,121 @@ +# Discover VEX Attestation in OCI Registry + +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy can discover VEX attestations for container images. +This feature allows you to automatically use VEX during container image scanning. + +## How It Works + +Trivy can automatically discover and utilize VEX attestations for container images during scanning by using the `--vex oci` flag. +This process enhances vulnerability detection results by incorporating the information from the VEX attestation. + +To use this feature, follow these three steps: + +1. Create a VEX document +2. Generate and upload a VEX attestation to an OCI registry +3. Use the VEX attestation with Trivy + +Steps 1 and 2 are not necessary if you are trying to scan a third-party container image and already have VEX attestation attached. + +Let's go through each step in detail. + +!!! note + In the following examples, the `cosign` command will write an attestation to a target OCI registry, so you must have permission to write. + If you want to avoid writing an OCI registry and only want to see an attestation, add the `--no-upload` option to the cosign command. + +### Step 1: Create a VEX Document + +Currently, Trivy does not have a built-in feature to create VEX documents, so you need to create them manually. +You can refer to the [OpenVEX section](./file.md#openvex) for guidance on creating VEX files. + +For container image vulnerabilities, the product ID should be the OCI type in the [PURL][purl] format. +For example: + +``` +pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy +``` + +This product ID applies the VEX statement to all tags of the `ghcr.io/aquasecurity/trivy` container image. +If you want to declare a statement for a specific digest only, you can use: + +``` +pkg:oci/trivy@sha256:5bd5ab35814f86783561603ebb35d5d5d99006dcdcd5c3f828ea1afb4c12d159?repository_url=ghcr.io/aquasecurity/trivy +``` + +!!! note + Using an image tag, like `pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy&tag=0.50.0`, is not supported in the product ID at the moment. + +Next, specify vulnerable packages as subcomponents, such as `pkg:apk/alpine/busybox`. +You can also include the package version and other [qualifiers][qualifiers] (e.g., `arch`) to limit statements, like `pkg:apk/alpine/busybox@1.36.1-r29?arch=x86`. + +Lastly, include the vulnerability IDs. + +Here's an example VEX document: + +```json +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "https://openvex.dev/docs/public/vex-2e67563e128250cbcb3e98930df948dd053e43271d70dc50cfa22d57e03fe96f", + "author": "Aqua Security", + "timestamp": "2024-07-30T19:07:16.853479631-06:00", + "version": 1, + "statements": [ + { + "vulnerability": { + "name": "CVE-2023-42363" + }, + "products": [ + { + "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/busybox"}, + {"@id": "pkg:apk/alpine/busybox-binsh"} + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_cannot_be_controlled_by_adversary", + "impact_statement": "awk is not used" + } + ] +} +``` + +You can also refer to [Trivy's example](https://github.com/aquasecurity/trivy/blob/4e54a7e84c33c1be80c52c6db78c634bc3911715/.vex/oci.openvex.json) for more inspiration. + +### Step 2: Generate and Upload a VEX Attestation to an OCI Registry + +You can use the [Cosign command](https://docs.sigstore.dev/verifying/attestation/) to generate and upload the VEX attestation. +Cosign offers methods both with and without keys. +For detailed instructions, please refer to the Cosign documentation. + +To generate and attach a VEX attestation to your image, use the following command: + +``` +$ cosign attest --predicate oci.openvex.json --type openvex +``` + +Note that this command attaches the attestation only to the specified image tag. +If needed, repeat the process for other tags and digests. + +### Step 3: Use VEX Attestation with Trivy + +Once you've attached the VEX attestation to the container image, Trivy can automatically discover and use it during scanning. +Simply add the `--vex oci` flag when scanning a container image: + +``` +$ trivy image --vex oci +``` + +To see which vulnerabilities were filtered by the VEX attestation, use the `--show-suppressed` flag: + +``` +$ trivy image --vex oci --show-suppressed +``` + +The `` specified in these commands must be the same as the one to which you attached the VEX attestation. + +[purl]: https://github.com/package-url/purl-spec +[qualifiers]: https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst \ No newline at end of file diff --git a/go.mod b/go.mod index 99b33b20a62e..b2e3289a5c2b 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d github.com/aquasecurity/table v1.8.0 - github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac + github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 github.com/aquasecurity/tml v0.6.1 github.com/aquasecurity/trivy-checks v0.13.0 github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 @@ -75,6 +75,7 @@ require ( github.com/liamg/iamgo v0.0.9 github.com/liamg/jfather v0.0.7 github.com/liamg/memoryfs v1.6.0 + github.com/magefile/mage v1.15.0 github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 github.com/masahiro331/go-ext4-filesystem v0.0.0-20231208112839-4339555a0cd4 @@ -90,6 +91,7 @@ require ( github.com/open-policy-agent/opa v0.66.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 + github.com/openvex/discovery v0.1.0 github.com/openvex/go-vex v0.2.5 github.com/owenrumney/go-sarif/v2 v2.3.3 github.com/owenrumney/squealer v1.2.3 @@ -133,8 +135,6 @@ require ( sigs.k8s.io/yaml v1.4.0 ) -require github.com/magefile/mage v1.15.0 - require ( cloud.google.com/go v0.112.1 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect @@ -182,6 +182,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/blang/semver v3.5.1+incompatible // indirect github.com/briandowns/spinner v1.23.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect @@ -198,9 +199,12 @@ require ( github.com/containerd/typeurl/v2 v2.1.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect + github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect github.com/docker/cli v27.0.3+incompatible // indirect @@ -218,6 +222,7 @@ require ( github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-chi/chi v4.1.2+incompatible // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect @@ -234,7 +239,6 @@ require ( github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/validate v0.24.0 // indirect - github.com/go-test/deep v1.1.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-yaml v1.9.5 // indirect github.com/gofrs/uuid v4.3.1+incompatible // indirect @@ -242,8 +246,10 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/certificate-transparency-go v1.1.8 // indirect + github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -260,12 +266,13 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/golang-lru v0.6.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/terraform-json v0.22.1 // indirect github.com/huandu/xstrings v1.4.0 // indirect - github.com/imdario/mergo v0.3.15 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -274,6 +281,7 @@ require ( github.com/klauspost/compress v1.17.9 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 // indirect github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect @@ -303,6 +311,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect @@ -316,30 +325,38 @@ require ( github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/common v0.51.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/rubenv/sql-migrate v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect + github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shirou/gopsutil/v3 v3.24.2 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.3.1 // indirect + github.com/sigstore/cosign/v2 v2.2.4 // indirect + github.com/sigstore/sigstore v1.8.3 // indirect + github.com/sigstore/timestamp-authority v1.2.2 // indirect github.com/skeema/knownhosts v1.2.2 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect + github.com/theupdateframework/go-tuf v0.7.0 // indirect + github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect + github.com/transparency-dev/merkle v0.0.2 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/vbatts/tar-split v0.11.5 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect @@ -371,6 +388,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect google.golang.org/grpc v1.64.0 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect + gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect @@ -397,3 +415,6 @@ require ( sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) + +// cf. https://github.com/openvex/discovery/pull/40 +replace github.com/openvex/discovery => github.com/knqyf263/discovery v0.1.1-0.20240726113521-97873005fd03 diff --git a/go.sum b/go.sum index 08ac8d17a66a..e840360c7d37 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,8 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 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.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.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -17,7 +15,6 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= @@ -29,92 +26,28 @@ cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y 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.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= -cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= -cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= -cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= -cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= -cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= -cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= -cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= -cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= -cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= -cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= -cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= -cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= -cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= -cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= -cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= -cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= -cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= -cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= -cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= -cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= -cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= -cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= -cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= -cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= -cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= -cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= -cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= -cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= -cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= -cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= -cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= -cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= -cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= -cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= -cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= -cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= -cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= -cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= -cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= -cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= -cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= -cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= -cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= -cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= -cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= -cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= -cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= -cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= -cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= -cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= -cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= -cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= -cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= -cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= -cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= -cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= -cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= -cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= -cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= -cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= 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.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -122,44 +55,12 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM 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.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= -cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= -cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= -cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= -cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= -cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= -cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= -cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= -cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= -cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= -cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= -cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= -cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= -cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= -cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= -cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= -cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= -cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= -cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= -cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= -cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= -cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= -cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= -cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= -cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= -cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= -cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= -cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= -cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= -cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= -cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= -cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= -cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= 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= @@ -167,453 +68,139 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= -cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= -cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= -cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= -cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= -cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= -cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= -cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= -cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= -cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= -cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= -cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= -cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= -cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= -cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= -cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= -cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= -cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= -cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= -cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= -cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= -cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= -cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= -cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= -cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= -cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= -cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= -cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= -cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= -cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= -cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= -cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= -cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= -cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= 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.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= -cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= -cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= -cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= -cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= -cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= -cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= -cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= -cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= -cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= -cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= -cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= -cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= -cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= -cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= -cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= -cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= -cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= -cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= -cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= -cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= -cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= -cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= -cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= -cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= -cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= -cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= -cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= -cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= -cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= -cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= -cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= -cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= -cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= -cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= -cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= -cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= -cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= -cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= -cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= -cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= -cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= -cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= -cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= -cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= -cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= -cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= -cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= -cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= -cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= -cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= -cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= -cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= -cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= -cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= -cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= -cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= -cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= -cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= -cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= -cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= -cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= -cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= -cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= -cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= -cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= -cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= -cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= -cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= -cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= -cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= -cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= -cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= -cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= -cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= -cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= -cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= -cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= -cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= -cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= -cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= -cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/kms v1.15.8 h1:szIeDCowID8th2i8XE4uRev5PMxQFqW+JjwYxL9h6xs= +cloud.google.com/go/kms v1.15.8/go.mod h1:WoUHcDjD9pluCg7pNds131awnH429QGvRM3N/4MyoVs= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= -cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= -cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= -cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= -cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= -cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= -cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= -cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= -cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= -cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= -cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= -cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= -cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= -cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= -cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= -cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= -cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= -cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= -cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= -cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= -cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= -cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= -cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= -cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= -cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= -cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= -cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= -cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= -cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= -cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= -cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= -cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= -cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= -cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= -cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= -cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= -cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= -cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= -cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= -cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= -cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= -cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= -cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= -cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= -cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= -cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= -cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= -cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= -cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= -cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= -cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= -cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= -cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= -cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= -cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= -cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= -cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= -cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= -cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= -cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= -cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= 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.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= -cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= -cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= -cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= -cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= -cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= -cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= -cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= -cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= -cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= -cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= -cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= -cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= -cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= -cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= -cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= -cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= -cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= -cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= -cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= -cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= -cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= -cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= -cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= -cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= -cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= -cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= -cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= -cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= -cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= -cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= -cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= -cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= -cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= -cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= -cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= -cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= -cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= -cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= -cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= -cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= -cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= -cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= -cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= -cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= -cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= -cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= -cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= -cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= -cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= -cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= -cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= -cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= -cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= -cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= -cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= -cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= -cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= -cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= -cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= -cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= -cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= -cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= -cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= -cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= -cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= -cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= -cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= -cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= -cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= -cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= -cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= -cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= -cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= -cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= -cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= -cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= -cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= -cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= -cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= -cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= -cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= -cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= -cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= -cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= -cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= -cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= -cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= -cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= -cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= -cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= -cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= -cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= -cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= -cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= -cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= -cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= -cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= -cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= -cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= -cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= -cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= -cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= -cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= -cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= -cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= -cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= -cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= -cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= -cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= -cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= -cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= -cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= -cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= -cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= -cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= -cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= -cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= -cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= -cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= -cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= -cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= -cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= -cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= -cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= -cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e h1:GwCVItFUPxwdsEYnlUcJ6PJxOjTeFFCKOh6QWg4oAzQ= +cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e/go.mod h1:ApHceQLLwcOkCEXM1+DyCXTHEJhNGDpJ2kmV6axsx24= +cuelang.org/go v0.8.1 h1:VFYsxIFSPY5KgSaH1jQ2GxHOrbu6Ga3kEI70yCZwnOg= +cuelang.org/go v0.8.1/go.mod h1:CoDbYolfMms4BhWUlhD+t5ORnihR7wvjcfgyO9lL5FI= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= -git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= -github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM= +github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 h1:8+4G8JaejP8Xa6W46PzJEwisNgBXMvFcz78N6zG/ARw= +github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww= @@ -622,28 +209,28 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0 h1:DRiANoJTiW6obBQe3SqZizkuV1PEgfiiGivmVocDy64= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0/go.mod h1:qLIye2hwb/ZouqhpSD9Zn3SJipvpEnz1Ywl3VUk9Y0s= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= github.com/Azure/go-autorest/autorest/adal v0.9.23 h1:Yepx8CvFxwNKpH6ja7RZ+sKX+DWYNldbLiALMC3BTz8= github.com/Azure/go-autorest/autorest/adal v0.9.23/go.mod h1:5pcMqFkdPhviJdlEy3kC/v1ZLnQl0MH6XA5YCcMhy4c= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk= +github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= @@ -651,7 +238,6 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -665,7 +251,6 @@ github.com/Intevation/gval v1.3.0 h1:+Ze5sft5MmGbZrHj06NVUbcxCb67l9RaPTLMNr37mjw github.com/Intevation/gval v1.3.0/go.mod h1:xmGyGpP5be12EL0P12h+dqiYG8qn2j3PJxIgkoOHO5o= github.com/Intevation/jsonpath v0.2.1 h1:rINNQJ0Pts5XTFEG+zamtdL7l9uuE1z0FBA+r55Sw+A= github.com/Intevation/jsonpath v0.2.1/go.mod h1:WnZ8weMmwAx/fAO3SutjYFU+v7DFreNYnibV7CiaYIw= -github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -677,30 +262,11 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= -github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= -github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= -github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim v0.12.0 h1:rbICA+XZFwrBef2Odk++0LjFvClNCJGRK+fsrP254Ts= github.com/Microsoft/hcsshim v0.12.0/go.mod h1:RZV12pcHCXQ42XnlQ3pz6FZfmrC1C+R4gaOHhRNML1g= -github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= -github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -708,34 +274,50 @@ github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8 github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E= +github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= -github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= -github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= +github.com/alibabacloud-go/cr-20160607 v1.0.1 h1:WEnP1iPFKJU74ryUKh/YDPHoxMZawqlPajOymyNAkts= +github.com/alibabacloud-go/cr-20160607 v1.0.1/go.mod h1:QHeKZtZ3F3FOE+/uIXCBAp8POwnUYekpLwr1dtQa5r0= +github.com/alibabacloud-go/cr-20181201 v1.0.10 h1:B60f6S1imsgn2fgC6X6FrVNrONDrbCT0NwYhsJ0C9/c= +github.com/alibabacloud-go/cr-20181201 v1.0.10/go.mod h1:VN9orB/w5G20FjytoSpZROqu9ZqxwycASmGqYUJSoDc= +github.com/alibabacloud-go/darabonba-openapi v0.2.1 h1:WyzxxKvhdVDlwpAMOHgAiCJ+NXa6g5ZWPFEzaK/ewwY= +github.com/alibabacloud-go/darabonba-openapi v0.2.1/go.mod h1:zXOqLbpIqq543oioL9IuuZYOQgHQ5B8/n5OPrnko8aY= +github.com/alibabacloud-go/debug v1.0.0 h1:3eIEQWfay1fB24PQIEzXAswlVJtdQok8f3EVN5VrBnA= +github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/endpoint-util v1.1.1 h1:ZkBv2/jnghxtU0p+upSU0GGzW1VL9GQdZO3mcSUTUy8= +github.com/alibabacloud-go/endpoint-util v1.1.1/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY= +github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/tea v1.2.1 h1:rFF1LnrAdhaiPmKwH5xwYOKlMh66CqRwPUTzIK74ask= +github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA= +github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA= +github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= +github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0= +github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA= github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0= +github.com/aliyun/credentials-go v1.3.1 h1:uq/0v7kWrxmoLGpqjx7vtQ/s03f0zR//0br/xWDTE28= +github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antchfx/htmlquery v1.3.2 h1:85YdttVkR1rAY+Oiv/nKI4FCimID+NXhDn82kz3mEvs= @@ -743,9 +325,6 @@ github.com/antchfx/htmlquery v1.3.2/go.mod h1:1mbkcEgEarAokJiWhTfr4hR06w/q2ZZjnY github.com/antchfx/xpath v1.3.1 h1:PNbFuUqHwWl0xRjvUPjJ95Agbmdj2uzzIwmQKgu4oCk= github.com/antchfx/xpath v1.3.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= -github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= -github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= @@ -765,8 +344,8 @@ github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d h1:4zour5S github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d/go.mod h1:1cPOp4BaQZ1G2F5fnw4dFz6pkOyXJI9KTuak8ghIl3U= github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0= github.com/aquasecurity/table v1.8.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw= -github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac h1:dy7xjLOAAeCNycqJ3kws4vDFGm8WdeCovkHXf2um5uA= -github.com/aquasecurity/testdocker v0.0.0-20240613070307-2c3868d658ac/go.mod h1:nyavBQqxtIkQh99lQE1ssup3i2uIq1+giL7tOSHapYk= +github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 h1:b43UVqYjz7qDqK+cVOtF2Lk6CxjytYItP6Pgf3wGsNE= +github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8/go.mod h1:wXA9k3uuaxY3yu7gxrxZDPo/04FEMJtwyecdAlYrEIo= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= github.com/aquasecurity/trivy-checks v0.13.0 h1:na6PTdY4U0uK/fjz3HNRYBxvYSJ8vgTb57a5T8Y5t9w= @@ -779,13 +358,10 @@ github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b h1 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b/go.mod h1:HOhrqoyIeTxpwnKr1EyWtQ+rt2XahV8b0UDBrRpSfEQ= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g= github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= @@ -809,10 +385,14 @@ github.com/aws/aws-sdk-go-v2/service/ec2 v1.172.0 h1:lJjLKG92RyKIIYujVvulR3JpVjr github.com/aws/aws-sdk-go-v2/service/ec2 v1.172.0/go.mod h1:o6QDjdVKpP5EF0dp/VlvqckzuSDATr1rLdHt3A5m0YY= github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3 h1:+v2hv29pWaVDASIScHuUhDC93nqJGVlGf6cujrJMHZE= github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3/go.mod h1:RhaP7Wil0+uuuhiE4FzOOEFZwkmFAk1ZflXzK+O3ptU= +github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 h1:PpbXaecV3sLAS6rjQiaKw4/jyq3Z8gNzmoJupHAoBp0= +github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2/go.mod h1:fUHpGXr4DrXkEDpGAjClPsviWf+Bszeb0daKE0blxv8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 h1:yS0JkEdV6h9JOo8sy2JSpjX+i7vsKifU8SIeHrqiDhU= +github.com/aws/aws-sdk-go-v2/service/kms v1.30.0/go.mod h1:+I8VUUSVD4p5ISQtzpgSva4I8cJ4SQ4b1dcBcof7O+g= github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALwfMWpd64tONS/NE= github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= @@ -823,45 +403,45 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudr github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= -github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M= +github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8/go.mod h1:2JF49jcDOrLStIXN/j/K1EKRq8a8R2qRnlZA6/o/c7c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c h1:C4UZIaS+HAw+X6jGUsoP2ZbM99PuqhCttjomg1yhNAI= github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo= -github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/buildkite/agent/v3 v3.62.0 h1:yvzSjI8Lgifw883I8m9u8/L/Thxt4cLFd5aWPn3gg70= +github.com/buildkite/agent/v3 v3.62.0/go.mod h1:jN6SokGXrVNNIpI0BGQ+j5aWeI3gin8F+3zwA5Q6gqM= +github.com/buildkite/go-pipeline v0.3.2 h1:SW4EaXNwfjow7xDRPGgX0Rcx+dPj5C1kV9LKCLjWGtM= +github.com/buildkite/go-pipeline v0.3.2/go.mod h1:iY5jzs3Afc8yHg6KDUcu3EJVkfaUkd9x/v/OH98qyUA= +github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 h1:k6UDF1uPYOs0iy1HPeotNa155qXRWrzKnqAaGXHLZCE= +github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251/go.mod h1:gbPR1gPu9dB96mucYIR7T3B7p/78hRVSOuzIWLHK2Y4= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= +github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= +github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -869,17 +449,16 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk= github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6jM60XI= +github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 h1:krfRl01rzPzxSxyLyrChD+U+MzsBXbm0OwYYB67uF+4= +github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589/go.mod h1:OuDyvmLnMCwa2ep4Jkm6nyA0ocJuZlGyk2gGseVzERM= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= @@ -887,229 +466,111 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= -github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= -github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= -github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= -github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= +github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= -github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= -github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= -github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= -github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.7.20 h1:Sl6jQYk3TRavaU83h66QMbI2Nqg9Jm6qzwX57Vsn1SQ= github.com/containerd/containerd v1.7.20/go.mod h1:52GsS5CwquuqPuLncsXwG0t2CiUce+KsNHJZQJvAgR0= github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA= github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= -github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= -github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= -github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= -github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= -github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= -github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= -github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= -github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= -github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= -github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= -github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU= github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= -github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= -github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= -github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= -github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= -github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= -github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= -github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= -github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= +github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU= +github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -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.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/csaf-poc/csaf_distribution/v3 v3.0.0 h1:ob9+Fmpff0YWgTP3dYaw7G2hKQ9cegh9l3zksc+q3sM= github.com/csaf-poc/csaf_distribution/v3 v3.0.0/go.mod h1:uilCTiNKivq+6zrDvjtZaUeLk70oe21iwKivo6ILwlQ= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 h1:2Dx4IHfC1yHWI12AxQDJM1QbRCDfk6M+blLzlZCXdrc= +github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= -github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= -github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= -github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= -github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= +github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= +github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= -github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= +github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE= +github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= +github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1GUYL7P0MlNa00M67axePTq+9nBSGddR8I= +github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y= +github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v27.0.3+incompatible h1:usGs0/BoBW8MWxGeEtqPMkzOY56jZ6kYlSN5BLDioCQ= github.com/docker/cli v27.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/proto v1.12.1 h1:6n/Z2pZAnBwuhU66Gs8160B8rrrYKo7h2F2sCOnNceE= +github.com/emicklei/proto v1.12.1/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -1121,15 +582,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= -github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= -github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= -github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= -github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= -github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= @@ -1141,35 +594,28 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4= github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= +github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= -github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= -github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= @@ -1183,17 +629,15 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= +github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= -github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -1206,44 +650,41 @@ github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC0 github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= -github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-piv/piv-go v1.11.0 h1:5vAaCdRTFSIW4PeqMbnsDlUZ7odMYWnHBDGdmtU/Zhg= +github.com/go-piv/piv-go v1.11.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U= +github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= @@ -1256,24 +697,15 @@ github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XE github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y= github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0= github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= -github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI= github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= -github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= @@ -1281,14 +713,9 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -1321,7 +748,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -1333,10 +759,12 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/certificate-transparency-go v1.1.8 h1:LGYKkgZF7satzgTak9R4yzfJXEeYVAjV6/EAEJOf1to= +github.com/google/certificate-transparency-go v1.1.8/go.mod h1:bV/o8r0TBKRf1X//iiiSgWrvII4d7/8OiA+3vG26gI8= github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -1354,15 +782,15 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0= github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg= +github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA= github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/licenseclassifier/v2 v2.0.0 h1:1Y57HHILNf4m0ABuMVb6xk4vAJYEUO0gDxNpog0pyeA= @@ -1383,25 +811,26 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= +github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= +github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4= +github.com/google/trillian v1.6.0/go.mod h1:Yu3nIMITzNhhMJEHjAtp6xKiu+H/iHu2Oq5FjV2mCWI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1410,8 +839,6 @@ github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -1423,43 +850,23 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -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/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/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= -github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -1469,14 +876,20 @@ github.com/hashicorp/go-getter v1.7.5 h1:dT58k9hQ/vbxNMwoI5+xFYAJuv6152UNvdHokfI github.com/hashicorp/go-getter v1.7.5/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= +github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -1490,78 +903,67 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk= github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= -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.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= +github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= +github.com/hashicorp/vault/api v1.12.2 h1:7YkCTE5Ni90TcmYHDBExdt4WGJxhpzaHqR6uGbQb/rE= +github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJdpL6HUYed8KE= +github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= +github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= +github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= +github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE= +github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= +github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/knqyf263/discovery v0.1.1-0.20240726113521-97873005fd03 h1:fsWNAqGAbq2sz7q0agtKCq/esMjvReNd26bgWN8Lk6w= +github.com/knqyf263/discovery v0.1.1-0.20240726113521-97873005fd03/go.mod h1:z4b//Qi7p7zcM/c41ogeTy+/nqfMbbeYnfZ+EMCTCD0= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg= github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 h1:PPPlUUqPP6fLudIK4n0l0VU4KT2cQGnheW9x8pNiCHI= @@ -1573,18 +975,11 @@ github.com/knqyf263/go-rpmdb v0.1.1/go.mod h1:9LQcoMCMQ9vrF7HcDtXfvqGO4+ddxFQ8+Y github.com/knqyf263/nested v0.0.1 h1:Sv26CegUMhjt19zqbBKntjwESdxe5hxVPSk0+AKjdUc= github.com/knqyf263/nested v0.0.1/go.mod h1:zwhsIhMkBg90DTOJQvxPkKIypEHPYkgWHs4gybdlUmk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -1594,8 +989,11 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 h1:WGrKdjHtWC67RX96eTkYD2f53NDHhrq/7robWTAfk4s= +github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491/go.mod h1:o158RFmdEbYyIZmXAbrvmJWesbyxlLKee6X64VPVuOc= github.com/liamg/iamgo v0.0.9 h1:tADGm3xVotyRJmuKKaH4+zsBn7LOcvgdpuF3WsSKW3c= github.com/liamg/iamgo v0.0.9/go.mod h1:Kk6ZxBF/GQqG9nnaUjIi6jf+WXNpeOTyhwc6gnguaZQ= github.com/liamg/jfather v0.0.7 h1:Xf78zS263yfT+xr2VSo6+kyAy4ROlCacRqJG7s5jt4k= @@ -1612,17 +1010,10 @@ github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNe github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= -github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -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.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= @@ -1631,7 +1022,6 @@ github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac h1:QyRucnGOLHJag1eB9CtuZwZk+/LpvTSYr5mnFLLFlgA= github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac/go.mod h1:J7Vb0sf0JzOhT0uHTeCqO6dqP/ELVcQvQ6yQ/56ZRGw= github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 h1:uQubA711SeYStvStohMLrdvRTTohdPHrEPFzerLcY9I= @@ -1658,27 +1048,21 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 h1:TLygBUBxikNJJfLwgm+Qwdgq1FtfV8Uh7bcxRyTzK8s= github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032/go.mod h1:vYT9HE7WCvL64iVeZylKmCsWKfE+JZ8105iuh2Trk8g= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= -github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= -github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= -github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -1690,10 +1074,8 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -1707,19 +1089,14 @@ github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkV github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= -github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1733,8 +1110,8 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mozillazg/docker-credential-acr-helper v0.3.0 h1:DVWFZ3/O8BP6Ue3iS/Olw+G07u1hCq1EOVCDZZjCIBI= +github.com/mozillazg/docker-credential-acr-helper v0.3.0/go.mod h1:cZlu3tof523ujmLuiNUb6JsjtHcNA70u1jitrrdnuyA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -1742,60 +1119,37 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 h1:Up6+btDp321ZG5/zdSLo48H9Iaq0UQGthrhWC6pCxzE= +github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481/go.mod h1:yKZQO8QE2bHlgozqWDiRVqTFlLQSj30K/6SAK8EeYFw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= +github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= -github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= github.com/open-policy-agent/opa v0.66.0 h1:DbrvfJQja0FBRcPOB3Z/BOckocN+M4ApNWyNhSRJt0w= github.com/open-policy-agent/opa v0.66.0/go.mod h1:EIgNnJcol7AvQR/IcWLwL13k64gHVbNAVG46b2G+/EY= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= @@ -1809,30 +1163,21 @@ github.com/owenrumney/squealer v1.2.3 h1:7v2BGNReEHYGyopOpjnurbnowk5WWagpN/u9KEu github.com/owenrumney/squealer v1.2.3/go.mod h1:F3PF/UaTAzaexT/cvvMYCSRHLRPBCiUcPClz3SZ6618= github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= -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/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 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/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= -github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 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.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -1841,70 +1186,47 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw= +github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/protocolbuffers/txtpbfmt v0.0.0-20231025115547-084445ff1adf h1:014O62zIzQwvoD7Ekj3ePDF5bv9Xxy0w6AZk0qYbjUk= +github.com/protocolbuffers/txtpbfmt v0.0.0-20231025115547-084445ff1adf/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= -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.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= -github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= -github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -1913,10 +1235,14 @@ github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= +github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= +github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4= +github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k= github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= +github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= +github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= @@ -1932,24 +1258,32 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sigstore/cosign/v2 v2.2.4 h1:iY4vtEacmu2hkNj1Fh+8EBqBwKs2DHM27/lbNWDFJro= +github.com/sigstore/cosign/v2 v2.2.4/go.mod h1:JZlRD2uaEjVAvZ1XJ3QkkZJhTqSDVtLaet+C/TMR81Y= +github.com/sigstore/fulcio v1.4.5 h1:WWNnrOknD0DbruuZWCbN+86WRROpEl3Xts+WT2Ek1yc= +github.com/sigstore/fulcio v1.4.5/go.mod h1:oz3Qwlma8dWcSS/IENR/6SjbW4ipN0cxpRVfgdsjMU8= github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8= github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sigstore/sigstore v1.8.3 h1:G7LVXqL+ekgYtYdksBks9B38dPoIsbscjQJX/MGWkA4= +github.com/sigstore/sigstore v1.8.3/go.mod h1:mqbTEariiGA94cn6G3xnDiV6BD8eSLdL/eA7bvJ0fVs= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.3 h1:LTfPadUAo+PDRUbbdqbeSl2OuoFQwUFTnJ4stu+nwWw= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.3/go.mod h1:QV/Lxlxm0POyhfyBtIbTWxNeF18clMlkkyL9mu45y18= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.3 h1:xgbPRCr2npmmsuVVteJqi/ERw9+I13Wou7kq0Yk4D8g= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.3/go.mod h1:G4+I83FILPX6MtnoaUdmv/bRGEVtR3JdLeJa/kXdk/0= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.3 h1:vDl2fqPT0h3D/k6NZPlqnKFd1tz3335wm39qjvpZNJc= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.3/go.mod h1:9uOJXbXEXj+M6QjMKH5PaL5WDMu43rHfbIMgXzA8eKI= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.3 h1:h9G8j+Ds21zqqulDbA/R/ft64oQQIyp8S7wJYABYSlg= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.3/go.mod h1:zgCeHOuqF6k7A7TTEvftcA9V3FRzB7mrPtHOhXAQBnc= +github.com/sigstore/timestamp-authority v1.2.2 h1:X4qyutnCQqJ0apMewFyx+3t7Tws00JQ/JonBiu3QvLE= +github.com/sigstore/timestamp-authority v1.2.2/go.mod h1:nEah4Eq4wpliDjlY342rXclGSO7Kb9hoRrl9tqLW13A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/sosedoff/gitkit v0.4.0 h1:opyQJ/h9xMRLsz2ca/2CRXtstePcpldiZN8DpLLF8Os= github.com/sosedoff/gitkit v0.4.0/go.mod h1:V3EpGZ0nvCBhXerPsbDeqtyReNb48cwP9KtkUYTKT5I= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -1958,43 +1292,25 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= -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.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -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.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -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 v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -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/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= -github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/spiffe/go-spiffe/v2 v2.2.0 h1:9Vf06UsvsDbLYK/zJ4sYsIsHmMFknUD+feA7IYoWMQY= +github.com/spiffe/go-spiffe/v2 v2.2.0/go.mod h1:Urzb779b3+IwDJD2ZbN8fVl3Aa8G4N/PiUe6iXC0XxU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -2002,19 +1318,17 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -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-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= +github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= @@ -2025,44 +1339,37 @@ github.com/testcontainers/testcontainers-go/modules/localstack v0.32.0 h1:FITjE+ github.com/testcontainers/testcontainers-go/modules/localstack v0.32.0/go.mod h1:JasdXHmUT8MTDYfyJza3JjO/k+QA3m8K2GQfnFQM++g= github.com/tetratelabs/wazero v1.7.3 h1:PBH5KVahrt3S2AHgEjKu4u+LlDbbk+nsGE3KLucy6Rw= github.com/tetratelabs/wazero v1.7.3/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= +github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= +github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= +github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= +github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= +github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= +github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8= github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= +github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= +github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/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.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= -github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= -github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4= +github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -2070,13 +1377,10 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMc github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -2084,7 +1388,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= @@ -2096,6 +1399,8 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMzt github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= +github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= @@ -2103,17 +1408,13 @@ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6 github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= github.com/zclconf/go-cty-yaml v1.0.3 h1:og/eOQ7lvA/WWhHGFETVWNduJM7Rjsv2RRpx1sdFMLc= github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= -github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= +github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -2142,82 +1443,48 @@ go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwP go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.step.sm/crypto v0.44.2 h1:t3p3uQ7raP2jp2ha9P6xkQF85TJZh+87xmjSLaib+jk= +go.step.sm/crypto v0.44.2/go.mod h1:x1439EnFhadzhkuaGX7sz03LEMQ+jV4gRamf5LCZJQQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 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-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -2241,13 +1508,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= @@ -2255,25 +1517,18 @@ golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -2284,23 +1539,21 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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= @@ -2312,19 +1565,13 @@ golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= @@ -2351,14 +1598,8 @@ golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7Lm golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2374,10 +1615,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= @@ -2385,48 +1624,32 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2435,36 +1658,23 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2472,12 +1682,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2497,21 +1703,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= @@ -2523,14 +1721,9 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= @@ -2543,50 +1736,32 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -2616,24 +1791,19 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= @@ -2650,15 +1820,6 @@ golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNq golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= -gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= -gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= -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.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -2706,17 +1867,7 @@ google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaE google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= -google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= -google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= -google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= google.golang.org/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk= google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -2726,13 +1877,11 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -2741,7 +1890,6 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -2761,13 +1909,10 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -2800,7 +1945,6 @@ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2 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-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= 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= @@ -2833,50 +1977,17 @@ google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53B google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= -google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= -google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -2899,7 +2010,6 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/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.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= @@ -2909,13 +2019,6 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= @@ -2934,58 +2037,42 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= +gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -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/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= helm.sh/helm/v3 v3.15.3 h1:HcZDaVFe9uHa6hpsR54mJjYyRy4uz/pc6csg27nxFOc= @@ -2997,134 +2084,72 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= -k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ= k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04= k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= -k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc= k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= -k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= -k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M= k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= k8s.io/cli-runtime v0.30.2 h1:ooM40eEJusbgHNEqnHziN9ZpLN5U4WcQGsdLKVxpkKE= k8s.io/cli-runtime v0.30.2/go.mod h1:Y4g/2XezFyTATQUbvV5WaChoUGhojv/jZAtdp5Zkm0A= -k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= -k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= -k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= -k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= -k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= -k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= k8s.io/component-base v0.30.1 h1:bvAtlPh1UrdaZL20D9+sWxsJljMi0QZ3Lmw+kmZAaxQ= k8s.io/component-base v0.30.1/go.mod h1:e/X9kDiOebwlI41AvBHuWdqFriSRrX50CdwA9TFaHLI= -k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= -k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/kubectl v0.30.1 h1:sHFIRI3oP0FFZmBAVEE8ErjnTyXDPkBcvO88mH9RjuY= k8s.io/kubectl v0.30.1/go.mod h1:7j+L0Cc38RYEcx+WH3y44jRBe1Q1jxdGPKkX0h4iDq0= -k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= -modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= -modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= -modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= -modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= -modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= -modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= -modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= -modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= -modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= -modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= -modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= mvdan.cc/sh/v3 v3.8.0 h1:ZxuJipLZwr/HLbASonmXtcvvC9HXY9d2lXZHnKGjFc8= mvdan.cc/sh/v3 v3.8.0/go.mod h1:w04623xkgBVo7/IUK89E0g8hBykgEpN0vgOj3RJr6MY= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/release-utils v0.7.7 h1:JKDOvhCk6zW8ipEOkpTGDH/mW3TI+XqtPp16aaQ79FU= +sigs.k8s.io/release-utils v0.7.7/go.mod h1:iU7DGVNi3umZJ8q6aHyUFzsDUIaYwNnNKGHo3YE5E3s= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= +software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/mkdocs.yml b/mkdocs.yml index f61ccdf7161d..9585c8341f94 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -131,6 +131,7 @@ nav: - Overview: docs/supply-chain/vex/index.md - VEX Repository: docs/supply-chain/vex/repo.md - Local VEX Files: docs/supply-chain/vex/file.md + - VEX Attestation: docs/supply-chain/vex/oci.md - Compliance: - Built-in Compliance: docs/compliance/compliance.md - Custom Compliance: docs/compliance/contrib-compliance.md diff --git a/pkg/fanal/image/image_test.go b/pkg/fanal/image/image_test.go index 0ae4e50f8b11..2728ddc45184 100644 --- a/pkg/fanal/image/image_test.go +++ b/pkg/fanal/image/image_test.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/http/httptest" - "os" "testing" "time" @@ -16,10 +15,11 @@ import ( "github.com/aquasecurity/testdocker/auth" "github.com/aquasecurity/testdocker/engine" "github.com/aquasecurity/testdocker/registry" + "github.com/aquasecurity/testdocker/tarfile" "github.com/aquasecurity/trivy/pkg/fanal/types" ) -func setupEngineAndRegistry() (*httptest.Server, *httptest.Server) { +func setupEngineAndRegistry(t *testing.T) (*httptest.Server, *httptest.Server) { imagePaths := map[string]string{ "alpine:3.10": "../test/testdata/alpine-310.tar.gz", "alpine:3.11": "../test/testdata/alpine-311.tar.gz", @@ -31,21 +31,21 @@ func setupEngineAndRegistry() (*httptest.Server, *httptest.Server) { } te := engine.NewDockerEngine(opt) - imagePaths = map[string]string{ - "v2/library/alpine:3.10": "../test/testdata/alpine-310.tar.gz", + images := map[string]v1.Image{ + "v2/library/alpine:3.10": localImage(t), } tr := registry.NewDockerRegistry(registry.Option{ - Images: imagePaths, + Images: images, Auth: auth.Auth{}, }) - os.Setenv("DOCKER_HOST", fmt.Sprintf("tcp://%s", te.Listener.Addr().String())) + t.Setenv("DOCKER_HOST", fmt.Sprintf("tcp://%s", te.Listener.Addr().String())) return te, tr } func TestNewDockerImage(t *testing.T) { - te, tr := setupEngineAndRegistry() + te, tr := setupEngineAndRegistry(t) defer func() { te.Close() tr.Close() @@ -301,12 +301,12 @@ func TestNewDockerImage(t *testing.T) { } } -func setupPrivateRegistry() *httptest.Server { - imagePaths := map[string]string{ - "v2/library/alpine:3.10": "../test/testdata/alpine-310.tar.gz", +func setupPrivateRegistry(t *testing.T) *httptest.Server { + images := map[string]v1.Image{ + "v2/library/alpine:3.10": localImage(t), } tr := registry.NewDockerRegistry(registry.Option{ - Images: imagePaths, + Images: images, Auth: auth.Auth{ User: "test", Password: "testpass", @@ -318,7 +318,7 @@ func setupPrivateRegistry() *httptest.Server { } func TestNewDockerImageWithPrivateRegistry(t *testing.T) { - tr := setupPrivateRegistry() + tr := setupPrivateRegistry(t) defer tr.Close() serverAddr := tr.Listener.Addr().String() @@ -503,7 +503,7 @@ func TestNewArchiveImage(t *testing.T) { } func TestDockerPlatformArguments(t *testing.T) { - tr := setupPrivateRegistry() + tr := setupPrivateRegistry(t) defer tr.Close() serverAddr := tr.Listener.Addr().String() @@ -555,3 +555,9 @@ func TestDockerPlatformArguments(t *testing.T) { }) } } + +func localImage(t *testing.T) v1.Image { + img, err := tarfile.ImageFromPath("../test/testdata/alpine-310.tar.gz") + require.NoError(t, err) + return img +} diff --git a/pkg/flag/vulnerability_flags.go b/pkg/flag/vulnerability_flags.go index da306997b5b8..2e9d651b6320 100644 --- a/pkg/flag/vulnerability_flags.go +++ b/pkg/flag/vulnerability_flags.go @@ -23,7 +23,7 @@ var ( VEXFlag = Flag[[]string]{ Name: "vex", ConfigName: "vulnerability.vex", - Usage: `[EXPERIMENTAL] VEX sources ("repo" or file path)`, + Usage: `[EXPERIMENTAL] VEX sources ("repo", "oci" or file path)`, } SkipVEXRepoUpdateFlag = Flag[bool]{ Name: "skip-vex-repo-update", diff --git a/pkg/remote/remote_test.go b/pkg/remote/remote_test.go index 33c5d2a7c68e..fb64deb5d4c4 100644 --- a/pkg/remote/remote_test.go +++ b/pkg/remote/remote_test.go @@ -18,16 +18,17 @@ import ( "github.com/aquasecurity/testdocker/auth" "github.com/aquasecurity/testdocker/registry" + "github.com/aquasecurity/testdocker/tarfile" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/version/app" ) -func setupPrivateRegistry() *httptest.Server { - imagePaths := map[string]string{ - "v2/library/alpine:3.10": "../fanal/test/testdata/alpine-310.tar.gz", +func setupPrivateRegistry(t *testing.T) *httptest.Server { + images := map[string]v1.Image{ + "v2/library/alpine:3.10": localImage(t), } tr := registry.NewDockerRegistry(registry.Option{ - Images: imagePaths, + Images: images, Auth: auth.Auth{ User: "test", Password: "testpass", @@ -60,7 +61,7 @@ func encode(user, pass string) string { } func TestGet(t *testing.T) { - tr := setupPrivateRegistry() + tr := setupPrivateRegistry(t) defer tr.Close() serverAddr := tr.Listener.Addr().String() @@ -219,7 +220,10 @@ type userAgentsTrackingHandler struct { } func newUserAgentsTrackingHandler(hr http.Handler) *userAgentsTrackingHandler { - return &userAgentsTrackingHandler{hr: hr, agents: make(map[string]struct{})} + return &userAgentsTrackingHandler{ + hr: hr, + agents: make(map[string]struct{}), + } } func (uh *userAgentsTrackingHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { @@ -232,12 +236,12 @@ func (uh *userAgentsTrackingHandler) ServeHTTP(rw http.ResponseWriter, r *http.R uh.hr.ServeHTTP(rw, r) } -func setupAgentTrackingRegistry() (*httptest.Server, *userAgentsTrackingHandler) { - imagePaths := map[string]string{ - "v2/library/alpine:3.10": "../fanal/test/testdata/alpine-310.tar.gz", +func setupAgentTrackingRegistry(t *testing.T) (*httptest.Server, *userAgentsTrackingHandler) { + images := map[string]v1.Image{ + "v2/library/alpine:3.10": localImage(t), } tr := registry.NewDockerRegistry(registry.Option{ - Images: imagePaths, + Images: images, }) tracker := newUserAgentsTrackingHandler(tr.Config.Handler) @@ -247,7 +251,7 @@ func setupAgentTrackingRegistry() (*httptest.Server, *userAgentsTrackingHandler) } func TestUserAgents(t *testing.T) { - tr, tracker := setupAgentTrackingRegistry() + tr, tracker := setupAgentTrackingRegistry(t) defer tr.Close() serverAddr := tr.Listener.Addr().String() @@ -270,3 +274,9 @@ func TestUserAgents(t *testing.T) { _, ok := tracker.agents[fmt.Sprintf("trivy/%s go-containerregistry", app.Version())] require.True(t, ok, `user-agent header equals to "trivy/dev go-containerregistry"`) } + +func localImage(t *testing.T) v1.Image { + img, err := tarfile.ImageFromPath("../fanal/test/testdata/alpine-310.tar.gz") + require.NoError(t, err) + return img +} diff --git a/pkg/vex/oci.go b/pkg/vex/oci.go new file mode 100644 index 000000000000..17b827fe4fbd --- /dev/null +++ b/pkg/vex/oci.go @@ -0,0 +1,55 @@ +package vex + +import ( + "fmt" + + "github.com/openvex/discovery/pkg/discovery" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/fanal/artifact" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/types" +) + +type OCI struct{} + +func NewOCI(report *types.Report) (*OpenVEX, error) { + if report.ArtifactType != artifact.TypeContainerImage || len(report.Metadata.RepoDigests) == 0 { + return nil, xerrors.New("'--vex oci' can be used only when scanning OCI artifacts stored in registries") + } + + // TODO(knqyf263): Add the PURL field to Report.Metadata + p, err := purl.New(purl.TypeOCI, report.Metadata, ftypes.Package{}) + if err != nil { + return nil, xerrors.Errorf("failed to create a package URL: %w", err) + } + + v, err := RetrieveVEXAttestation(p) + if err != nil { + return nil, xerrors.Errorf("failed to retrieve VEX attestation: %w", err) + } + return v, nil +} + +func RetrieveVEXAttestation(p *purl.PackageURL) (*OpenVEX, error) { + logger := log.WithPrefix("vex").With(log.String("type", "oci"), + log.String("purl", p.String())) + + // Probe the OCI artifact and retrieve VEX documents + vexDocuments, err := discovery.NewAgent().ProbePurl(p.String()) + if err != nil { + return nil, xerrors.Errorf("failed to probe the package URL: %w", err) + } + if len(vexDocuments) == 0 { + logger.Info("No VEX attestations found") + return nil, nil + } + + logger.Debug("VEX attestation found, taking the first one") + return &OpenVEX{ + vex: *vexDocuments[0], + source: fmt.Sprintf("VEX attestation in OCI registry (%s)", p.String()), + }, nil +} diff --git a/pkg/vex/oci_test.go b/pkg/vex/oci_test.go new file mode 100644 index 000000000000..e803d6ab20aa --- /dev/null +++ b/pkg/vex/oci_test.go @@ -0,0 +1,146 @@ +package vex_test + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "math/rand" + "net/http/httptest" + "strings" + "testing" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/random" + "github.com/google/go-containerregistry/pkg/v1/static" + "github.com/in-toto/in-toto-golang/in_toto" + openvex "github.com/openvex/go-vex/pkg/vex" + "github.com/package-url/packageurl-go" + "github.com/secure-systems-lab/go-securesystemslib/dsse" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/testdocker/auth" + "github.com/aquasecurity/testdocker/registry" + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/vex" +) + +func setUpRegistry(t *testing.T) (*httptest.Server, v1.Hash) { + imgWithVEX := setUpImage(t) + d, err := imgWithVEX.Digest() + require.NoError(t, err) + + images := map[string]v1.Image{ + "v2/debian:latest": imgWithVEX, + "v2/debian:no-vex": setUpImage(t), + fmt.Sprintf("v2/debian@%s", d.String()): imgWithVEX, + fmt.Sprintf("v2/debian:sha256-%s.att", d.Hex): setUpVEXAttestation(t), // VEX attestation + } + + tr := registry.NewDockerRegistry(registry.Option{ + Images: images, + Auth: auth.Auth{}, + }) + + return tr, d +} + +func setUpImage(t *testing.T) v1.Image { + img, err := random.Image(100, 1, random.WithSource(rand.NewSource(0))) + require.NoError(t, err) + + return img +} + +func setUpVEXAttestation(t *testing.T) v1.Image { + envelope := createVEXAttestation(t) + b, err := json.Marshal(envelope) + require.NoError(t, err) + + layer := static.NewLayer(b, "application/vnd.dsse.envelope.v1+json") + newImage, err := mutate.AppendLayers(empty.Image, layer) + require.NoError(t, err) + + return newImage +} + +func createVEXAttestation(t *testing.T) dsse.Envelope { + var v openvex.VEX + testutil.MustReadJSON(t, "testdata/openvex-oci.json", &v) + + // in-toto Statement + statement := in_toto.Statement{ + StatementHeader: in_toto.StatementHeader{ + Type: "https://in-toto.io/Statement/v0.1", + PredicateType: "https://openvex.dev/ns", + Subject: []in_toto.Subject{ + { + Name: "example", + Digest: map[string]string{ + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, + }, + }, + }, + Predicate: v, + } + + attestationJSON, err := json.Marshal(statement) + require.NoError(t, err) + + // Base64 encode + encodedAttestation := base64.StdEncoding.EncodeToString(attestationJSON) + + // Create a DSSE envelope + return dsse.Envelope{ + PayloadType: "application/vnd.in-toto+json", + Payload: encodedAttestation, + Signatures: nil, + } +} + +func TestRetrieveVEXAttestation(t *testing.T) { + tr, _ := setUpRegistry(t) + t.Cleanup(tr.Close) + + tests := []struct { + name string + url string + wantErr require.ErrorAssertionFunc + }{ + { + name: "vex found", + url: strings.TrimPrefix(tr.URL, "http://") + "/debian:latest", + wantErr: require.NoError, + }, + { + name: "vex not found", + url: strings.TrimPrefix(tr.URL, "http://") + "/debian:no-vex", + wantErr: require.NoError, + }, + { + name: "image not found", + url: strings.TrimPrefix(tr.URL, "http://") + "/debian:no-such-image", + wantErr: require.Error, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &purl.PackageURL{ + Type: packageurl.TypeOCI, + Name: "debian", + Qualifiers: packageurl.Qualifiers{ + { + Key: "repository_url", + Value: tt.url, + }, + }, + } + _, err := vex.RetrieveVEXAttestation(p) + tt.wantErr(t, err) + }) + } +} diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index de93f542ca00..e9ad15233b04 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -17,6 +17,7 @@ import ( const ( TypeFile SourceType = "file" TypeRepository SourceType = "repo" + TypeOCI SourceType = "oci" ) // VEX represents Vulnerability Exploitability eXchange. It abstracts multiple VEX formats. @@ -46,6 +47,8 @@ func NewSource(src string) Source { switch src { case "repository", "repo": return Source{Type: TypeRepository} + case "oci": + return Source{Type: TypeOCI} default: return Source{ Type: TypeFile, @@ -68,6 +71,7 @@ func Filter(ctx context.Context, report *types.Report, opts Options) error { return nil } + // NOTE: This method call has a side effect on the report bom, err := sbomio.NewEncoder(core.Options{Parents: true}).Encode(*report) if err != nil { return xerrors.Errorf("unable to encode the SBOM: %w", err) @@ -100,6 +104,13 @@ func New(ctx context.Context, report *types.Report, opts Options) (*Client, erro } else if err != nil { return nil, xerrors.Errorf("failed to create a vex repository set: %w", err) } + case TypeOCI: + v, err = NewOCI(report) + if err != nil { + return nil, xerrors.Errorf("VEX OCI error: %w", err) + } else if v == nil { + continue + } default: log.Warn("Unsupported VEX source", log.String("type", string(src.Type))) continue diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go index c76a9961643c..4a9686972a5e 100644 --- a/pkg/vex/vex_test.go +++ b/pkg/vex/vex_test.go @@ -2,8 +2,11 @@ package vex_test import ( "context" + "fmt" + "net/http/httptest" "os" "path/filepath" + "strings" "testing" "github.com/google/go-containerregistry/pkg/v1" @@ -152,6 +155,9 @@ func TestMain(m *testing.M) { } func TestFilter(t *testing.T) { + // Set up the OCI registry + tr, d := setUpRegistry(t) + type args struct { report *types.Report opts vex.Options @@ -509,6 +515,34 @@ repositories: }), }), }, + { + name: "VEX attestation from OCI registry", + args: args{ + // - oci:debian?tag=12 + // - pkg:deb/debian/bash@5.3 + report: imageReportWithAttestation([]types.Result{ + bashResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{ + vuln3, // filtered by VEX + }, + }), + }, fmt.Sprintf("%s/debian@%s", strings.TrimPrefix(tr.URL, "http://"), d.String())), + opts: vex.Options{ + Sources: []vex.Source{ + {Type: vex.TypeOCI}, + }, + }, + }, + want: imageReportWithAttestation([]types.Result{ + bashResult(types.Result{ + Vulnerabilities: []types.DetectedVulnerability{}, + ModifiedFindings: []types.ModifiedFinding{ + modifiedFinding(vuln3, vulnerableCodeNotInExecutePath, + fmt.Sprintf("VEX attestation in OCI registry (%s)", ociPURLString(tr, d))), + }, + }), + }, fmt.Sprintf("%s/debian@%s", strings.TrimPrefix(tr.URL, "http://"), d.String())), + }, { name: "unknown format", args: args{ @@ -550,7 +584,7 @@ func imageReport(results types.Results) *types.Report { ArtifactType: artifact.TypeContainerImage, Metadata: types.Metadata{ RepoDigests: []string{ - "debian:@sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", + "debian@sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", }, ImageConfig: v1.ConfigFile{ Architecture: "amd64", @@ -560,6 +594,31 @@ func imageReport(results types.Results) *types.Report { } } +func imageReportWithAttestation(results types.Results, repoDigest string) *types.Report { + report := imageReport(results) + report.Metadata.RepoDigests[0] = repoDigest + return report +} + +func ociPURLString(ts *httptest.Server, d v1.Hash) string { + p := &packageurl.PackageURL{ + Type: packageurl.TypeOCI, + Name: "debian", + Version: d.String(), + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "amd64", + }, + { + Key: "repository_url", + Value: strings.TrimPrefix(ts.URL, "http://") + "/debian", + }, + }, + } + return p.String() +} + func fsReport(results types.Results) *types.Report { return &types.Report{ ArtifactName: ".", From 3b7aad33924c12835eb13d39449a2573fd246f2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jul 2024 08:59:15 +0000 Subject: [PATCH 262/352] chore(deps): bump google.golang.org/grpc from 1.64.0 to 1.64.1 (#7136) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b2e3289a5c2b..58453c942f7d 100644 --- a/go.mod +++ b/go.mod @@ -386,7 +386,7 @@ require ( google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect - google.golang.org/grpc v1.64.0 // indirect + google.golang.org/grpc v1.64.1 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index e840360c7d37..c5c4bc01e657 100644 --- a/go.sum +++ b/go.sum @@ -2019,8 +2019,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From b3ee4bc88566f71cea75a80819d00b7d7ca7cdd9 Mon Sep 17 00:00:00 2001 From: pl0psec <156285439+pl0psec@users.noreply.github.com> Date: Tue, 30 Jul 2024 21:12:19 +0800 Subject: [PATCH 263/352] docs: update ecosystem page reporting with plopsec.com app (#7262) --- docs/ecosystem/reporting.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/ecosystem/reporting.md b/docs/ecosystem/reporting.md index 8f599cf2a12c..de6de6d8d47a 100644 --- a/docs/ecosystem/reporting.md +++ b/docs/ecosystem/reporting.md @@ -30,3 +30,9 @@ Trivy-Streamlit is a Streamlit application that allows you to quickly parse the This project is a web application that allows to load a Trivy report in json format and displays the vulnerabilities of a single target in an interactive data table. 👉 Get it at: + +## plopsec.com (Community) + +This project is a web application designed to help you visualize Trivy image scan reports. It enriches the data with additional exploitability metrics from EPSS, Metasploit, and Exploit-DB, updated daily. + +👉 Get it at: | From ff403a3841b150d6366961e36604ade54f331790 Mon Sep 17 00:00:00 2001 From: Aqua Security automated builds <54269356+aqua-bot@users.noreply.github.com> Date: Wed, 31 Jul 2024 08:32:30 +0300 Subject: [PATCH 264/352] release: v0.54.0 [main] (#7075) --- .release-please-manifest.json | 2 +- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e5e510d9d3ad..8f1dfd40939e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{".":"0.53.0"} +{".":"0.54.0"} diff --git a/CHANGELOG.md b/CHANGELOG.md index fccfdbb410c7..04a57a25147e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,48 @@ # Changelog +## [0.54.0](https://github.com/aquasecurity/trivy/compare/v0.53.0...v0.54.0) (2024-07-30) + + +### Features + +* add `log.FilePath()` function for logger ([#7080](https://github.com/aquasecurity/trivy/issues/7080)) ([1f5f348](https://github.com/aquasecurity/trivy/commit/1f5f34895823fae81bf521fc939bee743a50e304)) +* add openSUSE tumbleweed detection and scanning ([#6965](https://github.com/aquasecurity/trivy/issues/6965)) ([17b5dbf](https://github.com/aquasecurity/trivy/commit/17b5dbfa12180414b87859c6c46bfe6cc5ecf7ba)) +* **cli:** rename `--vuln-type` flag to `--pkg-types` flag ([#7104](https://github.com/aquasecurity/trivy/issues/7104)) ([7cbdb0a](https://github.com/aquasecurity/trivy/commit/7cbdb0a0b5dff33e506e1c1f3119951fa241b432)) +* **mariner:** Add support for Azure Linux ([#7186](https://github.com/aquasecurity/trivy/issues/7186)) ([5cbc452](https://github.com/aquasecurity/trivy/commit/5cbc452a09822d1bf300ead88f0d613d4cf0349a)) +* **misconf:** enabled China configuration for ACRs ([#7156](https://github.com/aquasecurity/trivy/issues/7156)) ([d1ec89d](https://github.com/aquasecurity/trivy/commit/d1ec89d1db4b039f0e31076ccd1ca969fb15628e)) +* **nodejs:** add license parser to pnpm analyser ([#7036](https://github.com/aquasecurity/trivy/issues/7036)) ([03ac93d](https://github.com/aquasecurity/trivy/commit/03ac93dc208f1b40896f3fa11fa1d45293176dca)) +* **sbom:** add image labels into `SPDX` and `CycloneDX` reports ([#7257](https://github.com/aquasecurity/trivy/issues/7257)) ([4a2f492](https://github.com/aquasecurity/trivy/commit/4a2f492c6e685ff577fb96a7006cd0c43755baf4)) +* **sbom:** add vulnerability support for SPDX formats ([#7213](https://github.com/aquasecurity/trivy/issues/7213)) ([efb1f69](https://github.com/aquasecurity/trivy/commit/efb1f6938321eec3529ef4fea6608261f6771ae0)) +* share build-in rules ([#7207](https://github.com/aquasecurity/trivy/issues/7207)) ([bff317c](https://github.com/aquasecurity/trivy/commit/bff317c77bf4a5f615a80d9875d129213bd52f6d)) +* **vex:** retrieve VEX attestations from OCI registries ([#7249](https://github.com/aquasecurity/trivy/issues/7249)) ([c2fd2e0](https://github.com/aquasecurity/trivy/commit/c2fd2e0d89567a0ccd996dda8790f3c3305ea6f7)) +* **vex:** VEX Repository support ([#7206](https://github.com/aquasecurity/trivy/issues/7206)) ([88ba460](https://github.com/aquasecurity/trivy/commit/88ba46047c93e6046292523ae701de774dfdc4dc)) +* **vuln:** add `--pkg-relationships` ([#7237](https://github.com/aquasecurity/trivy/issues/7237)) ([5c37361](https://github.com/aquasecurity/trivy/commit/5c37361600d922db27dd594b2a80c010a19b3a6e)) + + +### Bug Fixes + +* Add dependencyManagement exclusions to the child exclusions ([#6969](https://github.com/aquasecurity/trivy/issues/6969)) ([dc68a66](https://github.com/aquasecurity/trivy/commit/dc68a662a701980d6529f61a65006f1e4728a3e5)) +* add missing platform and type to spec ([#7149](https://github.com/aquasecurity/trivy/issues/7149)) ([c8a7abd](https://github.com/aquasecurity/trivy/commit/c8a7abd3b508975fcf10c254d13d1a2cd42da657)) +* **cli:** error on missing config file ([#7154](https://github.com/aquasecurity/trivy/issues/7154)) ([7fa5e7d](https://github.com/aquasecurity/trivy/commit/7fa5e7d0ab67f20d434b2922725988695e32e6af)) +* close file when failed to open gzip ([#7164](https://github.com/aquasecurity/trivy/issues/7164)) ([2a577a7](https://github.com/aquasecurity/trivy/commit/2a577a7bae37e5731dceaea8740683573b6b70a5)) +* **dotnet:** don't include non-runtime libraries into report for `*.deps.json` files ([#7039](https://github.com/aquasecurity/trivy/issues/7039)) ([5bc662b](https://github.com/aquasecurity/trivy/commit/5bc662be9a8f072599f90abfd3b400c8ab055ed6)) +* **dotnet:** show `nuget package dir not found` log only when checking `nuget` packages ([#7194](https://github.com/aquasecurity/trivy/issues/7194)) ([d76feba](https://github.com/aquasecurity/trivy/commit/d76febaee107c645e864da0f4d74a8f6ae4ad232)) +* ignore nodes when listing permission is not allowed ([#7107](https://github.com/aquasecurity/trivy/issues/7107)) ([25f8143](https://github.com/aquasecurity/trivy/commit/25f8143f120965c636c5ea8386398b211b082398)) +* **java:** avoid panic if deps from `pom` in `it` dir are not found ([#7245](https://github.com/aquasecurity/trivy/issues/7245)) ([4e54a7e](https://github.com/aquasecurity/trivy/commit/4e54a7e84c33c1be80c52c6db78c634bc3911715)) +* **java:** use `go-mvn-version` to remove `Package` duplicates ([#7088](https://github.com/aquasecurity/trivy/issues/7088)) ([a7a304d](https://github.com/aquasecurity/trivy/commit/a7a304d53e1ce230f881c28c4f35885774cf3b9a)) +* **misconf:** do not evaluate TF when a load error occurs ([#7109](https://github.com/aquasecurity/trivy/issues/7109)) ([f27c236](https://github.com/aquasecurity/trivy/commit/f27c236d6e155cb366aeef619b6ea96d20fb93da)) +* **nodejs:** detect direct dependencies when using `latest` version for files `yarn.lock` + `package.json` ([#7110](https://github.com/aquasecurity/trivy/issues/7110)) ([54bb8bd](https://github.com/aquasecurity/trivy/commit/54bb8bdfb934d114b5570005853bf4bc0d40c609)) +* **report:** hide empty table when all secrets/license/misconfigs are ignored ([#7171](https://github.com/aquasecurity/trivy/issues/7171)) ([c3036de](https://github.com/aquasecurity/trivy/commit/c3036de6d7719323d306a9666ccc8d928d936f9a)) +* **secret:** skip regular strings contain secret patterns ([#7182](https://github.com/aquasecurity/trivy/issues/7182)) ([174b1e3](https://github.com/aquasecurity/trivy/commit/174b1e3515a6394cf8d523216d6267c1aefb820a)) +* **secret:** trim excessively long lines ([#7192](https://github.com/aquasecurity/trivy/issues/7192)) ([92b13be](https://github.com/aquasecurity/trivy/commit/92b13be668bd20f8e9dac2f0cb8e5a2708b9b3b5)) +* **secret:** update length of `hugging-face-access-token` ([#7216](https://github.com/aquasecurity/trivy/issues/7216)) ([8c87194](https://github.com/aquasecurity/trivy/commit/8c87194f0a6b194bc5d340c8a65bd99a3132d973)) +* **server:** pass license categories to options ([#7203](https://github.com/aquasecurity/trivy/issues/7203)) ([9d52018](https://github.com/aquasecurity/trivy/commit/9d5201808da89607ae43570bdf1f335b482a6b79)) + + +### Performance Improvements + +* **debian:** use `bytes.Index` in `emptyLineSplit` to cut allocation ([#7065](https://github.com/aquasecurity/trivy/issues/7065)) ([acbec05](https://github.com/aquasecurity/trivy/commit/acbec053c985388a26d899e73b4b7f5a6d1fa210)) + ## [0.53.0](https://github.com/aquasecurity/trivy/compare/v0.52.0...v0.53.0) (2024-07-01) From 45b3f344042bcd90ca63ab696b69bff0e9ab4e36 Mon Sep 17 00:00:00 2001 From: yusuke-koyoshi <92022336+yusuke-koyoshi@users.noreply.github.com> Date: Wed, 31 Jul 2024 16:30:20 +0900 Subject: [PATCH 265/352] feat(vm): Support direct filesystem (#7058) Signed-off-by: yusuke.koyoshi --- go.mod | 4 ++-- go.sum | 8 ++++---- pkg/fanal/walker/vm.go | 10 +++++++++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 58453c942f7d..4fe6146acb4d 100644 --- a/go.mod +++ b/go.mod @@ -76,12 +76,12 @@ require ( github.com/liamg/jfather v0.0.7 github.com/liamg/memoryfs v1.6.0 github.com/magefile/mage v1.15.0 - github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac + github.com/masahiro331/go-disk v0.0.0-20240625071113-56c933208fee github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 github.com/masahiro331/go-ext4-filesystem v0.0.0-20231208112839-4339555a0cd4 github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 github.com/masahiro331/go-vmdk-parser v0.0.0-20221225061455-612096e4bbbd - github.com/masahiro331/go-xfs-filesystem v0.0.0-20230608043311-a335f4599b70 + github.com/masahiro331/go-xfs-filesystem v0.0.0-20231205045356-1b22259a6c44 github.com/mattn/go-shellwords v1.0.12 github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 github.com/mitchellh/go-homedir v1.1.0 diff --git a/go.sum b/go.sum index c5c4bc01e657..2db8a06b1722 100644 --- a/go.sum +++ b/go.sum @@ -1022,8 +1022,8 @@ github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac h1:QyRucnGOLHJag1eB9CtuZwZk+/LpvTSYr5mnFLLFlgA= -github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac/go.mod h1:J7Vb0sf0JzOhT0uHTeCqO6dqP/ELVcQvQ6yQ/56ZRGw= +github.com/masahiro331/go-disk v0.0.0-20240625071113-56c933208fee h1:cgm8mE25x5XXX2oyvJDlyJ72K+rDu/4ZCYce2worNb8= +github.com/masahiro331/go-disk v0.0.0-20240625071113-56c933208fee/go.mod h1:rojbW5tVhH1cuVYFKZS+QX+VGXK45JVsRO+jW92kkKM= github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 h1:uQubA711SeYStvStohMLrdvRTTohdPHrEPFzerLcY9I= github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323/go.mod h1:OdtzwqTtu49Gh5RFkNEU1SbcihIuVTtUipwHflqxckE= github.com/masahiro331/go-ext4-filesystem v0.0.0-20231208112839-4339555a0cd4 h1:uHO44vOunB0oEtk+r8ifBbFOD0mr6+fmoyFNCgLE66k= @@ -1032,8 +1032,8 @@ github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 h1:AevU github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08/go.mod h1:JOkBRrE1HvgTyjk6diFtNGgr8XJMtIfiBzkL5krqzVk= github.com/masahiro331/go-vmdk-parser v0.0.0-20221225061455-612096e4bbbd h1:Y30EzvuoVp97b0unb/GOFXzBUKRXZXUN2e0wYmvC+ic= github.com/masahiro331/go-vmdk-parser v0.0.0-20221225061455-612096e4bbbd/go.mod h1:5f7mCJGW9cJb8SDn3z8qodGxpMCOo8d/2nls/tiwRrw= -github.com/masahiro331/go-xfs-filesystem v0.0.0-20230608043311-a335f4599b70 h1:X6W6raTo07X0q4pvSI/68Pj/Ic4iIU2CfQU65OH0Zhc= -github.com/masahiro331/go-xfs-filesystem v0.0.0-20230608043311-a335f4599b70/go.mod h1:QKBZqdn6teT0LK3QhAf3K6xakItd1LonOShOEC44idQ= +github.com/masahiro331/go-xfs-filesystem v0.0.0-20231205045356-1b22259a6c44 h1:VmSjn0UCyfXUNdePDr7uM/uZTnGSp+mKD5+cYkEoLx4= +github.com/masahiro331/go-xfs-filesystem v0.0.0-20231205045356-1b22259a6c44/go.mod h1:QKBZqdn6teT0LK3QhAf3K6xakItd1LonOShOEC44idQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= diff --git a/pkg/fanal/walker/vm.go b/pkg/fanal/walker/vm.go index 5d7336f1623c..0bc3e87ac86d 100644 --- a/pkg/fanal/walker/vm.go +++ b/pkg/fanal/walker/vm.go @@ -9,9 +9,12 @@ import ( "strings" "github.com/masahiro331/go-disk" + diskFs "github.com/masahiro331/go-disk/fs" "github.com/masahiro331/go-disk/gpt" "github.com/masahiro331/go-disk/mbr" "github.com/masahiro331/go-disk/types" + "github.com/masahiro331/go-ext4-filesystem/ext4" + "github.com/masahiro331/go-xfs-filesystem/xfs" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/vm/filesystem" @@ -29,6 +32,11 @@ var requiredDiskName = []string{ "3", // Common image name } +var checkFsFuncs = []diskFs.CheckFsFunc{ + ext4.Check, + xfs.Check, +} + func AppendPermitDiskName(s ...string) { requiredDiskName = append(requiredDiskName, s...) } @@ -53,7 +61,7 @@ func (w *VM) Walk(vreader *io.SectionReader, root string, opt Option, fn WalkFun // This function will be called on each file. w.analyzeFn = fn - driver, err := disk.NewDriver(vreader) + driver, err := disk.NewDriver(vreader, checkFsFuncs...) if err != nil { return xerrors.Errorf("failed to new disk driver: %w", err) } From 70245721372720027b7089bd61c693df48add865 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 31 Jul 2024 12:07:28 +0400 Subject: [PATCH 266/352] feat(cli)!: delete deprecated SBOM flags (#7266) Signed-off-by: knqyf263 --- pkg/commands/app.go | 1 - pkg/flag/options.go | 12 ----------- pkg/flag/sbom_flags.go | 49 ------------------------------------------ 3 files changed, 62 deletions(-) delete mode 100644 pkg/flag/sbom_flags.go diff --git a/pkg/commands/app.go b/pkg/commands/app.go index a3bd7b78e6f1..a3fb6df353d9 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -1146,7 +1146,6 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { RemoteFlagGroup: flag.NewClientFlags(), // for client/server mode ReportFlagGroup: reportFlagGroup, ScanFlagGroup: scanFlagGroup, - SBOMFlagGroup: flag.NewSBOMFlagGroup(), VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), LicenseFlagGroup: licenseFlagGroup, } diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 3f48b75ac9b4..e5c46c81c64f 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -326,7 +326,6 @@ type Flags struct { RegoFlagGroup *RegoFlagGroup RepoFlagGroup *RepoFlagGroup ReportFlagGroup *ReportFlagGroup - SBOMFlagGroup *SBOMFlagGroup ScanFlagGroup *ScanFlagGroup SecretFlagGroup *SecretFlagGroup VulnerabilityFlagGroup *VulnerabilityFlagGroup @@ -350,7 +349,6 @@ type Options struct { RemoteOptions RepoOptions ReportOptions - SBOMOptions ScanOptions SecretOptions VulnerabilityOptions @@ -549,9 +547,6 @@ func (f *Flags) groups() []FlagGroup { if f.ImageFlagGroup != nil { groups = append(groups, f.ImageFlagGroup) } - if f.SBOMFlagGroup != nil { - groups = append(groups, f.SBOMFlagGroup) - } if f.VulnerabilityFlagGroup != nil { groups = append(groups, f.VulnerabilityFlagGroup) } @@ -760,13 +755,6 @@ func (f *Flags) ToOptions(args []string) (Options, error) { } } - 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, err = f.ScanFlagGroup.ToOptions(args) if err != nil { diff --git a/pkg/flag/sbom_flags.go b/pkg/flag/sbom_flags.go deleted file mode 100644 index 9af8414ed546..000000000000 --- a/pkg/flag/sbom_flags.go +++ /dev/null @@ -1,49 +0,0 @@ -package flag - -var ( - ArtifactTypeFlag = Flag[string]{ - Name: "artifact-type", - ConfigName: "sbom.artifact-type", - Usage: "deprecated", - Removed: `Use 'trivy image' or other subcommands. See also https://github.com/aquasecurity/trivy/discussions/2407`, - } - SBOMFormatFlag = Flag[string]{ - Name: "sbom-format", - ConfigName: "sbom.format", - Usage: "deprecated", - Removed: `Use 'trivy image' or other subcommands. See also https://github.com/aquasecurity/trivy/discussions/2407`, - } -) - -type SBOMFlagGroup struct { - ArtifactType *Flag[string] // deprecated - SBOMFormat *Flag[string] // deprecated -} - -type SBOMOptions struct{} - -func NewSBOMFlagGroup() *SBOMFlagGroup { - return &SBOMFlagGroup{ - ArtifactType: ArtifactTypeFlag.Clone(), - SBOMFormat: SBOMFormatFlag.Clone(), - } -} - -func (f *SBOMFlagGroup) Name() string { - return "SBOM" -} - -func (f *SBOMFlagGroup) Flags() []Flagger { - return []Flagger{ - f.ArtifactType, - f.SBOMFormat, - } -} - -func (f *SBOMFlagGroup) ToOptions() (SBOMOptions, error) { - if err := parseFlags(f); err != nil { - return SBOMOptions{}, err - } - - return SBOMOptions{}, nil -} From 35c60f030fa48de8d8e57958e5ba379814126831 Mon Sep 17 00:00:00 2001 From: Aruneko Date: Wed, 31 Jul 2024 19:49:47 +0900 Subject: [PATCH 267/352] feat(vm): support the Ext2/Ext3 filesystems (#6983) --- docs/docs/target/vm.md | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/target/vm.md b/docs/docs/target/vm.md index b0dc23e9c507..e2c2cac74467 100644 --- a/docs/docs/target/vm.md +++ b/docs/docs/target/vm.md @@ -230,7 +230,7 @@ Reference: [VMware Virtual Disk Format 1.1.pdf][vmdk] |-------------------|:-------:| | XFS | ✔ | | EXT4 | ✔ | -| EXT2/3 | | +| EXT2/3 | ✔ | | ZFS | | diff --git a/go.mod b/go.mod index 4fe6146acb4d..70277514b22a 100644 --- a/go.mod +++ b/go.mod @@ -78,7 +78,7 @@ require ( github.com/magefile/mage v1.15.0 github.com/masahiro331/go-disk v0.0.0-20240625071113-56c933208fee github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 - github.com/masahiro331/go-ext4-filesystem v0.0.0-20231208112839-4339555a0cd4 + github.com/masahiro331/go-ext4-filesystem v0.0.0-20240620024024-ca14e6327bbd github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 github.com/masahiro331/go-vmdk-parser v0.0.0-20221225061455-612096e4bbbd github.com/masahiro331/go-xfs-filesystem v0.0.0-20231205045356-1b22259a6c44 diff --git a/go.sum b/go.sum index 2db8a06b1722..a5dde364799f 100644 --- a/go.sum +++ b/go.sum @@ -1026,8 +1026,8 @@ github.com/masahiro331/go-disk v0.0.0-20240625071113-56c933208fee h1:cgm8mE25x5X github.com/masahiro331/go-disk v0.0.0-20240625071113-56c933208fee/go.mod h1:rojbW5tVhH1cuVYFKZS+QX+VGXK45JVsRO+jW92kkKM= github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 h1:uQubA711SeYStvStohMLrdvRTTohdPHrEPFzerLcY9I= github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323/go.mod h1:OdtzwqTtu49Gh5RFkNEU1SbcihIuVTtUipwHflqxckE= -github.com/masahiro331/go-ext4-filesystem v0.0.0-20231208112839-4339555a0cd4 h1:uHO44vOunB0oEtk+r8ifBbFOD0mr6+fmoyFNCgLE66k= -github.com/masahiro331/go-ext4-filesystem v0.0.0-20231208112839-4339555a0cd4/go.mod h1:3XMMY1M486mWGTD13WPItg6FsgflQR72ZMAkd+gsyoQ= +github.com/masahiro331/go-ext4-filesystem v0.0.0-20240620024024-ca14e6327bbd h1:JEIW94K3spsvBI5Xb9PGhKSIza9/jxO1lF30tPCAJlA= +github.com/masahiro331/go-ext4-filesystem v0.0.0-20240620024024-ca14e6327bbd/go.mod h1:3XMMY1M486mWGTD13WPItg6FsgflQR72ZMAkd+gsyoQ= github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 h1:AevUBW4cc99rAF8q8vmddIP8qd/0J5s/UyltGbp66dg= github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08/go.mod h1:JOkBRrE1HvgTyjk6diFtNGgr8XJMtIfiBzkL5krqzVk= github.com/masahiro331/go-vmdk-parser v0.0.0-20221225061455-612096e4bbbd h1:Y30EzvuoVp97b0unb/GOFXzBUKRXZXUN2e0wYmvC+ic= From b3ee6dac269bd7847674f3ce985a5ff7f8f0ba38 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 31 Jul 2024 15:16:26 +0400 Subject: [PATCH 268/352] fix(plugin): do not call GitHub content API for releases and tags (#7274) Signed-off-by: knqyf263 --- pkg/downloader/download.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/downloader/download.go b/pkg/downloader/download.go index 63b130a667fd..96fd3ce49008 100644 --- a/pkg/downloader/download.go +++ b/pkg/downloader/download.go @@ -154,7 +154,8 @@ func (t *CustomTransport) RoundTrip(req *http.Request) (*http.Response, error) { func NewGitHubTransport(u *url.URL, insecure bool, token string) http.RoundTripper { client := newGitHubClient(insecure, token) ss := strings.SplitN(u.Path, "/", 4) - if len(ss) < 4 || strings.HasPrefix(ss[3], "archive/") { + if len(ss) < 4 || strings.HasPrefix(ss[3], "archive/") || strings.HasPrefix(ss[3], "releases/") || + strings.HasPrefix(ss[3], "tags/") { // Use the default transport from go-github for authentication return client.Client().Transport } From 49d5270163e305f88fedcf50412973736e69dc69 Mon Sep 17 00:00:00 2001 From: Colm O hEigeartaigh Date: Wed, 31 Jul 2024 13:07:33 +0100 Subject: [PATCH 269/352] fix(java): Return error when trying to find a remote pom to avoid segfault (#7275) Co-authored-by: DmitriyLewen --- pkg/dependency/parser/java/pom/parse.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index 550c5761c6ee..cbd7bf47db17 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -13,7 +13,7 @@ import ( "sort" "strings" - multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-multierror" "github.com/samber/lo" "golang.org/x/net/html/charset" "golang.org/x/xerrors" @@ -680,18 +680,15 @@ func (p *Parser) fetchPOMFromRemoteRepositories(paths []string, snapshot bool) ( func (p *Parser) remoteRepoRequest(repo string, paths []string) (*http.Request, error) { repoURL, err := url.Parse(repo) if err != nil { - p.logger.Error("URL parse error", log.String("repo", repo)) - return nil, nil + return nil, xerrors.Errorf("unable to parse URL: %w", err) } paths = append([]string{repoURL.Path}, paths...) repoURL.Path = path.Join(paths...) - logger := p.logger.With(log.String("host", repoURL.Host), log.String("path", repoURL.Path)) req, err := http.NewRequest("GET", repoURL.String(), http.NoBody) if err != nil { - logger.Debug("HTTP request failed") - return nil, nil + return nil, xerrors.Errorf("unable to create HTTP request: %w", err) } if repoURL.User != nil { password, _ := repoURL.User.Password() @@ -709,7 +706,8 @@ func (p *Parser) fetchPomFileNameFromMavenMetadata(repo string, paths []string) req, err := p.remoteRepoRequest(repo, mavenMetadataPaths) if err != nil { - return "", xerrors.Errorf("unable to create request for maven-metadata.xml file") + p.logger.Debug("Unable to create request", log.String("repo", repo), log.Err(err)) + return "", nil } client := &http.Client{} @@ -739,7 +737,8 @@ func (p *Parser) fetchPomFileNameFromMavenMetadata(repo string, paths []string) func (p *Parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom, error) { req, err := p.remoteRepoRequest(repo, paths) if err != nil { - return nil, xerrors.Errorf("unable to create request for pom file") + p.logger.Debug("Unable to create request", log.String("repo", repo), log.Err(err)) + return nil, nil } client := &http.Client{} From 2a0e529c36057b572119815af59c28e4790034ca Mon Sep 17 00:00:00 2001 From: afdesk Date: Wed, 31 Jul 2024 18:43:26 +0600 Subject: [PATCH 270/352] fix(flag): incorrect behavior for deprected flag `--clear-cache` (#7281) --- pkg/flag/cache_flags.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/flag/cache_flags.go b/pkg/flag/cache_flags.go index 074953c2ea44..9cf8403a1e56 100644 --- a/pkg/flag/cache_flags.go +++ b/pkg/flag/cache_flags.go @@ -80,6 +80,7 @@ type CacheOptions struct { // NewCacheFlagGroup returns a default CacheFlagGroup func NewCacheFlagGroup() *CacheFlagGroup { return &CacheFlagGroup{ + ClearCache: ClearCacheFlag.Clone(), CacheBackend: CacheBackendFlag.Clone(), CacheTTL: CacheTTLFlag.Clone(), RedisTLS: RedisTLSFlag.Clone(), From e95152f796308c25aaaa6cf75b2e7a81b3ab4388 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Fri, 2 Aug 2024 13:34:57 +0700 Subject: [PATCH 271/352] refactor(misconf): remove file filtering from parsers (#7289) Signed-off-by: nikpivkin --- pkg/iac/detection/detect.go | 11 +++- pkg/iac/detection/detect_test.go | 64 +++++++++++++++++++ pkg/iac/rego/scanner.go | 4 -- pkg/iac/scanners/azure/arm/parser/parser.go | 44 ++----------- .../scanners/azure/arm/parser/parser_test.go | 5 -- pkg/iac/scanners/azure/arm/scanner.go | 12 ++-- .../scanners/cloudformation/parser/parser.go | 29 --------- pkg/iac/scanners/cloudformation/scanner.go | 15 ++--- pkg/iac/scanners/dockerfile/parser/parser.go | 19 +----- pkg/iac/scanners/dockerfile/scanner.go | 33 +++++----- pkg/iac/scanners/helm/parser/parser.go | 21 ------ pkg/iac/scanners/helm/scanner.go | 6 +- pkg/iac/scanners/json/parser/parser.go | 20 +----- pkg/iac/scanners/json/scanner.go | 29 ++++----- pkg/iac/scanners/kubernetes/parser/parser.go | 29 +-------- pkg/iac/scanners/kubernetes/scanner.go | 29 ++++----- pkg/iac/scanners/options/parser.go | 7 -- pkg/iac/scanners/options/scanner.go | 7 -- pkg/iac/scanners/terraform/parser/parser.go | 5 -- pkg/iac/scanners/terraform/scanner.go | 12 ++-- .../scanners/terraformplan/tfjson/scanner.go | 12 ++-- pkg/iac/scanners/toml/parser/parser.go | 19 +----- pkg/iac/scanners/toml/scanner.go | 29 ++++----- pkg/iac/scanners/yaml/parser/parser.go | 19 +----- pkg/iac/scanners/yaml/scanner.go | 29 ++++----- pkg/misconf/scanner.go | 1 - 26 files changed, 173 insertions(+), 337 deletions(-) diff --git a/pkg/iac/detection/detect.go b/pkg/iac/detection/detect.go index 596d8da1851d..787b82cb0b6a 100644 --- a/pkg/iac/detection/detect.go +++ b/pkg/iac/detection/detect.go @@ -135,15 +135,20 @@ func init() { } sniff := struct { - ContentType string `json:"contentType"` - Parameters map[string]any `json:"parameters"` - Resources []any `json:"resources"` + Schema string `json:"$schema"` + Parameters map[string]any `json:"parameters"` + Resources []any `json:"resources"` }{} metadata := types.NewUnmanagedMetadata() if err := armjson.UnmarshalFromReader(r, &sniff, &metadata); err != nil { return false } + // schema is required https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/syntax + if !strings.HasPrefix(sniff.Schema, "https://schema.management.azure.com/schemas") { + return false + } + return (sniff.Parameters != nil && len(sniff.Parameters) > 0) || (sniff.Resources != nil && len(sniff.Resources) > 0) } diff --git a/pkg/iac/detection/detect_test.go b/pkg/iac/detection/detect_test.go index 5a0d9f876b16..20998427d99d 100644 --- a/pkg/iac/detection/detect_test.go +++ b/pkg/iac/detection/detect_test.go @@ -355,6 +355,70 @@ rules: FileTypeYAML, }, }, + { + name: "Azure ARM template with resources", + path: "test.json", + r: strings.NewReader(` +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-09-01", + "name": "{provide-unique-name}", + "location": "eastus", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "properties": { + "supportsHttpsTrafficOnly": true + } + } + ] +} +`), + expected: []FileType{ + FileTypeJSON, + FileTypeAzureARM, + }, + }, + { + name: "Azure ARM template with parameters", + path: "test.json", + r: strings.NewReader(` +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "storageName": { + "type": "string", + "minLength": 3, + "maxLength": 24 + } + } +} +`), + expected: []FileType{ + FileTypeJSON, + FileTypeAzureARM, + }, + }, + { + name: "empty Azure ARM template", + path: "test.json", + r: strings.NewReader(` +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [] +} +`), + expected: []FileType{ + FileTypeJSON, + }, + }, } for _, test := range tests { diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go index 2e0516761a02..f7293c46a0c5 100644 --- a/pkg/iac/rego/scanner.go +++ b/pkg/iac/rego/scanner.go @@ -138,10 +138,6 @@ func (s *Scanner) SetPolicyNamespaces(namespaces ...string) { } } -func (s *Scanner) SetSkipRequiredCheck(_ bool) { - // NOTE: Skip required option not applicable for rego. -} - func (s *Scanner) SetRegoErrorLimit(limit int) { s.regoErrorLimit = limit } diff --git a/pkg/iac/scanners/azure/arm/parser/parser.go b/pkg/iac/scanners/azure/arm/parser/parser.go index e5e171914b42..9fbf7b7019a8 100644 --- a/pkg/iac/scanners/azure/arm/parser/parser.go +++ b/pkg/iac/scanners/azure/arm/parser/parser.go @@ -5,8 +5,6 @@ import ( "fmt" "io" "io/fs" - "path/filepath" - "strings" "github.com/aquasecurity/trivy/pkg/iac/debug" azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" @@ -17,19 +15,14 @@ import ( ) type Parser struct { - targetFS fs.FS - skipRequired bool - debug debug.Logger + targetFS fs.FS + debug debug.Logger } func (p *Parser) SetDebugWriter(writer io.Writer) { p.debug = debug.New(writer, "azure", "arm") } -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - func New(targetFS fs.FS, opts ...options.ParserOption) *Parser { p := &Parser{ targetFS: targetFS, @@ -56,14 +49,13 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) ([]azure2.Deployment, if entry.IsDir() { return nil } - if !p.Required(path) { - return nil - } + f, err := p.targetFS.Open(path) if err != nil { return err } defer f.Close() + deployment, err := p.parseFile(f, path) if err != nil { return err @@ -77,34 +69,6 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) ([]azure2.Deployment, return deployments, nil } -func (p *Parser) Required(path string) bool { - if p.skipRequired { - return true - } - if !strings.HasSuffix(path, ".json") { - return false - } - data, err := fs.ReadFile(p.targetFS, path) - if err != nil { - return false - } - var template Template - root := types.NewMetadata( - types.NewRange(filepath.Base(path), 0, 0, "", p.targetFS), - "", - ) - if err := armjson.Unmarshal(data, &template, &root); err != nil { - p.debug.Log("Error scanning %s: %s", path, err) - return false - } - - if template.Schema.Kind != azure2.KindString { - return false - } - - return strings.HasPrefix(template.Schema.AsString(), "https://schema.management.azure.com") -} - func (p *Parser) parseFile(r io.Reader, filename string) (*azure2.Deployment, error) { var template Template data, err := io.ReadAll(r) diff --git a/pkg/iac/scanners/azure/arm/parser/parser_test.go b/pkg/iac/scanners/azure/arm/parser/parser_test.go index 3273c0dd6596..b16213d9bebd 100644 --- a/pkg/iac/scanners/azure/arm/parser/parser_test.go +++ b/pkg/iac/scanners/azure/arm/parser/parser_test.go @@ -36,11 +36,6 @@ func TestParser_Parse(t *testing.T) { want func() azure2.Deployment wantDeployment bool }{ - { - name: "invalid code", - input: `blah`, - wantDeployment: false, - }, { name: "basic param", input: `{ diff --git a/pkg/iac/scanners/azure/arm/scanner.go b/pkg/iac/scanners/azure/arm/scanner.go index b4bcfc539486..871b58df3f3d 100644 --- a/pkg/iac/scanners/azure/arm/scanner.go +++ b/pkg/iac/scanners/azure/arm/scanner.go @@ -24,12 +24,12 @@ import ( var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic +type Scanner struct { + mu sync.Mutex scannerOptions []options.ScannerOption parserOptions []options.ParserOption debug debug.Logger frameworks []framework.Framework - skipRequired bool regoOnly bool loadEmbeddedPolicies bool loadEmbeddedLibraries bool @@ -37,7 +37,6 @@ type Scanner struct { // nolint: gocritic policyReaders []io.Reader regoScanner *rego.Scanner spec string - sync.Mutex } func (s *Scanner) SetIncludeDeprecatedChecks(b bool) {} @@ -73,9 +72,6 @@ func (s *Scanner) SetPolicyDirs(dirs ...string) { s.policyDirs = dirs } -func (s *Scanner) SetSkipRequiredCheck(skipRequired bool) { - s.skipRequired = skipRequired -} func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } @@ -106,8 +102,8 @@ func (s *Scanner) SetPolicyNamespaces(...string) {} func (s *Scanner) SetRegoErrorLimit(_ int) {} func (s *Scanner) initRegoScanner(srcFS fs.FS) error { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return nil } diff --git a/pkg/iac/scanners/cloudformation/parser/parser.go b/pkg/iac/scanners/cloudformation/parser/parser.go index 5aa760e19882..d281b585035e 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser.go +++ b/pkg/iac/scanners/cloudformation/parser/parser.go @@ -1,7 +1,6 @@ package parser import ( - "bytes" "context" "encoding/json" "errors" @@ -15,7 +14,6 @@ import ( "gopkg.in/yaml.v3" "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/iac/ignore" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) @@ -24,7 +22,6 @@ var _ options.ConfigurableParser = (*Parser)(nil) type Parser struct { debug debug.Logger - skipRequired bool parameterFiles []string parameters map[string]any overridedParameters Parameters @@ -59,10 +56,6 @@ func (p *Parser) SetDebugWriter(writer io.Writer) { p.debug = debug.New(writer, "cloudformation", "parser") } -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - func New(opts ...options.ParserOption) *Parser { p := &Parser{} for _, option := range opts { @@ -86,11 +79,6 @@ func (p *Parser) ParseFS(ctx context.Context, fsys fs.FS, dir string) (FileConte return nil } - if !p.Required(fsys, path) { - p.debug.Log("not a CloudFormation file, skipping %s", path) - return nil - } - c, err := p.ParseFile(ctx, fsys, path) if err != nil { p.debug.Log("Error parsing file '%s': %s", path, err) @@ -104,23 +92,6 @@ func (p *Parser) ParseFS(ctx context.Context, fsys fs.FS, dir string) (FileConte return contexts, nil } -func (p *Parser) Required(fsys fs.FS, path string) bool { - if p.skipRequired { - return true - } - - f, err := fsys.Open(filepath.ToSlash(path)) - if err != nil { - return false - } - defer func() { _ = f.Close() }() - if data, err := io.ReadAll(f); err == nil { - return detection.IsType(path, bytes.NewReader(data), detection.FileTypeCloudFormation) - } - return false - -} - func (p *Parser) ParseFile(ctx context.Context, fsys fs.FS, path string) (fctx *FileContext, err error) { defer func() { if e := recover(); e != nil { diff --git a/pkg/iac/scanners/cloudformation/scanner.go b/pkg/iac/scanners/cloudformation/scanner.go index 1bbbe39f2117..20b96ce947fd 100644 --- a/pkg/iac/scanners/cloudformation/scanner.go +++ b/pkg/iac/scanners/cloudformation/scanner.go @@ -47,13 +47,13 @@ func WithConfigsFS(fsys fs.FS) options.ScannerOption { var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic +type Scanner struct { + mu sync.Mutex debug debug.Logger policyDirs []string policyReaders []io.Reader parser *parser.Parser regoScanner *rego.Scanner - skipRequired bool regoOnly bool loadEmbeddedPolicies bool loadEmbeddedLibraries bool @@ -61,7 +61,6 @@ type Scanner struct { // nolint: gocritic parserOptions []options.ParserOption frameworks []framework.Framework spec string - sync.Mutex } func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} @@ -98,12 +97,9 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - func (s *Scanner) SetDebugWriter(writer io.Writer) { s.debug = debug.New(writer, "cloudformation", "scanner") + s.parserOptions = append(s.parserOptions, options.ParserWithDebug(writer)) } func (s *Scanner) SetPolicyDirs(dirs ...string) { @@ -132,14 +128,13 @@ func New(opts ...options.ScannerOption) *Scanner { for _, opt := range opts { opt(s) } - s.addParserOptions(options.ParserWithSkipRequiredCheck(s.skipRequired)) s.parser = parser.New(s.parserOptions...) return s } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } diff --git a/pkg/iac/scanners/dockerfile/parser/parser.go b/pkg/iac/scanners/dockerfile/parser/parser.go index d6ff7b4df21a..4255b4609b46 100644 --- a/pkg/iac/scanners/dockerfile/parser/parser.go +++ b/pkg/iac/scanners/dockerfile/parser/parser.go @@ -12,7 +12,6 @@ import ( "github.com/moby/buildkit/frontend/dockerfile/parser" "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) @@ -20,18 +19,13 @@ import ( var _ options.ConfigurableParser = (*Parser)(nil) type Parser struct { - debug debug.Logger - skipRequired bool + debug debug.Logger } func (p *Parser) SetDebugWriter(writer io.Writer) { p.debug = debug.New(writer, "dockerfile", "parser") } -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - // New creates a new Dockerfile parser func New(opts ...options.ParserOption) *Parser { p := &Parser{} @@ -56,9 +50,7 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st if entry.IsDir() { return nil } - if !p.Required(path) { - return nil - } + df, err := p.ParseFile(ctx, target, path) if err != nil { // TODO add debug for parse errors @@ -82,13 +74,6 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (*dockerf return p.parse(path, f) } -func (p *Parser) Required(path string) bool { - if p.skipRequired { - return true - } - return detection.IsType(path, nil, detection.FileTypeDockerfile) -} - func (p *Parser) parse(path string, r io.Reader) (*dockerfile.Dockerfile, error) { parsed, err := parser.Parse(r) if err != nil { diff --git a/pkg/iac/scanners/dockerfile/scanner.go b/pkg/iac/scanners/dockerfile/scanner.go index 29df54634d58..561872c70636 100644 --- a/pkg/iac/scanners/dockerfile/scanner.go +++ b/pkg/iac/scanners/dockerfile/scanner.go @@ -19,17 +19,17 @@ import ( var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - debug debug.Logger - policyDirs []string - policyReaders []io.Reader - parser *parser.Parser - regoScanner *rego.Scanner - skipRequired bool - options []options.ScannerOption - frameworks []framework.Framework - spec string - sync.Mutex +type Scanner struct { + mu sync.Mutex + debug debug.Logger + policyDirs []string + policyReaders []io.Reader + parser *parser.Parser + regoScanner *rego.Scanner + options []options.ScannerOption + parserOpts []options.ParserOption + frameworks []framework.Framework + spec string loadEmbeddedLibraries bool loadEmbeddedPolicies bool } @@ -63,12 +63,9 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - func (s *Scanner) SetDebugWriter(writer io.Writer) { s.debug = debug.New(writer, "dockerfile", "scanner") + s.parserOpts = append(s.parserOpts, options.ParserWithDebug(writer)) } func (s *Scanner) SetTraceWriter(_ io.Writer) { @@ -110,7 +107,7 @@ func NewScanner(opts ...options.ScannerOption) *Scanner { for _, opt := range opts { opt(s) } - s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) + s.parser = parser.New() return s } @@ -154,8 +151,8 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } diff --git a/pkg/iac/scanners/helm/parser/parser.go b/pkg/iac/scanners/helm/parser/parser.go index 8768ac459867..bf4a9fc91721 100644 --- a/pkg/iac/scanners/helm/parser/parser.go +++ b/pkg/iac/scanners/helm/parser/parser.go @@ -34,7 +34,6 @@ type Parser struct { ChartSource string filepaths []string debug debug.Logger - skipRequired bool workingFS fs.FS valuesFiles []string values []string @@ -53,10 +52,6 @@ func (p *Parser) SetDebugWriter(writer io.Writer) { p.debug = debug.New(writer, "helm", "parser") } -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - func (p *Parser) SetValuesFile(s ...string) { p.valuesFiles = s } @@ -129,10 +124,6 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) error { return nil } - if !p.required(path, p.workingFS) { - return nil - } - if detection.IsArchive(path) { tarFS, err := p.addTarToFS(path) if errors.Is(err, errSkipFS) { @@ -310,15 +301,3 @@ func getManifestPath(manifest string) string { } return manifestFilePathParts[0] } - -func (p *Parser) required(path string, workingFS fs.FS) bool { - if p.skipRequired { - return true - } - content, err := fs.ReadFile(workingFS, path) - if err != nil { - return false - } - - return detection.IsType(path, bytes.NewReader(content), detection.FileTypeHelm) -} diff --git a/pkg/iac/scanners/helm/scanner.go b/pkg/iac/scanners/helm/scanner.go index fc54af44781f..fe74911c51bc 100644 --- a/pkg/iac/scanners/helm/scanner.go +++ b/pkg/iac/scanners/helm/scanner.go @@ -36,7 +36,6 @@ type Scanner struct { loadEmbeddedLibraries bool loadEmbeddedPolicies bool policyFS fs.FS - skipRequired bool frameworks []framework.Framework spec string regoScanner *rego.Scanner @@ -88,12 +87,9 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - func (s *Scanner) SetDebugWriter(writer io.Writer) { s.debug = debug.New(writer, "helm", "scanner") + s.parserOptions = append(s.parserOptions, options.ParserWithDebug(writer)) } func (s *Scanner) SetTraceWriter(_ io.Writer) { diff --git a/pkg/iac/scanners/json/parser/parser.go b/pkg/iac/scanners/json/parser/parser.go index 340f23c9d11c..8f35794229ef 100644 --- a/pkg/iac/scanners/json/parser/parser.go +++ b/pkg/iac/scanners/json/parser/parser.go @@ -8,25 +8,19 @@ import ( "path/filepath" "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) var _ options.ConfigurableParser = (*Parser)(nil) type Parser struct { - debug debug.Logger - skipRequired bool + debug debug.Logger } func (p *Parser) SetDebugWriter(writer io.Writer) { p.debug = debug.New(writer, "json", "parser") } -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - // New creates a new parser func New(opts ...options.ParserOption) *Parser { p := &Parser{} @@ -51,14 +45,13 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st if entry.IsDir() { return nil } - if !p.Required(path) { - return nil - } + df, err := p.ParseFile(ctx, target, path) if err != nil { p.debug.Log("Parse error in '%s': %s", path, err) return nil } + files[path] = df return nil }); err != nil { @@ -80,10 +73,3 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (any, err } return target, nil } - -func (p *Parser) Required(path string) bool { - if p.skipRequired { - return true - } - return detection.IsType(path, nil, detection.FileTypeJSON) -} diff --git a/pkg/iac/scanners/json/scanner.go b/pkg/iac/scanners/json/scanner.go index 3d563c34c790..3aa0dffdb485 100644 --- a/pkg/iac/scanners/json/scanner.go +++ b/pkg/iac/scanners/json/scanner.go @@ -19,15 +19,15 @@ import ( var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - debug debug.Logger - policyDirs []string - policyReaders []io.Reader - parser *parser.Parser - regoScanner *rego.Scanner - skipRequired bool - options []options.ScannerOption - sync.Mutex +type Scanner struct { + mu sync.Mutex + debug debug.Logger + policyDirs []string + policyReaders []io.Reader + parser *parser.Parser + regoScanner *rego.Scanner + options []options.ScannerOption + parserOpts []options.ParserOption frameworks []framework.Framework spec string loadEmbeddedPolicies bool @@ -61,6 +61,7 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { func (s *Scanner) SetDebugWriter(writer io.Writer) { s.debug = debug.New(writer, "json", "scanner") + s.parserOpts = append(s.parserOpts, options.ParserWithDebug(writer)) } func (s *Scanner) SetTraceWriter(_ io.Writer) { @@ -76,10 +77,6 @@ func (s *Scanner) SetPolicyDirs(dirs ...string) { func (s *Scanner) SetDataDirs(_ ...string) {} func (s *Scanner) SetPolicyNamespaces(_ ...string) {} -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - func (s *Scanner) SetPolicyFilesystem(_ fs.FS) { // handled by rego when option is passed on } @@ -96,7 +93,7 @@ func NewScanner(opts ...options.ScannerOption) *Scanner { for _, opt := range opts { opt(s) } - s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) + s.parser = parser.New(s.parserOpts...) return s } @@ -144,8 +141,8 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } diff --git a/pkg/iac/scanners/kubernetes/parser/parser.go b/pkg/iac/scanners/kubernetes/parser/parser.go index a1c5ecf46962..e2f225a9fb86 100644 --- a/pkg/iac/scanners/kubernetes/parser/parser.go +++ b/pkg/iac/scanners/kubernetes/parser/parser.go @@ -1,7 +1,6 @@ package parser import ( - "bytes" "context" "encoding/json" "fmt" @@ -15,25 +14,19 @@ import ( kyaml "sigs.k8s.io/yaml" "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) var _ options.ConfigurableParser = (*Parser)(nil) type Parser struct { - debug debug.Logger - skipRequired bool + debug debug.Logger } func (p *Parser) SetDebugWriter(writer io.Writer) { p.debug = debug.New(writer, "kubernetes", "parser") } -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - // New creates a new K8s parser func New(opts ...options.ParserOption) *Parser { p := &Parser{} @@ -57,14 +50,13 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st if entry.IsDir() { return nil } - if !p.required(target, path) { - return nil - } + parsed, err := p.ParseFile(ctx, target, path) if err != nil { p.debug.Log("Parse error in '%s': %s", path, err) return nil } + files[path] = parsed return nil }); err != nil { @@ -83,21 +75,6 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]any, e return p.Parse(f, path) } -func (p *Parser) required(fsys fs.FS, path string) bool { - if p.skipRequired { - return true - } - f, err := fsys.Open(filepath.ToSlash(path)) - if err != nil { - return false - } - defer func() { _ = f.Close() }() - if data, err := io.ReadAll(f); err == nil { - return detection.IsType(path, bytes.NewReader(data), detection.FileTypeKubernetes) - } - return false -} - func (p *Parser) Parse(r io.Reader, path string) ([]any, error) { contents, err := io.ReadAll(r) diff --git a/pkg/iac/scanners/kubernetes/scanner.go b/pkg/iac/scanners/kubernetes/scanner.go index 44f13ce5b003..c5437f292d85 100644 --- a/pkg/iac/scanners/kubernetes/scanner.go +++ b/pkg/iac/scanners/kubernetes/scanner.go @@ -23,15 +23,15 @@ import ( var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - debug debug.Logger - options []options.ScannerOption - policyDirs []string - policyReaders []io.Reader - regoScanner *rego.Scanner - parser *parser.Parser - skipRequired bool - sync.Mutex +type Scanner struct { + mu sync.Mutex + debug debug.Logger + options []options.ScannerOption + parserOpts []options.ParserOption + policyDirs []string + policyReaders []io.Reader + regoScanner *rego.Scanner + parser *parser.Parser loadEmbeddedPolicies bool frameworks []framework.Framework spec string @@ -62,12 +62,9 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - func (s *Scanner) SetDebugWriter(writer io.Writer) { s.debug = debug.New(writer, "kubernetes", "scanner") + s.parserOpts = append(s.parserOpts, options.ParserWithDebug(writer)) } func (s *Scanner) SetTraceWriter(_ io.Writer) { @@ -100,7 +97,7 @@ func NewScanner(opts ...options.ScannerOption) *Scanner { for _, opt := range opts { opt(s) } - s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) + s.parser = parser.New(s.parserOpts...) return s } @@ -109,8 +106,8 @@ func (s *Scanner) Name() string { } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } diff --git a/pkg/iac/scanners/options/parser.go b/pkg/iac/scanners/options/parser.go index 65ec41bb825d..e411126a6329 100644 --- a/pkg/iac/scanners/options/parser.go +++ b/pkg/iac/scanners/options/parser.go @@ -4,17 +4,10 @@ import "io" type ConfigurableParser interface { SetDebugWriter(io.Writer) - SetSkipRequiredCheck(bool) } type ParserOption func(s ConfigurableParser) -func ParserWithSkipRequiredCheck(skip bool) ParserOption { - return func(s ConfigurableParser) { - s.SetSkipRequiredCheck(skip) - } -} - // ParserWithDebug specifies an io.Writer for debug logs - if not set, they are discarded func ParserWithDebug(w io.Writer) ParserOption { return func(s ConfigurableParser) { diff --git a/pkg/iac/scanners/options/scanner.go b/pkg/iac/scanners/options/scanner.go index 8e79b0c4a185..291400887037 100644 --- a/pkg/iac/scanners/options/scanner.go +++ b/pkg/iac/scanners/options/scanner.go @@ -14,7 +14,6 @@ type ConfigurableScanner interface { SetPolicyDirs(...string) SetDataDirs(...string) SetPolicyNamespaces(...string) - SetSkipRequiredCheck(bool) SetPolicyReaders([]io.Reader) SetPolicyFilesystem(fs.FS) SetDataFilesystem(fs.FS) @@ -104,12 +103,6 @@ func ScannerWithPolicyNamespaces(namespaces ...string) ScannerOption { } } -func ScannerWithSkipRequiredCheck(skip bool) ScannerOption { - return func(s ConfigurableScanner) { - s.SetSkipRequiredCheck(skip) - } -} - func ScannerWithPolicyFilesystem(f fs.FS) ScannerOption { return func(s ConfigurableScanner) { s.SetPolicyFilesystem(f) diff --git a/pkg/iac/scanners/terraform/parser/parser.go b/pkg/iac/scanners/terraform/parser/parser.go index fa511fed54c2..4f79c1fdf6f2 100644 --- a/pkg/iac/scanners/terraform/parser/parser.go +++ b/pkg/iac/scanners/terraform/parser/parser.go @@ -48,7 +48,6 @@ type Parser struct { allowDownloads bool skipCachedModules bool fsMap map[string]fs.FS - skipRequired bool configsFS fs.FS } @@ -76,10 +75,6 @@ func (p *Parser) SetSkipCachedModules(b bool) { p.skipCachedModules = b } -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - func (p *Parser) SetConfigsFS(fsys fs.FS) { p.configsFS = fsys } diff --git a/pkg/iac/scanners/terraform/scanner.go b/pkg/iac/scanners/terraform/scanner.go index c999acf337f5..a64201e1fcc5 100644 --- a/pkg/iac/scanners/terraform/scanner.go +++ b/pkg/iac/scanners/terraform/scanner.go @@ -27,8 +27,8 @@ var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) var _ ConfigurableTerraformScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - sync.Mutex +type Scanner struct { + mu sync.Mutex options []options.ScannerOption parserOpt []options.ParserOption executorOpt []executor.Option @@ -87,10 +87,6 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.parserOpt = append(s.parserOpt, options.ParserWithSkipRequiredCheck(skip)) -} - func (s *Scanner) SetDebugWriter(writer io.Writer) { s.parserOpt = append(s.parserOpt, options.ParserWithDebug(writer)) s.executorOpt = append(s.executorOpt, executor.OptionWithDebugWriter(writer)) @@ -131,8 +127,8 @@ func New(opts ...options.ScannerOption) *Scanner { } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } diff --git a/pkg/iac/scanners/terraformplan/tfjson/scanner.go b/pkg/iac/scanners/terraformplan/tfjson/scanner.go index 6f62d822177f..b25eed6ae42b 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/scanner.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner.go @@ -23,8 +23,8 @@ var tfPlanExts = []string{ } type Scanner struct { - parser parser.Parser - parserOpt []options.ParserOption + parser *parser.Parser + parserOpt []parser.Option debug debug.Logger options []options.ScannerOption @@ -68,12 +68,8 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.parserOpt = append(s.parserOpt, options.ParserWithSkipRequiredCheck(skip)) -} - func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.parserOpt = append(s.parserOpt, options.ParserWithDebug(writer)) + s.parserOpt = append(s.parserOpt, parser.OptionWithDebugWriter(writer)) s.executorOpt = append(s.executorOpt, executor.OptionWithDebugWriter(writer)) s.debug = debug.New(writer, "tfplan", "scanner") } @@ -128,12 +124,12 @@ func (s *Scanner) ScanFS(ctx context.Context, inputFS fs.FS, dir string) (scan.R func New(opts ...options.ScannerOption) *Scanner { scanner := &Scanner{ - parser: *parser.New(), options: opts, } for _, o := range opts { o(scanner) } + scanner.parser = parser.New(scanner.parserOpt...) return scanner } diff --git a/pkg/iac/scanners/toml/parser/parser.go b/pkg/iac/scanners/toml/parser/parser.go index a8cdcd730f86..6beed83b1908 100644 --- a/pkg/iac/scanners/toml/parser/parser.go +++ b/pkg/iac/scanners/toml/parser/parser.go @@ -9,25 +9,19 @@ import ( "github.com/BurntSushi/toml" "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) var _ options.ConfigurableParser = (*Parser)(nil) type Parser struct { - debug debug.Logger - skipRequired bool + debug debug.Logger } func (p *Parser) SetDebugWriter(writer io.Writer) { p.debug = debug.New(writer, "toml", "parser") } -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - // New creates a new parser func New(opts ...options.ParserOption) *Parser { p := &Parser{} @@ -52,9 +46,7 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st if entry.IsDir() { return nil } - if !p.Required(path) { - return nil - } + df, err := p.ParseFile(ctx, target, path) if err != nil { p.debug.Log("Parse error in '%s': %s", path, err) @@ -81,10 +73,3 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) (any, err } return target, nil } - -func (p *Parser) Required(path string) bool { - if p.skipRequired { - return true - } - return detection.IsType(path, nil, detection.FileTypeTOML) -} diff --git a/pkg/iac/scanners/toml/scanner.go b/pkg/iac/scanners/toml/scanner.go index 0a05fdbac18f..37e1807ac254 100644 --- a/pkg/iac/scanners/toml/scanner.go +++ b/pkg/iac/scanners/toml/scanner.go @@ -17,15 +17,15 @@ import ( var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - debug debug.Logger - options []options.ScannerOption - policyDirs []string - policyReaders []io.Reader - parser *parser.Parser - regoScanner *rego.Scanner - skipRequired bool - sync.Mutex +type Scanner struct { + mu sync.Mutex + debug debug.Logger + options []options.ScannerOption + parserOptions []options.ParserOption + policyDirs []string + policyReaders []io.Reader + parser *parser.Parser + regoScanner *rego.Scanner frameworks []framework.Framework spec string loadEmbeddedPolicies bool @@ -60,12 +60,9 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - func (s *Scanner) SetDebugWriter(writer io.Writer) { s.debug = debug.New(writer, "toml", "scanner") + s.parserOptions = append(s.parserOptions, options.ParserWithDebug(writer)) } func (s *Scanner) SetTraceWriter(_ io.Writer) {} @@ -94,7 +91,7 @@ func NewScanner(opts ...options.ScannerOption) *Scanner { for _, opt := range opts { opt(s) } - s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) + s.parser = parser.New(s.parserOptions...) return s } @@ -138,8 +135,8 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } diff --git a/pkg/iac/scanners/yaml/parser/parser.go b/pkg/iac/scanners/yaml/parser/parser.go index 6f113635be4a..d8b50faf2437 100644 --- a/pkg/iac/scanners/yaml/parser/parser.go +++ b/pkg/iac/scanners/yaml/parser/parser.go @@ -11,25 +11,19 @@ import ( "gopkg.in/yaml.v3" "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) var _ options.ConfigurableParser = (*Parser)(nil) type Parser struct { - debug debug.Logger - skipRequired bool + debug debug.Logger } func (p *Parser) SetDebugWriter(writer io.Writer) { p.debug = debug.New(writer, "yaml", "parser") } -func (p *Parser) SetSkipRequiredCheck(b bool) { - p.skipRequired = b -} - // New creates a new parser func New(opts ...options.ParserOption) *Parser { p := &Parser{} @@ -54,9 +48,7 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st if entry.IsDir() { return nil } - if !p.Required(path) { - return nil - } + df, err := p.ParseFile(ctx, target, path) if err != nil { p.debug.Log("Parse error in '%s': %s", path, err) @@ -101,10 +93,3 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]any, e return results, nil } - -func (p *Parser) Required(path string) bool { - if p.skipRequired { - return true - } - return detection.IsType(path, nil, detection.FileTypeYAML) -} diff --git a/pkg/iac/scanners/yaml/scanner.go b/pkg/iac/scanners/yaml/scanner.go index 0adc43bbd4cf..534ccbd8cb7b 100644 --- a/pkg/iac/scanners/yaml/scanner.go +++ b/pkg/iac/scanners/yaml/scanner.go @@ -17,15 +17,15 @@ import ( var _ options.ConfigurableScanner = (*Scanner)(nil) -type Scanner struct { // nolint: gocritic - options []options.ScannerOption - debug debug.Logger - policyDirs []string - policyReaders []io.Reader - parser *parser.Parser - regoScanner *rego.Scanner - skipRequired bool - sync.Mutex +type Scanner struct { + mu sync.Mutex + options []options.ScannerOption + parserOptions []options.ParserOption + debug debug.Logger + policyDirs []string + policyReaders []io.Reader + parser *parser.Parser + regoScanner *rego.Scanner frameworks []framework.Framework spec string loadEmbeddedLibraries bool @@ -60,12 +60,9 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetSkipRequiredCheck(skip bool) { - s.skipRequired = skip -} - func (s *Scanner) SetDebugWriter(writer io.Writer) { s.debug = debug.New(writer, "yaml", "scanner") + s.parserOptions = append(s.parserOptions, options.ParserWithDebug(writer)) } func (s *Scanner) SetTraceWriter(_ io.Writer) {} @@ -93,7 +90,7 @@ func NewScanner(opts ...options.ScannerOption) *Scanner { for _, opt := range opts { opt(s) } - s.parser = parser.New(options.ParserWithSkipRequiredCheck(s.skipRequired)) + s.parser = parser.New(s.parserOptions...) return s } @@ -139,8 +136,8 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R } func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() if s.regoScanner != nil { return s.regoScanner, nil } diff --git a/pkg/misconf/scanner.go b/pkg/misconf/scanner.go index 90ee90cb4216..8730b03d2faf 100644 --- a/pkg/misconf/scanner.go +++ b/pkg/misconf/scanner.go @@ -215,7 +215,6 @@ func (s *Scanner) filterFS(fsys fs.FS) (fs.FS, error) { func scannerOptions(t detection.FileType, opt ScannerOption) ([]options.ScannerOption, error) { opts := []options.ScannerOption{ - options.ScannerWithSkipRequiredCheck(true), options.ScannerWithEmbeddedPolicies(!opt.DisableEmbeddedPolicies), options.ScannerWithEmbeddedLibraries(!opt.DisableEmbeddedLibraries), options.ScannerWithIncludeDeprecatedChecks(opt.IncludeDeprecatedChecks), From fd8348d610f20c6c33da81cd7b0e7d5504ce26be Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Fri, 2 Aug 2024 14:41:56 +0400 Subject: [PATCH 272/352] feat(vuln): Add `--detection-priority` flag for accuracy tuning (#7288) Signed-off-by: knqyf263 --- docs/docs/coverage/language/dart.md | 16 +- docs/docs/coverage/language/golang.md | 9 +- docs/docs/coverage/language/java.md | 13 +- docs/docs/coverage/language/python.md | 11 +- docs/docs/coverage/os/conda.md | 5 + .../configuration/cli/trivy_filesystem.md | 4 + .../configuration/cli/trivy_image.md | 4 + .../configuration/cli/trivy_kubernetes.md | 4 + .../configuration/cli/trivy_repository.md | 4 + .../configuration/cli/trivy_rootfs.md | 4 + .../configuration/cli/trivy_sbom.md | 4 + .../references/configuration/cli/trivy_vm.md | 4 + docs/docs/scanner/vulnerability.md | 73 +++++++ integration/client_server_test.go | 14 +- integration/standalone_tar_test.go | 33 ++- integration/testdata/fixtures/db/python.yaml | 8 + .../testdata/fixtures/db/vulnerability.yaml | 21 +- .../testdata/ubi-7-comprehensive.json.golden | 192 ++++++++++++++++++ pkg/cache/key.go | 24 ++- pkg/cache/key_test.go | 51 +++-- pkg/commands/artifact/run.go | 6 + pkg/dependency/parser/dart/pub/parse.go | 24 ++- pkg/dependency/parser/dart/pub/parse_test.go | 42 +++- pkg/fanal/analyzer/analyzer.go | 34 ++-- .../analyzer/language/dart/pub/pubspec.go | 4 +- pkg/fanal/artifact/artifact.go | 2 + pkg/fanal/types/detection.go | 10 + pkg/flag/scan_flags.go | 91 +++++---- pkg/types/scan.go | 91 +++++++++ pkg/types/target.go | 94 --------- 30 files changed, 675 insertions(+), 221 deletions(-) create mode 100644 integration/testdata/ubi-7-comprehensive.json.golden create mode 100644 pkg/fanal/types/detection.go delete mode 100644 pkg/types/target.go diff --git a/docs/docs/coverage/language/dart.md b/docs/docs/coverage/language/dart.md index a64a19eb83c2..5e86b6c399f4 100644 --- a/docs/docs/coverage/language/dart.md +++ b/docs/docs/coverage/language/dart.md @@ -11,9 +11,9 @@ The following scanners are supported. The following table provides an outline of the features Trivy offers. -| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | -|-------------------------|--------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| -| [Dart][dart-repository] | pubspec.lock | ✓ | Included | ✓ | - | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|-------------------------|--------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| [Dart][dart-repository] | pubspec.lock | ✓ | Included | ✓ | - | ✓ | ## Dart In order to detect dependencies, Trivy searches for `pubspec.lock`. @@ -22,11 +22,13 @@ Trivy marks indirect dependencies, but `pubspec.lock` file doesn't have options So Trivy includes all dependencies in report. ### SDK dependencies -Dart uses version `0.0.0` for SDK dependencies (e.g. Flutter). It is not possible to accurately determine the versions of these dependencies. +Dart uses version `0.0.0` for SDK dependencies (e.g. Flutter). +It is not possible to accurately determine the versions of these dependencies. +Trivy just treats them as `0.0.0`. -Therefore, we use the first version of the constraint for the SDK. +If [--detection-priority comprehensive][detection-priority] is passed, Trivy uses the minimum version of the constraint for the SDK. +For example, in the following case, the version of `flutter` would be `3.3.0`: -For example in this case the version of `flutter` should be `3.3.0`: ```yaml flutter: dependency: "direct main" @@ -40,6 +42,7 @@ sdks: ### Dependency tree To build `dependency tree` Trivy parses [cache directory][cache-directory]. Currently supported default directories and `PUB_CACHE` environment (absolute path only). + !!! note Make sure the cache directory contains all the dependencies installed in your application. To download missing dependencies, use `dart pub get` command. @@ -47,3 +50,4 @@ To build `dependency tree` Trivy parses [cache directory][cache-directory]. Curr [dart-repository]: https://pub.dev/ [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies [cache-directory]: https://dart.dev/tools/pub/glossary#system-cache +[detection-priority]: ../../scanner/vulnerability.md#detection-priority \ No newline at end of file diff --git a/docs/docs/coverage/language/golang.md b/docs/docs/coverage/language/golang.md index 6b3646329318..cf3e160a608b 100644 --- a/docs/docs/coverage/language/golang.md +++ b/docs/docs/coverage/language/golang.md @@ -16,10 +16,10 @@ The following scanners are supported. The table below provides an outline of the features Trivy offers. -| Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | Stdlib | -|----------|:-----------:|:-----------------|:------------------------------------:|:------:| -| Modules | ✅ | Include | ✅[^2] | - | -| Binaries | ✅ | Exclude | - | ✅[^4] | +| Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | Stdlib | [Detection Priority][detection-priority] | +|----------|:-----------:|:-----------------|:------------------------------------:|:------:|:----------------------------------------:| +| Modules | ✅ | Include | ✅[^2] | - | - | +| Binaries | ✅ | Exclude | - | ✅[^4] | Not needed | !!! note Trivy scans only dependencies of the Go project. @@ -95,3 +95,4 @@ empty if it cannot do so[^5]. For the second case, the version of such packages [^5]: See https://github.com/golang/go/issues/63432#issuecomment-1751610604 [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[detection-priority]: ../../scanner/vulnerability.md#detection-priority diff --git a/docs/docs/coverage/language/java.md b/docs/docs/coverage/language/java.md index bb90366c1772..67cd8c135b9d 100644 --- a/docs/docs/coverage/language/java.md +++ b/docs/docs/coverage/language/java.md @@ -12,12 +12,12 @@ Each artifact supports the following scanners: The following table provides an outline of the features Trivy offers. -| Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | -|------------------|:---------------------:|:----------------:|:------------------------------------:|:--------:| -| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | -| pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | -| *gradle.lockfile | - | Exclude | ✓ | ✓ | -| *.sbt.lock | - | Exclude | - | ✓ | +| Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|------------------|:---------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | Not needed | +| pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | - | +| *gradle.lockfile | - | Exclude | ✓ | ✓ | Not needed | +| *.sbt.lock | - | Exclude | - | ✓ | Not needed | These may be enabled or disabled depending on the target. See [here](./index.md) for the detail. @@ -119,3 +119,4 @@ Make sure that you have cache[^8] directory to find licenses from `*.pom` depend [maven-central]: https://repo.maven.apache.org/maven2/ [maven-pom-repos]: https://maven.apache.org/settings.html#repositories [sbt-dependency-lock]: https://stringbean.github.io/sbt-dependency-lock +[detection-priority]: ../../scanner/vulnerability.md#detection-priority diff --git a/docs/docs/coverage/language/python.md b/docs/docs/coverage/language/python.md index c4f6b6d83e86..15cadd6f96c3 100644 --- a/docs/docs/coverage/language/python.md +++ b/docs/docs/coverage/language/python.md @@ -21,11 +21,11 @@ The following scanners are supported for Python packages. The following table provides an outline of the features Trivy offers. -| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | -|-----------------|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| -| pip | requirements.txt | - | Include | - | ✓ | -| Pipenv | Pipfile.lock | ✓ | Include | - | ✓ | -| Poetry | poetry.lock | ✓ | Exclude | ✓ | - | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|-----------------|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| pip | requirements.txt | - | Include | - | ✓ | - | +| Pipenv | Pipfile.lock | ✓ | Include | - | ✓ | Not needed | +| Poetry | poetry.lock | ✓ | Exclude | ✓ | - | Not needed | | Packaging | Dependency graph | @@ -130,3 +130,4 @@ Trivy looks for `.dist-info/META-DATA` to identify Python packages. [^1]: Trivy checks `python`, `python3`, `python2` and `python.exe` file names. [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[detection-priority]: ../../scanner/vulnerability.md#detection-priority diff --git a/docs/docs/coverage/os/conda.md b/docs/docs/coverage/os/conda.md index 10c1e93c1b8c..773bd8097b6d 100644 --- a/docs/docs/coverage/os/conda.md +++ b/docs/docs/coverage/os/conda.md @@ -8,6 +8,9 @@ Trivy supports the following scanners for Conda packages. | Vulnerability | - | | License | ✓ | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|-----------------|-----------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| Conda | environment.yml | - | Include | - | ✓ | - | ## `.json` @@ -41,3 +44,5 @@ To correctly define licenses, make sure your `environment.yml`[^1] contains `pre [environment.yml]: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#sharing-an-environment [env-version-range]: https://docs.conda.io/projects/conda-build/en/latest/resources/package-spec.html#examples-of-package-specs [prefix]: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#specifying-a-location-for-an-environment +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[detection-priority]: ../../scanner/vulnerability.md#detection-priority diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 99f1df23c069..2202bc27f518 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -30,6 +30,10 @@ trivy filesystem [flags] PATH --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index e7f2e3c70ea1..a1de0595a7bc 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -44,6 +44,10 @@ trivy image [flags] IMAGE_NAME --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --docker-host string unix domain socket path to use for docker scanning --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 6c98a35d9020..4516509d5834 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -39,6 +39,10 @@ trivy kubernetes [flags] [CONTEXT] --config-data strings specify paths from which data for the Rego checks will be recursively loaded --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --disable-node-collector When the flag is activated, the node-collector job will not be executed, thus skipping misconfiguration findings on the node. --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index ba9aa4983d22..5d4bc5ce4161 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -30,6 +30,10 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 39ccef2dd07a..60fdf4e623fa 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -32,6 +32,10 @@ trivy rootfs [flags] ROOTDIR --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index d6d1bf07e1c6..1b0df922c7fc 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -25,6 +25,10 @@ trivy sbom [flags] SBOM_PATH --compliance string compliance report to generate --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --exit-code int specify exit code when any security issues are found diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 110cc0a3ecb7..4bae981c7577 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -28,6 +28,10 @@ trivy vm [flags] VM_IMAGE --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --detection-priority string specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. + (precise,comprehensive) (default "precise") --download-db-only download/update vulnerability database but don't run a scan --download-java-db-only download/update Java index database but don't run a scan --enable-modules strings [EXPERIMENTAL] module names to enable diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md index 9df2b43d8b80..00a9594ead62 100644 --- a/docs/docs/scanner/vulnerability.md +++ b/docs/docs/scanner/vulnerability.md @@ -198,6 +198,54 @@ The default is `ghcr.io/aquasecurity/trivy-java-db`. If authentication is required, you need to run `docker login YOUR_REGISTRY`. Currently, specifying a username and password is not supported. +## Detection Behavior +Trivy prioritizes precision in vulnerability detection, aiming to minimize false positives while potentially accepting some false negatives. +This approach is particularly relevant in two key areas: + +- Handling Software Installed via OS Packages +- Handling Packages with Unspecified Versions + +### Handling Software Installed via OS Packages +For files installed by OS package managers, such as `apt`, Trivy exclusively uses advisories from the OS vendor. +This means that even if a JAR file is present in a container image, if it was installed via an OS package manager (e.g., `apt`), Trivy will not analyze the JAR file itself and use upstream security advisories. + +For example, consider the Python `requests` package in Red Hat Universal Base Image 8: + +```bash +[root@987ee49dc93d /]# head -n 3 /usr/lib/python3.6/site-packages/requests-2.20.0-py3.6.egg-info/PKG-INFO +Metadata-Version: 2.1 +Name: requests +Version: 2.20.0 +``` + +Version 2.20.0 is installed, and this package is installed by `dnf`. + +```bash +[root@987ee49dc93d /]# rpm -ql python3-requests | grep PKG-INFO +/usr/lib/python3.6/site-packages/requests-2.20.0-py3.6.egg-info/PKG-INFO +``` + +At first glance, this might seem vulnerable to [CVE-2023-32681], which affects versions of requests prior to v2.31.0. +However, Red Hat backported the fix to v2.20.0-3 in [RHSA-2023:4520], and the package is not vulnerable. + +- Upstream (PyPI [requests]): Fixed in v2.31.0 +- Red Hat (`python-requests`): Backported fix applied in v2.20.0-3 (RHSA-2023:4520) + +If Trivy were to detect CVE-2023-32681 in this case, it would be a false positive. +This illustrates why using the correct security advisory is crucial to avoid false detections. +To minimize false positives, Trivy trusts the OS vendor's advisory for software installed via OS package managers and does not use upstream advisories for these packages. + +However, this approach may lead to false negatives if the OS vendor's advisories are delayed or missing. +In such cases, using [--detection-priority comprehensive](#detection-priority) allows Trivy to consider upstream advisories (e.g., [GitHub Advisory Database][ghsa]), potentially increasing false positives but reducing false negatives. + +### Handling Packages with Unspecified Versions +When a package version cannot be uniquely determined (e.g., `package-a: ">=3.0"`), Trivy typically skips vulnerability detection for that package to avoid false positives. +If a lock file is present with fixed versions, Trivy will use those for detection. + +To detect potential vulnerabilities even with unspecified versions, use [--detection-priority comprehensive](#detection-priority). +This option makes Trivy use the minimum version in the specified range for vulnerability detection. +While this may increase false positives if the actual version used is not the minimum, it helps reduce false negatives. + ## Configuration This section describes vulnerability-specific configuration. Other common options are documented [here](../configuration/index.md). @@ -307,6 +355,25 @@ By default, all relationships are included in the scan. !!! warning As it may not provide a complete package list, `--pkg-relationships` cannot be used with `--dependency-tree`, `--vex` or SBOM generation. +### Detection Priority + +Trivy provides a `--detection-priority` flag to control the balance between false positives and false negatives in vulnerability detection. +This concept is similar to the relationship between [precision and recall][precision-recall] in machine learning evaluation. + +```bash +$ trivy image --detection-priority {precise|comprehensive} alpine:3.15 +``` + +- `precise`: This mode prioritizes reducing false positives. It results in less noisy vulnerability reports but may miss some potential vulnerabilities. +- `comprehensive`: This mode aims to detect more vulnerabilities, potentially including some that might be false positives. + It provides broader coverage but may increase the noise in the results. + +The default value is `precise`. Also refer to the [detection behavior](#detection-behavior) section for more information. + +Regardless of the chosen mode, user review of detected vulnerabilities is crucial: + +- `precise`: Review thoroughly, considering potential missed vulnerabilities. +- `comprehensive`: Carefully investigate each reported vulnerability due to increased false positive possibility. [^1]: https://github.com/GoogleContainerTools/distroless @@ -353,3 +420,9 @@ By default, all relationships are included in the scan. [nvd]: https://nvd.nist.gov/vuln [k8s-cve]: https://kubernetes.io/docs/reference/issues-security/official-cve-feed/ + +[CVE-2023-32681]: https://nvd.nist.gov/vuln/detail/CVE-2023-32681 +[RHSA-2023:4520]: https://access.redhat.com/errata/RHSA-2023:4520 +[ghsa]: https://github.com/advisories +[requests]: https://pypi.org/project/requests/ +[precision-recall]: https://developers.google.com/machine-learning/crash-course/classification/precision-and-recall \ No newline at end of file diff --git a/integration/client_server_test.go b/integration/client_server_test.go index c6dd7fb74dc5..e0edce7aa6a8 100644 --- a/integration/client_server_test.go +++ b/integration/client_server_test.go @@ -287,7 +287,7 @@ func TestClientServer(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + osArgs := setupClient(t, tt.args, addr, cacheDir) if tt.args.secretConfig != "" { osArgs = append(osArgs, "--secret-config", tt.args.secretConfig) @@ -407,7 +407,7 @@ func TestClientServerWithFormat(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Setenv("AWS_REGION", "test-region") t.Setenv("AWS_ACCOUNT_ID", "123456789012") - osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + osArgs := setupClient(t, tt.args, addr, cacheDir) runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{ override: overrideUID, @@ -435,7 +435,7 @@ func TestClientServerWithCycloneDX(t *testing.T) { addr, cacheDir := setup(t, setupOptions{}) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + osArgs := setupClient(t, tt.args, addr, cacheDir) runTest(t, osArgs, tt.golden, "", types.FormatCycloneDX, runOptions{ fakeUUID: "3ff14136-e09f-4df9-80ea-%012d", }) @@ -488,7 +488,7 @@ func TestClientServerWithToken(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden) + osArgs := setupClient(t, tt.args, addr, cacheDir) runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{ override: overrideUID, wantErr: tt.wantErr, @@ -515,7 +515,7 @@ func TestClientServerWithRedis(t *testing.T) { golden := "testdata/alpine-39.json.golden" t.Run("alpine 3.9", func(t *testing.T) { - osArgs := setupClient(t, testArgs, addr, cacheDir, golden) + osArgs := setupClient(t, testArgs, addr, cacheDir) // Run Trivy client runTest(t, osArgs, golden, "", types.FormatJSON, runOptions{ @@ -527,7 +527,7 @@ func TestClientServerWithRedis(t *testing.T) { require.NoError(t, redisC.Terminate(ctx)) t.Run("sad path", func(t *testing.T) { - osArgs := setupClient(t, testArgs, addr, cacheDir, golden) + osArgs := setupClient(t, testArgs, addr, cacheDir) // Run Trivy client runTest(t, osArgs, "", "", types.FormatJSON, runOptions{ @@ -592,7 +592,7 @@ func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []stri return osArgs } -func setupClient(t *testing.T, c csArgs, addr string, cacheDir string, golden string) []string { +func setupClient(t *testing.T, c csArgs, addr string, cacheDir string) []string { if c.Command == "" { c.Command = "image" } diff --git a/integration/standalone_tar_test.go b/integration/standalone_tar_test.go index 2cb372b86b0a..dce852cf7f3c 100644 --- a/integration/standalone_tar_test.go +++ b/integration/standalone_tar_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" "github.com/stretchr/testify/require" @@ -15,13 +16,14 @@ import ( func TestTar(t *testing.T) { type args struct { - IgnoreUnfixed bool - Severity []string - IgnoreIDs []string - Format types.Format - Input string - SkipDirs []string - SkipFiles []string + IgnoreUnfixed bool + Severity []string + IgnoreIDs []string + Format types.Format + Input string + SkipDirs []string + SkipFiles []string + DetectionPriority ftypes.DetectionPriority } tests := []struct { name string @@ -240,7 +242,7 @@ func TestTar(t *testing.T) { golden: "testdata/centos-7.json.golden", }, { - name: "centos 7with --ignore-unfixed option", + name: "centos 7 with --ignore-unfixed option", args: args{ IgnoreUnfixed: true, Format: types.FormatJSON, @@ -274,6 +276,15 @@ func TestTar(t *testing.T) { }, golden: "testdata/ubi-7.json.golden", }, + { + name: "ubi 7 with comprehensive priority", + args: args{ + Format: types.FormatJSON, + Input: "testdata/fixtures/images/ubi-7.tar.gz", + DetectionPriority: ftypes.PriorityComprehensive, + }, + golden: "testdata/ubi-7-comprehensive.json.golden", + }, { name: "almalinux 8", args: args{ @@ -380,7 +391,7 @@ func TestTar(t *testing.T) { "-q", "--format", string(tt.args.Format), - "--skip-update", + "--skip-db-update", } if tt.args.IgnoreUnfixed { @@ -411,6 +422,10 @@ func TestTar(t *testing.T) { } } + if tt.args.DetectionPriority != "" { + osArgs = append(osArgs, "--detection-priority", string(tt.args.DetectionPriority)) + } + // Run Trivy runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{}) }) diff --git a/integration/testdata/fixtures/db/python.yaml b/integration/testdata/fixtures/db/python.yaml index 2d484feff17c..728f11b67e6d 100644 --- a/integration/testdata/fixtures/db/python.yaml +++ b/integration/testdata/fixtures/db/python.yaml @@ -14,3 +14,11 @@ - 0.11.6 VulnerableVersions: - < 0.11.6 + - bucket: setuptools + pairs: + - key: CVE-2022-40897 + value: + PatchedVersions: + - 65.5.1 + VulnerableVersions: + - < 65.5.1 \ No newline at end of file diff --git a/integration/testdata/fixtures/db/vulnerability.yaml b/integration/testdata/fixtures/db/vulnerability.yaml index 9b16712d0dbc..6fcdcece75bd 100644 --- a/integration/testdata/fixtures/db/vulnerability.yaml +++ b/integration/testdata/fixtures/db/vulnerability.yaml @@ -1399,4 +1399,23 @@ - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14155" - "https://nvd.nist.gov/vuln/detail/CVE-2020-14155" PublishedDate: "2020-06-15T17:15:00Z" - LastModifiedDate: "2022-04-28T15:06:00Z" \ No newline at end of file + LastModifiedDate: "2022-04-28T15:06:00Z" + - key: CVE-2022-40897 + value: + Title: "pypa-setuptools: Regular Expression Denial of Service (ReDoS) in package_index.py" + Description: "Python Packaging Authority (PyPA) setuptools before 65.5.1 allows remote attackers to cause a denial of service via HTML in a crafted package or custom PackageIndex page. There is a Regular Expression Denial of Service (ReDoS) in package_index.py." + Severity: MEDIUM + CweIDs: + - CWE-1333 + VendorSeverity: + ghsa: 3 + nvd: 2 + CVSS: + nvd: + V3Vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H" + V3Score: 5.9 + References: + - "https://access.redhat.com/errata/RHSA-2023:0952" + - "https://access.redhat.com/security/cve/CVE-2022-40897" + PublishedDate: "2022-12-23T00:15:13.987Z" + LastModifiedDate: "2024-06-21T19:15:23.877Z" diff --git a/integration/testdata/ubi-7-comprehensive.json.golden b/integration/testdata/ubi-7-comprehensive.json.golden new file mode 100644 index 000000000000..6df4b8241456 --- /dev/null +++ b/integration/testdata/ubi-7-comprehensive.json.golden @@ -0,0 +1,192 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/images/ubi-7.tar.gz", + "ArtifactType": "container_image", + "Metadata": { + "OS": { + "Family": "redhat", + "Name": "7.7" + }, + "ImageID": "sha256:6fecccc91c83e11ae4fede6793e9410841221d4779520c2b9e9fb7f7b3830264", + "DiffIDs": [ + "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac", + "sha256:ecb0311889b3478bc9b62660fa9391d5ebf8da4c6ae143cb33434873668f9e36" + ], + "ImageConfig": { + "architecture": "amd64", + "created": "2019-09-02T12:56:43.939095Z", + "docker_version": "1.13.1", + "history": [ + { + "created": "2019-09-02T12:56:36.440695936Z", + "comment": "Imported from -" + }, + { + "created": "2019-09-02T12:56:43.939095Z" + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac", + "sha256:ecb0311889b3478bc9b62660fa9391d5ebf8da4c6ae143cb33434873668f9e36" + ] + }, + "config": { + "Cmd": [ + "/bin/bash" + ], + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "container=oci" + ], + "Hostname": "0da2e3774382", + "Image": "2e9103a7b91a7ffe333e9162ce98ea078263747527571655e93bd4d35ee278f0", + "Labels": { + "architecture": "x86_64", + "authoritative-source-url": "registry.access.redhat.com", + "build-date": "2019-09-02T12:56:18.824770", + "com.redhat.build-host": "cpt-1005.osbs.prod.upshift.rdu2.redhat.com", + "com.redhat.component": "ubi7-container", + "com.redhat.license_terms": "https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI", + "description": "The Universal Base Image is designed and engineered to be the base layer for all of your containerized applications, middleware and utilities. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly.", + "distribution-scope": "public", + "io.k8s.description": "The Universal Base Image is designed and engineered to be the base layer for all of your containerized applications, middleware and utilities. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly.", + "io.k8s.display-name": "Red Hat Universal Base Image 7", + "io.openshift.tags": "base rhel7", + "maintainer": "Red Hat, Inc.", + "name": "ubi7", + "release": "140", + "summary": "Provides the latest release of the Red Hat Universal Base Image 7.", + "url": "https://access.redhat.com/containers/#/registry.access.redhat.com/ubi7/images/7.7-140", + "vcs-ref": "4c80c8aa26e69950ab11b87789c8fb7665b1632d", + "vcs-type": "git", + "vendor": "Red Hat, Inc.", + "version": "7.7" + }, + "ArgsEscaped": true + } + } + }, + "Results": [ + { + "Target": "testdata/fixtures/images/ubi-7.tar.gz (redhat 7.7)", + "Class": "os-pkgs", + "Type": "redhat", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2019-18276", + "PkgID": "bash@4.2.46-33.el7.x86_64", + "PkgName": "bash", + "PkgIdentifier": { + "PURL": "pkg:rpm/redhat/bash@4.2.46-33.el7?arch=x86_64\u0026distro=redhat-7.7", + "UID": "f5b786381193ad1b" + }, + "InstalledVersion": "4.2.46-33.el7", + "Status": "will_not_fix", + "Layer": { + "Digest": "sha256:7b1c937e0f6794db2535be6e4cb6d60a0b668ef78c2576611a3fb9c97a95ccdf", + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + }, + "SeveritySource": "redhat", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18276", + "Title": "bash: when effective UID is not equal to its real UID the saved UID is not dropped", + "Description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", + "Severity": "LOW", + "CweIDs": [ + "CWE-273" + ], + "VendorSeverity": { + "cbl-mariner": 3, + "nvd": 3, + "oracle-oval": 1, + "photon": 3, + "redhat": 1, + "ubuntu": 1 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 7.2, + "V3Score": 7.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + } + }, + "References": [ + "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html", + "https://access.redhat.com/security/cve/CVE-2019-18276", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276", + "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff", + "https://linux.oracle.com/cve/CVE-2019-18276.html", + "https://linux.oracle.com/errata/ELSA-2021-1679.html", + "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E", + "https://nvd.nist.gov/vuln/detail/CVE-2019-18276", + "https://security.gentoo.org/glsa/202105-34", + "https://security.netapp.com/advisory/ntap-20200430-0003/", + "https://www.youtube.com/watch?v=-wGtxJ8opa8" + ], + "PublishedDate": "2019-11-28T01:15:00Z", + "LastModifiedDate": "2021-05-26T12:15:00Z" + } + ] + }, + { + "Target": "Python", + "Class": "lang-pkgs", + "Type": "python-pkg", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2022-40897", + "PkgName": "setuptools", + "PkgPath": "usr/lib/python2.7/site-packages/setuptools-0.9.8-py2.7.egg-info/PKG-INFO", + "PkgIdentifier": { + "PURL": "pkg:pypi/setuptools@0.9.8", + "UID": "3f4c89bf681c1d7a" + }, + "InstalledVersion": "0.9.8", + "FixedVersion": "65.5.1", + "Status": "fixed", + "Layer": { + "Digest": "sha256:7b1c937e0f6794db2535be6e4cb6d60a0b668ef78c2576611a3fb9c97a95ccdf", + "DiffID": "sha256:4468e6d912c76d5b127f3554c3cd83b7dc07cce6107c6b916299ba76fa7d15ac" + }, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-40897", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Pip", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip" + }, + "Title": "pypa-setuptools: Regular Expression Denial of Service (ReDoS) in package_index.py", + "Description": "Python Packaging Authority (PyPA) setuptools before 65.5.1 allows remote attackers to cause a denial of service via HTML in a crafted package or custom PackageIndex page. There is a Regular Expression Denial of Service (ReDoS) in package_index.py.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-1333" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 2 + }, + "CVSS": { + "nvd": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 5.9 + } + }, + "References": [ + "https://access.redhat.com/errata/RHSA-2023:0952", + "https://access.redhat.com/security/cve/CVE-2022-40897" + ], + "PublishedDate": "2022-12-23T00:15:13.987Z", + "LastModifiedDate": "2024-06-21T19:15:23.877Z" + } + ] + } + ] +} diff --git a/pkg/cache/key.go b/pkg/cache/key.go index 7d6720393554..0ad0fde82fe1 100644 --- a/pkg/cache/key.go +++ b/pkg/cache/key.go @@ -13,6 +13,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/types" ) func CalcKey(id string, analyzerVersions analyzer.Versions, hookVersions map[string]int, artifactOpt artifact.Option) (string, error) { @@ -24,13 +25,22 @@ func CalcKey(id string, analyzerVersions analyzer.Versions, hookVersions map[str // Write ID, analyzer/handler versions, skipped files/dirs and file patterns keyBase := struct { - ID string - AnalyzerVersions analyzer.Versions - HookVersions map[string]int - SkipFiles []string - SkipDirs []string - FilePatterns []string `json:",omitempty"` - }{id, analyzerVersions, hookVersions, artifactOpt.WalkerOption.SkipFiles, artifactOpt.WalkerOption.SkipDirs, artifactOpt.FilePatterns} + ID string + AnalyzerVersions analyzer.Versions + HookVersions map[string]int + SkipFiles []string + SkipDirs []string + FilePatterns []string `json:",omitempty"` + DetectionPriority types.DetectionPriority `json:",omitempty"` + }{ + id, + analyzerVersions, + hookVersions, + artifactOpt.WalkerOption.SkipFiles, + artifactOpt.WalkerOption.SkipDirs, + artifactOpt.FilePatterns, + artifactOpt.DetectionPriority, + } if err := json.NewEncoder(h).Encode(keyBase); err != nil { return "", xerrors.Errorf("json encode error: %w", err) diff --git a/pkg/cache/key_test.go b/pkg/cache/key_test.go index 012e9edd407f..d80748567683 100644 --- a/pkg/cache/key_test.go +++ b/pkg/cache/key_test.go @@ -8,21 +8,23 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/artifact" + "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" "github.com/aquasecurity/trivy/pkg/misconf" ) func TestCalcKey(t *testing.T) { type args struct { - key string - analyzerVersions analyzer.Versions - hookVersions map[string]int - skipFiles []string - skipDirs []string - patterns []string - policy []string - data []string - secretConfigPath string + key string + analyzerVersions analyzer.Versions + hookVersions map[string]int + skipFiles []string + skipDirs []string + patterns []string + policy []string + data []string + secretConfigPath string + detectionPriority types.DetectionPriority } tests := []struct { name string @@ -115,7 +117,10 @@ func TestCalcKey(t *testing.T) { "debian": 1, }, }, - patterns: []string{"test", ""}, + patterns: []string{ + "test", + "", + }, }, want: "sha256:71abf09bf1422531e2838db692b80f9b9f48766f56b7d3d02aecdb36b019e103", }, @@ -129,7 +134,10 @@ func TestCalcKey(t *testing.T) { "debian": 1, }, }, - patterns: []string{"", "test"}, + patterns: []string{ + "", + "test", + }, }, want: "sha256:71abf09bf1422531e2838db692b80f9b9f48766f56b7d3d02aecdb36b019e103", }, @@ -177,6 +185,23 @@ func TestCalcKey(t *testing.T) { }, want: "sha256:363f70f4ee795f250873caea11c2fc94ef12945444327e7e2f8a99e3884695e0", }, + { + name: "detection priority", + args: args{ + key: "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e", + analyzerVersions: analyzer.Versions{ + Analyzers: map[string]int{ + "alpine": 1, + "debian": 1, + }, + }, + skipFiles: []string{"app/deployment.yaml"}, + skipDirs: []string{"usr/java"}, + policy: []string{"testdata/policy"}, + detectionPriority: types.PriorityComprehensive, + }, + want: "sha256:2f1c898271e84f4382cd48ae7533069cc3dc656c2d688ac108f5db1a0d9fd393", + }, { name: "secret config", @@ -231,7 +256,8 @@ func TestCalcKey(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { artifactOpt := artifact.Option{ - FilePatterns: tt.args.patterns, + FilePatterns: tt.args.patterns, + DetectionPriority: tt.args.detectionPriority, MisconfScannerOption: misconf.ScannerOption{ PolicyPaths: tt.args.policy, @@ -249,7 +275,6 @@ func TestCalcKey(t *testing.T) { } got, err := CalcKey(tt.args.key, tt.args.analyzerVersions, tt.args.hookVersions, artifactOpt) if tt.wantErr != "" { - require.Error(t, err) assert.ErrorContains(t, err, tt.wantErr) return } diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index e49829e352fb..6528ec2c3d36 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -568,6 +568,10 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan fileChecksum = true } + // Disable the post handler for filtering system file when detection priority is comprehensive. + disabledHandlers := lo.Ternary(opts.DetectionPriority == ftypes.PriorityComprehensive, + []ftypes.HandlerType{ftypes.SystemFileFilteringPostHandler}, nil) + return ScannerConfig{ Target: target, CacheOptions: opts.CacheOpts(), @@ -579,6 +583,7 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan }, ArtifactOption: artifact.Option{ DisabledAnalyzers: disabledAnalyzers(opts), + DisabledHandlers: disabledHandlers, FilePatterns: opts.FilePatterns, Parallel: opts.Parallel, Offline: opts.OfflineScan, @@ -592,6 +597,7 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan AWSRegion: opts.Region, AWSEndpoint: opts.Endpoint, FileChecksum: fileChecksum, + DetectionPriority: opts.DetectionPriority, // For image scanning ImageOption: ftypes.ImageOptions{ diff --git a/pkg/dependency/parser/dart/pub/parse.go b/pkg/dependency/parser/dart/pub/parse.go index 1bd85a56f1d6..f17fedf0a2b0 100644 --- a/pkg/dependency/parser/dart/pub/parse.go +++ b/pkg/dependency/parser/dart/pub/parse.go @@ -19,12 +19,14 @@ const ( // Parser is a parser for pubspec.lock type Parser struct { - logger *log.Logger + logger *log.Logger + useMinVersion bool } -func NewParser() *Parser { +func NewParser(useMinVersion bool) *Parser { return &Parser{ - logger: log.WithPrefix("pub"), + logger: log.WithPrefix("pub"), + useMinVersion: useMinVersion, } } @@ -50,7 +52,7 @@ func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency var pkgs []ftypes.Package for name, dep := range l.Packages { version := dep.Version - if version == "0.0.0" && dep.Source == "sdk" { + if version == "0.0.0" && dep.Source == "sdk" && p.useMinVersion { version = p.findSDKVersion(l, name, dep) } @@ -71,27 +73,27 @@ func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency return pkgs, nil, nil } -// findSDKVersion detects the first version of the SDK constraint specified in the Description. +// findSDKVersion detects the minimum version of the SDK constraint specified in the Description. // If the constraint is not found, it returns the original version. func (p Parser) findSDKVersion(l *lock, name string, dep Dep) string { // Some dependencies use one of the SDK versions. // In this case dep.Version == `0.0.0`. // We can't get versions for these dependencies. - // Therefore, we use the first version of the SDK constraint specified in the Description. + // Therefore, we use the minimum version of the SDK constraint specified in the Description. // See https://github.com/aquasecurity/trivy/issues/6017 constraint, ok := l.Sdks[string(dep.Description)] if !ok { return dep.Version } - v, err := firstVersionOfConstrain(constraint) + v, err := minVersionOfConstrain(constraint) if err != nil { p.logger.Warn("Unable to get sdk version from constraint", log.Err(err)) return dep.Version } else if v == "" { return dep.Version } - p.logger.Info("Using the first version of the constraint from the sdk source", log.String("dep", name), + p.logger.Info("Using the minimum version of the constraint from the sdk source", log.String("dep", name), log.String("constraint", constraint)) return v } @@ -106,8 +108,8 @@ func (p Parser) relationship(dep string) ftypes.Relationship { return ftypes.RelationshipUnknown } -// firstVersionOfConstrain returns the first acceptable version for constraint -func firstVersionOfConstrain(constraint string) (string, error) { +// minVersionOfConstrain returns the minimum acceptable version for constraint +func minVersionOfConstrain(constraint string) (string, error) { css, err := goversion.NewConstraints(constraint) if err != nil { return "", xerrors.Errorf("unable to parse constraints: %w", err) @@ -119,7 +121,7 @@ func firstVersionOfConstrain(constraint string) (string, error) { if len(constraints) == 0 || len(constraints[0]) == 0 { return "", nil } - // We only need to get the first version from the range + // We only need to get the minimum version from the range if constraints[0][0].Operator() != ">=" && constraints[0][0].Operator() != "^" { return "", nil } diff --git a/pkg/dependency/parser/dart/pub/parse_test.go b/pkg/dependency/parser/dart/pub/parse_test.go index 5f4591201b7c..e884adf28306 100644 --- a/pkg/dependency/parser/dart/pub/parse_test.go +++ b/pkg/dependency/parser/dart/pub/parse_test.go @@ -15,14 +15,42 @@ import ( func TestParser_Parse(t *testing.T) { tests := []struct { - name string - inputFile string - want []ftypes.Package - wantErr assert.ErrorAssertionFunc + name string + useMinVersion bool + inputFile string + want []ftypes.Package + wantErr assert.ErrorAssertionFunc }{ { - name: "happy path", - inputFile: "testdata/happy.lock", + name: "not use minimum version", + useMinVersion: false, + inputFile: "testdata/happy.lock", + want: []ftypes.Package{ + { + ID: "crypto@3.0.2", + Name: "crypto", + Version: "3.0.2", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "flutter_test@0.0.0", + Name: "flutter_test", + Version: "0.0.0", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "uuid@3.0.6", + Name: "uuid", + Version: "3.0.6", + Relationship: ftypes.RelationshipIndirect, + }, + }, + wantErr: assert.NoError, + }, + { + name: "use minimum version", + useMinVersion: true, + inputFile: "testdata/happy.lock", want: []ftypes.Package{ { ID: "crypto@3.0.2", @@ -63,7 +91,7 @@ func TestParser_Parse(t *testing.T) { require.NoError(t, err) defer f.Close() - gotPkgs, _, err := pub.NewParser().Parse(f) + gotPkgs, _, err := pub.NewParser(tt.useMinVersion).Parse(f) if !tt.wantErr(t, err, fmt.Sprintf("Parse(%v)", tt.inputFile)) { return } diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go index d2d07ffcc733..b5bb8e629acf 100644 --- a/pkg/fanal/analyzer/analyzer.go +++ b/pkg/fanal/analyzer/analyzer.go @@ -44,6 +44,7 @@ type AnalyzerOptions struct { Parallel int FilePatterns []string DisabledAnalyzers []Type + DetectionPriority types.DetectionPriority MisconfScannerOption misconf.ScannerOption SecretScannerOption SecretScannerOption LicenseScannerOption LicenseScannerOption @@ -120,10 +121,11 @@ type CustomGroup interface { type Opener func() (xio.ReadSeekCloserAt, error) type AnalyzerGroup struct { - logger *log.Logger - analyzers []analyzer - postAnalyzers []PostAnalyzer - filePatterns map[Type][]*regexp.Regexp + logger *log.Logger + analyzers []analyzer + postAnalyzers []PostAnalyzer + filePatterns map[Type][]*regexp.Regexp + detectionPriority types.DetectionPriority } /////////////////////////// @@ -312,17 +314,18 @@ func belongToGroup(groupName Group, analyzerType Type, disabledAnalyzers []Type, const separator = ":" -func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) { - groupName := opt.Group +func NewAnalyzerGroup(opts AnalyzerOptions) (AnalyzerGroup, error) { + groupName := opts.Group if groupName == "" { groupName = GroupBuiltin } group := AnalyzerGroup{ - logger: log.WithPrefix("analyzer"), - filePatterns: make(map[Type][]*regexp.Regexp), + logger: log.WithPrefix("analyzer"), + filePatterns: make(map[Type][]*regexp.Regexp), + detectionPriority: opts.DetectionPriority, } - for _, p := range opt.FilePatterns { + for _, p := range opts.FilePatterns { // e.g. "dockerfile:my_dockerfile_*" s := strings.SplitN(p, separator, 2) if len(s) != 2 { @@ -343,12 +346,12 @@ func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) { } for analyzerType, a := range analyzers { - if !belongToGroup(groupName, analyzerType, opt.DisabledAnalyzers, a) { + if !belongToGroup(groupName, analyzerType, opts.DisabledAnalyzers, a) { continue } // Initialize only scanners that have Init() if ini, ok := a.(Initializer); ok { - if err := ini.Init(opt); err != nil { + if err := ini.Init(opts); err != nil { return AnalyzerGroup{}, xerrors.Errorf("analyzer initialization error: %w", err) } } @@ -356,11 +359,11 @@ func NewAnalyzerGroup(opt AnalyzerOptions) (AnalyzerGroup, error) { } for analyzerType, init := range postAnalyzers { - a, err := init(opt) + a, err := init(opts) if err != nil { return AnalyzerGroup{}, xerrors.Errorf("post-analyzer init error: %w", err) } - if !belongToGroup(groupName, analyzerType, opt.DisabledAnalyzers, a) { + if !belongToGroup(groupName, analyzerType, opts.DisabledAnalyzers, a) { continue } group.postAnalyzers = append(group.postAnalyzers, a) @@ -473,6 +476,11 @@ func (ag AnalyzerGroup) PostAnalyze(ctx context.Context, compositeFS *CompositeF } skippedFiles := result.SystemInstalledFiles + if ag.detectionPriority == types.PriorityComprehensive { + // If the detection priority is comprehensive, system files installed by the OS package manager will not be skipped. + // It can lead to false positives and duplicates, but it may be necessary to detect all possible vulnerabilities. + skippedFiles = nil + } for _, app := range result.Applications { skippedFiles = append(skippedFiles, app.FilePath) for _, pkg := range app.Packages { diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec.go b/pkg/fanal/analyzer/language/dart/pub/pubspec.go index 9def336d406f..30fc2dadb18b 100644 --- a/pkg/fanal/analyzer/language/dart/pub/pubspec.go +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec.go @@ -37,10 +37,10 @@ type pubSpecLockAnalyzer struct { parser language.Parser } -func newPubSpecLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { +func newPubSpecLockAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { return pubSpecLockAnalyzer{ logger: log.WithPrefix("pub"), - parser: pub.NewParser(), + parser: pub.NewParser(opts.DetectionPriority == types.PriorityComprehensive), }, nil } diff --git a/pkg/fanal/artifact/artifact.go b/pkg/fanal/artifact/artifact.go index e431278f6fc2..b6034cb5ac63 100644 --- a/pkg/fanal/artifact/artifact.go +++ b/pkg/fanal/artifact/artifact.go @@ -28,6 +28,7 @@ type Option struct { AWSRegion string AWSEndpoint string FileChecksum bool // For SPDX + DetectionPriority types.DetectionPriority // Git repositories RepoBranch string @@ -50,6 +51,7 @@ func (o *Option) AnalyzerOptions() analyzer.AnalyzerOptions { FilePatterns: o.FilePatterns, Parallel: o.Parallel, DisabledAnalyzers: o.DisabledAnalyzers, + DetectionPriority: o.DetectionPriority, MisconfScannerOption: o.MisconfScannerOption, SecretScannerOption: o.SecretScannerOption, LicenseScannerOption: o.LicenseScannerOption, diff --git a/pkg/fanal/types/detection.go b/pkg/fanal/types/detection.go new file mode 100644 index 000000000000..38d1634ed2a3 --- /dev/null +++ b/pkg/fanal/types/detection.go @@ -0,0 +1,10 @@ +package types + +// DetectionPriority represents the priority of detection +type DetectionPriority string + +// PriorityPrecise tries to minimize false positives +const PriorityPrecise DetectionPriority = "precise" + +// PriorityComprehensive tries to minimize false negatives +const PriorityComprehensive DetectionPriority = "comprehensive" diff --git a/pkg/flag/scan_flags.go b/pkg/flag/scan_flags.go index b413fce01fe9..544d56691883 100644 --- a/pkg/flag/scan_flags.go +++ b/pkg/flag/scan_flags.go @@ -5,6 +5,7 @@ import ( "github.com/samber/lo" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" xstrings "github.com/aquasecurity/trivy/pkg/x/strings" @@ -96,43 +97,59 @@ var ( Default: "https://rekor.sigstore.dev", Usage: "[EXPERIMENTAL] address of rekor STL server", } + DetectionPriority = Flag[string]{ + Name: "detection-priority", + ConfigName: "scan.detection-priority", + Default: string(ftypes.PriorityPrecise), + Values: xstrings.ToStringSlice([]ftypes.DetectionPriority{ + ftypes.PriorityPrecise, + ftypes.PriorityComprehensive, + }), + Usage: `specify the detection priority: + - "precise": Prioritizes precise by minimizing false positives. + - "comprehensive": Aims to detect more security findings at the cost of potential false positives. +`, + } ) type ScanFlagGroup struct { - SkipDirs *Flag[[]string] - SkipFiles *Flag[[]string] - OfflineScan *Flag[bool] - Scanners *Flag[[]string] - FilePatterns *Flag[[]string] - Slow *Flag[bool] // deprecated - Parallel *Flag[int] - SBOMSources *Flag[[]string] - RekorURL *Flag[string] + SkipDirs *Flag[[]string] + SkipFiles *Flag[[]string] + OfflineScan *Flag[bool] + Scanners *Flag[[]string] + FilePatterns *Flag[[]string] + Slow *Flag[bool] // deprecated + Parallel *Flag[int] + SBOMSources *Flag[[]string] + RekorURL *Flag[string] + DetectionPriority *Flag[string] } type ScanOptions struct { - Target string - SkipDirs []string - SkipFiles []string - OfflineScan bool - Scanners types.Scanners - FilePatterns []string - Parallel int - SBOMSources []string - RekorURL string + Target string + SkipDirs []string + SkipFiles []string + OfflineScan bool + Scanners types.Scanners + FilePatterns []string + Parallel int + SBOMSources []string + RekorURL string + DetectionPriority ftypes.DetectionPriority } func NewScanFlagGroup() *ScanFlagGroup { return &ScanFlagGroup{ - SkipDirs: SkipDirsFlag.Clone(), - SkipFiles: SkipFilesFlag.Clone(), - OfflineScan: OfflineScanFlag.Clone(), - Scanners: ScannersFlag.Clone(), - FilePatterns: FilePatternsFlag.Clone(), - Parallel: ParallelFlag.Clone(), - SBOMSources: SBOMSourcesFlag.Clone(), - RekorURL: RekorURLFlag.Clone(), - Slow: SlowFlag.Clone(), + SkipDirs: SkipDirsFlag.Clone(), + SkipFiles: SkipFilesFlag.Clone(), + OfflineScan: OfflineScanFlag.Clone(), + Scanners: ScannersFlag.Clone(), + FilePatterns: FilePatternsFlag.Clone(), + Parallel: ParallelFlag.Clone(), + SBOMSources: SBOMSourcesFlag.Clone(), + RekorURL: RekorURLFlag.Clone(), + Slow: SlowFlag.Clone(), + DetectionPriority: DetectionPriority.Clone(), } } @@ -151,6 +168,7 @@ func (f *ScanFlagGroup) Flags() []Flagger { f.Parallel, f.SBOMSources, f.RekorURL, + f.DetectionPriority, } } @@ -171,14 +189,15 @@ func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { } return ScanOptions{ - Target: target, - SkipDirs: f.SkipDirs.Value(), - SkipFiles: f.SkipFiles.Value(), - OfflineScan: f.OfflineScan.Value(), - Scanners: xstrings.ToTSlice[types.Scanner](f.Scanners.Value()), - FilePatterns: f.FilePatterns.Value(), - Parallel: parallel, - SBOMSources: f.SBOMSources.Value(), - RekorURL: f.RekorURL.Value(), + Target: target, + SkipDirs: f.SkipDirs.Value(), + SkipFiles: f.SkipFiles.Value(), + OfflineScan: f.OfflineScan.Value(), + Scanners: xstrings.ToTSlice[types.Scanner](f.Scanners.Value()), + FilePatterns: f.FilePatterns.Value(), + Parallel: parallel, + SBOMSources: f.SBOMSources.Value(), + RekorURL: f.RekorURL.Value(), + DetectionPriority: ftypes.DetectionPriority(f.DetectionPriority.Value()), }, nil } diff --git a/pkg/types/scan.go b/pkg/types/scan.go index 09b90e745c58..0fef0028bef3 100644 --- a/pkg/types/scan.go +++ b/pkg/types/scan.go @@ -1,9 +1,100 @@ package types import ( + "slices" + "github.com/aquasecurity/trivy/pkg/fanal/types" ) +// PkgType represents package type +type PkgType = string + +// Scanner represents the type of security scanning +type Scanner string + +// Scanners is a slice of scanners +type Scanners []Scanner + +const ( + // PkgTypeUnknown is a package type of unknown + PkgTypeUnknown PkgType = "unknown" + + // PkgTypeOS is a package type of OS packages + PkgTypeOS PkgType = "os" + + // PkgTypeLibrary is a package type of programming language dependencies + PkgTypeLibrary PkgType = "library" + + // UnknownScanner is the scanner of unknown + UnknownScanner Scanner = "unknown" + + // NoneScanner is the scanner of none + NoneScanner Scanner = "none" + + // SBOMScanner is the virtual scanner of SBOM, which cannot be enabled by the user + SBOMScanner Scanner = "sbom" + + // VulnerabilityScanner is the scanner of vulnerabilities + VulnerabilityScanner Scanner = "vuln" + + // MisconfigScanner is the scanner of misconfigurations + MisconfigScanner Scanner = "misconfig" + + // SecretScanner is the scanner of secrets + SecretScanner Scanner = "secret" + + // RBACScanner is the scanner of rbac assessment + RBACScanner Scanner = "rbac" + + // LicenseScanner is the scanner of licenses + LicenseScanner Scanner = "license" +) + +var ( + PkgTypes = []string{ + PkgTypeOS, + PkgTypeLibrary, + } + + AllScanners = Scanners{ + VulnerabilityScanner, + MisconfigScanner, + RBACScanner, + SecretScanner, + LicenseScanner, + NoneScanner, + } + + // AllImageConfigScanners has a list of available scanners on container image config. + // The container image in container registries consists of manifest, config and layers. + // Trivy is also able to detect security issues on the image config. + AllImageConfigScanners = Scanners{ + MisconfigScanner, + SecretScanner, + NoneScanner, + } +) + +func (scanners *Scanners) Enable(s Scanner) { + if !scanners.Enabled(s) { + *scanners = append(*scanners, s) + } +} + +func (scanners *Scanners) Enabled(s Scanner) bool { + return slices.Contains(*scanners, s) +} + +// AnyEnabled returns true if any of the passed scanners is included. +func (scanners *Scanners) AnyEnabled(ss ...Scanner) bool { + for _, s := range ss { + if scanners.Enabled(s) { + return true + } + } + return false +} + // ScanTarget holds the attributes for scanning. type ScanTarget struct { Name string // container image name, file path, etc diff --git a/pkg/types/target.go b/pkg/types/target.go deleted file mode 100644 index a8cccced3de7..000000000000 --- a/pkg/types/target.go +++ /dev/null @@ -1,94 +0,0 @@ -package types - -import ( - "slices" -) - -// PkgType represents package type -type PkgType = string - -// Scanner represents the type of security scanning -type Scanner string - -// Scanners is a slice of scanners -type Scanners []Scanner - -const ( - // PkgTypeUnknown is a package type of unknown - PkgTypeUnknown = PkgType("unknown") - - // PkgTypeOS is a package type of OS packages - PkgTypeOS = PkgType("os") - - // PkgTypeLibrary is a package type of programming language dependencies - PkgTypeLibrary = PkgType("library") - - // UnknownScanner is the scanner of unknown - UnknownScanner = Scanner("unknown") - - // NoneScanner is the scanner of none - NoneScanner = Scanner("none") - - // SBOMScanner is the virtual scanner of SBOM, which cannot be enabled by the user - SBOMScanner = Scanner("sbom") - - // VulnerabilityScanner is the scanner of vulnerabilities - VulnerabilityScanner = Scanner("vuln") - - // MisconfigScanner is the scanner of misconfigurations - MisconfigScanner = Scanner("misconfig") - - // SecretScanner is the scanner of secrets - SecretScanner = Scanner("secret") - - // RBACScanner is the scanner of rbac assessment - RBACScanner = Scanner("rbac") - - // LicenseScanner is the scanner of licenses - LicenseScanner = Scanner("license") -) - -var ( - PkgTypes = []string{ - PkgTypeOS, - PkgTypeLibrary, - } - - AllScanners = Scanners{ - VulnerabilityScanner, - MisconfigScanner, - RBACScanner, - SecretScanner, - LicenseScanner, - NoneScanner, - } - - // AllImageConfigScanners has a list of available scanners on container image config. - // The container image in container registries consists of manifest, config and layers. - // Trivy is also able to detect security issues on the image config. - AllImageConfigScanners = Scanners{ - MisconfigScanner, - SecretScanner, - NoneScanner, - } -) - -func (scanners *Scanners) Enable(s Scanner) { - if !scanners.Enabled(s) { - *scanners = append(*scanners, s) - } -} - -func (scanners *Scanners) Enabled(s Scanner) bool { - return slices.Contains(*scanners, s) -} - -// AnyEnabled returns true if any of the passed scanners is included. -func (scanners *Scanners) AnyEnabled(ss ...Scanner) bool { - for _, s := range ss { - if scanners.Enabled(s) { - return true - } - } - return false -} From 555ac8c11dc282fa55627083d9f6ca0171d1a231 Mon Sep 17 00:00:00 2001 From: afdesk Date: Mon, 5 Aug 2024 13:52:40 +0600 Subject: [PATCH 273/352] docs: add auto-generated config (#7261) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- .../references/configuration/config-file.md | 928 +++++++++--------- magefiles/docs.go | 125 +++ pkg/flag/global_flags.go | 2 +- pkg/flag/options.go | 10 + 4 files changed, 595 insertions(+), 470 deletions(-) diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index 535a1b9e657e..b3876d6ad225 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -5,615 +5,605 @@ The config path can be overridden by the `--config` flag. An example is [here][example]. -## Global Options +These samples contain default values for flags. +## Global options ```yaml -# Same as '--quiet' -# Default is false -quiet: false +cache: + # Same as '--cache-dir' + dir: "/path/to/cache" # Same as '--debug' -# Default is false debug: false # Same as '--insecure' -# Default is false insecure: false +# Same as '--quiet' +quiet: false + # Same as '--timeout' -# Default is '5m' -timeout: 10m +timeout: 5m0s -# Same as '--cache-dir' -# Default is your system cache dir -cache: - dir: $HOME/.cache/trivy ``` - -## Report Options +## Cache options ```yaml -# Same as '--format' -# Default is 'table' -format: table +cache: + # Same as '--cache-backend' + backend: "fs" -# Same as '--report' (available with 'trivy k8s') -# Default is all -report: all + # Same as '--clear-cache' + clear: false -# Same as '--template' -# Default is empty -template: - -# Same as '--dependency-tree' -# Default is false -dependency-tree: false + redis: + # Same as '--redis-ca' + ca: "" -# Same as '--list-all-pkgs' -# Default is false -list-all-pkgs: false + # Same as '--redis-cert' + cert: "" -# Same as '--ignorefile' -# Default is '.trivyignore' -ignorefile: .trivyignore + # Same as '--redis-key' + key: "" -# Same as '--ignore-policy' -# Default is empty -ignore-policy: + # Same as '--redis-tls' + tls: false -# Same as '--exit-code' -# Default is 0 -exit-code: 0 + # Same as '--cache-ttl' + ttl: 0s -# Same as '--exit-on-eol' -# Default is 0 -exit-on-eol: 0 +``` +## Clean options -# Same as '--output' -# Default is empty (stdout) -output: +```yaml +clean: + # Same as '--all' + all: false -# Same as '--severity' -# Default is all severities -severity: - - UNKNOWN - - LOW - - MEDIUM - - HIGH - - CRITICAL - -# Same as '--pkg-types' -# Default is 'os,library' -pkg-types: - - os - - library - + # Same as '--checks-bundle' + checks-bundle: false -scan: - # Same as '--compliance' - # Default is empty - compliance: + # Same as '--java-db' + java-db: false - # Same as '--show-suppressed' - # Default is false - show-suppressed: false -``` + # Same as '--scan-cache' + scan-cache: false -## Scan Options -Available in client/server mode + # Same as '--vex-repo' + vex-repo: false -```yaml -scan: - # Same as '--file-patterns' - # Default is empty - file-patterns: - - + # Same as '--vuln-db' + vuln-db: false - # Same as '--skip-dirs' - # Default is empty - skip-dirs: - - usr/local/ - - etc/ +``` +## Client/Server options - # Same as '--skip-files' - # Default is empty - skip-files: - - package-dev.json +```yaml +server: + # Same as '--server' + addr: "" - # Same as '--offline-scan' - # Default is false - offline: false + # Same as '--custom-headers' + custom-headers: [] - # Same as '--scanners' - # Default depends on subcommand - scanners: - - vuln - - misconfig - - secret - - license - - - # Same as '--parallel' - # Default is 5 - parallel: 1 + # Same as '--listen' + listen: "localhost:4954" - # Same as '--sbom-sources' - # Default is empty - sbom-sources: - - oci - - rekor + # Same as '--token' + token: "" - # Same as '--rekor-url' - # Default is 'https://rekor.sigstore.dev' - rekor-url: https://rekor.sigstore.dev + # Same as '--token-header' + token-header: "Trivy-Token" - # Same as '--include-dev-deps' - # Default is false - include-dev-deps: false ``` - -## Cache Options +## DB options ```yaml -cache: - # Same as '--cache-backend' - # Default is 'fs' - backend: 'fs' - - # Same as '--cache-ttl' - # Default is 0 (no ttl) - ttl: 0 +db: + # Same as '--download-java-db-only' + download-java-only: false - # Redis options - redis: - # Same as '--redis-tls' - # Default is false - tls: - # Same as '--redis-ca' - # Default is empty - ca: + # Same as '--download-db-only' + download-only: false - # Same as '--redis-cert' - # Default is empty - cert: + # Same as '--java-db-repository' + java-repository: "ghcr.io/aquasecurity/trivy-java-db:1" - # Same as '--redis-key' - # Default is empty - key: -``` + # Same as '--skip-java-db-update' + java-skip-update: false -## DB Options + # Same as '--light' + light: false -```yaml -db: # Same as '--no-progress' - # Default is false no-progress: false - - # Same as '--skip-db-update' - # Default is false - skip-update: false # Same as '--db-repository' - # Default is 'ghcr.io/aquasecurity/trivy-db:2' - repository: ghcr.io/aquasecurity/trivy-db:2 + repository: "ghcr.io/aquasecurity/trivy-db:2" - # Same as '--skip-java-db-update' - # Default is false - java-skip-update: false + # Same as '--skip-db-update' + skip-update: false - # Same as '--java-db-repository' - # Default is 'ghcr.io/aquasecurity/trivy-java-db:1' - java-repository: ghcr.io/aquasecurity/trivy-java-db:1 -``` +# Same as '--reset' +reset: false -## Registry Options +``` +## Image options ```yaml -registry: - # Same as '--username' - # Default is empty - username: +image: + docker: + # Same as '--docker-host' + host: "" - # Same as '--password' - # Default is empty - password: - - # Same as '--registry-token' - # Default is empty - registry-token: -``` + # Same as '--image-config-scanners' + image-config-scanners: [] -## Image Options -Available with container image scanning + # Same as '--input' + input: "" -```yaml -image: - # Same as '--input' (available with 'trivy image') - # Default is empty - input: + # Same as '--platform' + platform: "" + + podman: + # Same as '--podman-host' + host: "" # Same as '--removed-pkgs' - # Default is false removed-pkgs: false - - # Same as '--platform' - # Default is empty - platform: # Same as '--image-src' - # Default is 'docker,containerd,podman,remote' source: - - podman - - docker - - # Same as '--image-config-scanners' - # Default is empty - image-config-scanners: - - misconfig - - secret - - docker: - # Same as '--docker-host' - # Default is empty - host: - - podman: - # Same as '--podman-host' - # Default is empty - host: -``` + - docker + - containerd + - podman + - remote -## Vulnerability Options -Available with vulnerability scanning +``` +## Kubernetes options ```yaml -vulnerability: - # Same as '--ignore-unfixed' - # Default is false - ignore-unfixed: false +kubernetes: + # Same as '--burst' + burst: 10 - # Same as '--ignore-unfixed' - # Default is empty - ignore-status: - - end_of_life + # Same as '--disable-node-collector' + disableNodeCollector: false - # Same as '--vex' - # Default is empty - vex: - - path/to/vex/file - - repo + exclude: + # Same as '--exclude-nodes' + nodes: [] - # Same as '--skip-vex-repo-update' - # Default is false - skip-vex-repo-update: true -``` + # Same as '--exclude-owned' + owned: false -## License Options -Available with license scanning + # Same as '--exclude-kinds' + excludeKinds: [] -```yaml -license: - # Same as '--license-full' - # Default is false - full: false + # Same as '--exclude-namespaces' + excludeNamespaces: [] - # Same as '--ignored-licenses' - # Default is empty - ignored: - - MPL-2.0 - - MIT + # Same as '--include-kinds' + includeKinds: [] + # Same as '--include-namespaces' + includeNamespaces: [] + + # Same as '--k8s-version' + k8s-version: "" + + # Same as '--kubeconfig' + kubeconfig: "" + + node-collector: + # Same as '--node-collector-imageref' + imageref: "ghcr.io/aquasecurity/node-collector:0.3.1" + + # Same as '--node-collector-namespace' + namespace: "trivy-temp" + + # Same as '--qps' + qps: 5 + + # Same as '--skip-images' + skipImages: false + + # Same as '--tolerations' + tolerations: [] + +``` +## License options + +```yaml +license: # Same as '--license-confidence-level' - # Default is 0.9 confidenceLevel: 0.9 - # Set list of forbidden licenses - # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L171 forbidden: - - AGPL-1.0 - - AGPL-3.0 + - AGPL-1.0 + - AGPL-3.0 + - CC-BY-NC-1.0 + - CC-BY-NC-2.0 + - CC-BY-NC-2.5 + - CC-BY-NC-3.0 + - CC-BY-NC-4.0 + - CC-BY-NC-ND-1.0 + - CC-BY-NC-ND-2.0 + - CC-BY-NC-ND-2.5 + - CC-BY-NC-ND-3.0 + - CC-BY-NC-ND-4.0 + - CC-BY-NC-SA-1.0 + - CC-BY-NC-SA-2.0 + - CC-BY-NC-SA-2.5 + - CC-BY-NC-SA-3.0 + - CC-BY-NC-SA-4.0 + - Commons-Clause + - Facebook-2-Clause + - Facebook-3-Clause + - Facebook-Examples + - WTFPL - # Set list of restricted licenses - # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L199 - restricted: - - AGPL-1.0 - - AGPL-3.0 + # Same as '--license-full' + full: false - # Set list of reciprocal licenses - # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L238 - reciprocal: - - AGPL-1.0 - - AGPL-3.0 + # Same as '--ignored-licenses' + ignored: [] - # Set list of notice licenses - # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L260 notice: - - AGPL-1.0 - - AGPL-3.0 + - AFL-1.1 + - AFL-1.2 + - AFL-2.0 + - AFL-2.1 + - AFL-3.0 + - Apache-1.0 + - Apache-1.1 + - Apache-2.0 + - Artistic-1.0-cl8 + - Artistic-1.0-Perl + - Artistic-1.0 + - Artistic-2.0 + - BSL-1.0 + - BSD-2-Clause-FreeBSD + - BSD-2-Clause-NetBSD + - BSD-2-Clause + - BSD-3-Clause-Attribution + - BSD-3-Clause-Clear + - BSD-3-Clause-LBNL + - BSD-3-Clause + - BSD-4-Clause + - BSD-4-Clause-UC + - BSD-Protection + - CC-BY-1.0 + - CC-BY-2.0 + - CC-BY-2.5 + - CC-BY-3.0 + - CC-BY-4.0 + - FTL + - ISC + - ImageMagick + - Libpng + - Lil-1.0 + - Linux-OpenIB + - LPL-1.02 + - LPL-1.0 + - MS-PL + - MIT + - NCSA + - OpenSSL + - PHP-3.01 + - PHP-3.0 + - PIL + - Python-2.0 + - Python-2.0-complete + - PostgreSQL + - SGI-B-1.0 + - SGI-B-1.1 + - SGI-B-2.0 + - Unicode-DFS-2015 + - Unicode-DFS-2016 + - Unicode-TOU + - UPL-1.0 + - W3C-19980720 + - W3C-20150513 + - W3C + - X11 + - Xnet + - Zend-2.0 + - zlib-acknowledgement + - Zlib + - ZPL-1.1 + - ZPL-2.0 + - ZPL-2.1 + + permissive: [] - # Set list of permissive licenses - # Default is empty - permissive: - - AGPL-1.0 - - AGPL-3.0 + reciprocal: + - APSL-1.0 + - APSL-1.1 + - APSL-1.2 + - APSL-2.0 + - CDDL-1.0 + - CDDL-1.1 + - CPL-1.0 + - EPL-1.0 + - EPL-2.0 + - FreeImage + - IPL-1.0 + - MPL-1.0 + - MPL-1.1 + - MPL-2.0 + - Ruby + + restricted: + - BCL + - CC-BY-ND-1.0 + - CC-BY-ND-2.0 + - CC-BY-ND-2.5 + - CC-BY-ND-3.0 + - CC-BY-ND-4.0 + - CC-BY-SA-1.0 + - CC-BY-SA-2.0 + - CC-BY-SA-2.5 + - CC-BY-SA-3.0 + - CC-BY-SA-4.0 + - GPL-1.0 + - GPL-2.0 + - GPL-2.0-with-autoconf-exception + - GPL-2.0-with-bison-exception + - GPL-2.0-with-classpath-exception + - GPL-2.0-with-font-exception + - GPL-2.0-with-GCC-exception + - GPL-3.0 + - GPL-3.0-with-autoconf-exception + - GPL-3.0-with-GCC-exception + - LGPL-2.0 + - LGPL-2.1 + - LGPL-3.0 + - NPL-1.0 + - NPL-1.1 + - OSL-1.0 + - OSL-1.1 + - OSL-2.0 + - OSL-2.1 + - OSL-3.0 + - QPL-1.0 + - Sleepycat - # Set list of unencumbered licenses - # Default is https://github.com/aquasecurity/trivy/blob/164b025413c5fb9c6759491e9a306b46b869be93/pkg/licensing/category.go#L334 unencumbered: - - AGPL-1.0 - - AGPL-3.0 -``` + - CC0-1.0 + - Unlicense + - 0BSD -## Secret Options -Available with secret scanning +``` +## Misconfiguration options ```yaml -secret: - # Same as '--secret-config' - # Default is 'trivy-secret.yaml' - config: config/trivy/secret.yaml -``` +misconfiguration: + # Same as '--checks-bundle-repository' + checks-bundle-repository: "ghcr.io/aquasecurity/trivy-checks:0" -## Rego Options + cloudformation: + # Same as '--cf-params' + params: [] -```yaml -rego: - # Same as '--trace' - # Default is false - trace: false + helm: + # Same as '--helm-api-versions' + api-versions: [] - # Same as '--skip-check-update' - # Default is false - skip-check-update: false + # Same as '--helm-kube-version' + kube-version: "" - # Same as '--config-policy' - # Default is empty - policy: - - policy/repository - - policy/custom - - policy/some-policy.rego + # Same as '--helm-set' + set: [] - # Same as '--config-data' - # Default is empty - data: - - data/ - - # Same as '--policy-namespaces' - # Default is empty - namespaces: - - opa.examples - - users -``` + # Same as '--helm-set-file' + set-file: [] -## Misconfiguration Options -Available with misconfiguration scanning + # Same as '--helm-set-string' + set-string: [] + + # Same as '--helm-values' + values: [] -```yaml -misconfiguration: # Same as '--include-non-failures' - # Default is false include-non-failures: false - - # Same as '--include-deprecated-checks' - # Default is false - include-deprecated-checks: false - # Same as '--check-bundle-repository' and '--policy-bundle-repository' - # Default is 'ghcr.io/aquasecurity/trivy-checks:0' - check-bundle-repository: ghcr.io/aquasecurity/trivy-checks:0 - - # Same as '--miconfig-scanners' - # Default is all scanners + # Same as '--reset-checks-bundle' + reset-checks-bundle: false + + # Same as '--misconfig-scanners' scanners: - - dockerfile - - terraform + - azure-arm + - cloudformation + - dockerfile + - helm + - kubernetes + - terraform + - terraformplan-json + - terraformplan-snapshot - # helm value override configurations - helm: - # set individual values - set: - - securityContext.runAsUser=10001 + terraform: + # Same as '--tf-exclude-downloaded-modules' + exclude-downloaded-modules: false - # set values with file - values: - - overrides.yaml + # Same as '--tf-vars' + vars: [] - # set specific values from specific files - set-file: - - image=dev-overrides.yaml +``` +## Module options - # set as string and preserve type - set-string: - - name=true +```yaml +module: + # Same as '--module-dir' + dir: "$HOME/.trivy/modules" - # Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. - api-versions: - - policy/v1/PodDisruptionBudget - - apps/v1/Deployment + # Same as '--enable-modules' + enable-modules: [] - # Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command. - kube-version: "v1.21.0" +``` +## Registry options - # terraform tfvars overrrides - terraform: - vars: - - dev-terraform.tfvars - - common-terraform.tfvars - - # Same as '--tf-exclude-downloaded-modules' - # Default is false - exclude-downloaded-modules: false +```yaml +registry: + # Same as '--password' + password: [] + + # Same as '--registry-token' + token: "" + + # Same as '--username' + username: [] - # Same as '--cf-params' - # Default is false - cloudformation: - params: - - params.json ``` +## Rego options -## Kubernetes Options -Available with Kubernetes scanning +```yaml +rego: + # Same as '--config-check' + check: [] + + # Same as '--config-data' + data: [] + + # Same as '--include-deprecated-checks' + include-deprecated-checks: false + + # Same as '--check-namespaces' + namespaces: [] + + # Same as '--skip-check-update' + skip-check-update: false + + # Same as '--trace' + trace: false + +``` +## Report options ```yaml -kubernetes: - # Same as '--context' - # Default is empty - context: +# Same as '--dependency-tree' +dependency-tree: false + +# Same as '--exit-code' +exit-code: 0 - # Same as '--namespace' - # Default is empty - namespace: +# Same as '--exit-on-eol' +exit-on-eol: 0 - # Same as '--kubeconfig' - # Default is empty - kubeconfig: ~/.kube/config2 +# Same as '--format' +format: "table" - # Same as '--components' - # Default is 'workload,infra' - components: - - workload - - infra +# Same as '--ignore-policy' +ignore-policy: "" - # Same as '--k8s-version' - # Default is empty - k8s-version: 1.21.0 +# Same as '--ignorefile' +ignorefile: ".trivyignore" - # Same as '--tolerations' - # Default is empty - tolerations: - - key1=value1:NoExecute - - key2=value2:NoSchedule +# Same as '--list-all-pkgs' +list-all-pkgs: false - # Same as '--all-namespaces' - # Default is false - all-namespaces: false +# Same as '--output' +output: "" - node-collector: - # Same as '--node-collector-namespace' - # Default is 'trivy-temp' - namespace: ~/.kube/config2 +# Same as '--output-plugin-arg' +output-plugin-arg: "" - # Same as '--node-collector-imageref' - # Default is 'ghcr.io/aquasecurity/node-collector:0.0.9' - imageref: ghcr.io/aquasecurity/node-collector:0.0.9 +# Same as '--report' +report: "all" - exclude: - # Same as '--exclude-owned' - # Default is false - owned: true +scan: + # Same as '--compliance' + compliance: "" - # Same as '--exclude-nodes' - # Default is empty - nodes: - - kubernetes.io/arch:arm64 - - team:dev + # Same as '--show-suppressed' + show-suppressed: false - # Same as '--qps' - # Default is 5.0 - qps: 5.0 +# Same as '--severity' +severity: + - UNKNOWN + - LOW + - MEDIUM + - HIGH + - CRITICAL - # Same as '--burst' - # Default is 10 - burst: 10 -``` +# Same as '--template' +template: "" -## Repository Options -Available with git repository scanning (`trivy repo`) +``` +## Repository options ```yaml repository: # Same as '--branch' - # Default is empty - branch: + branch: "" # Same as '--commit' - # Default is empty - commit: + commit: "" # Same as '--tag' - # Default is empty - tag: -``` + tag: "" -## Client/Server Options -Available in client/server mode +``` +## Scan options ```yaml -server: - # Same as '--server' (available in client mode) - # Default is empty - addr: http://localhost:4954 +scan: + # Same as '--detection-priority' + detection-priority: "precise" - # Same as '--token' - # Default is empty - token: "something-secret" + # Same as '--file-patterns' + file-patterns: [] - # Same as '--token-header' - # Default is 'Trivy-Token' - token-header: 'My-Token-Header' + # Same as '--offline-scan' + offline: false - # Same as '--custom-headers' - # Default is empty - custom-headers: - - scanner: trivy - - x-api-token: xxx - - # Same as '--listen' (available in server mode) - # Default is 'localhost:4954' - listen: 0.0.0.0:10000 -``` + # Same as '--parallel' + parallel: 5 + + # Same as '--rekor-url' + rekor-url: "https://rekor.sigstore.dev" -## Cloud Options + # Same as '--sbom-sources' + sbom-sources: [] -Available for cloud scanning (currently only `trivy aws`) + # Same as '--scanners' + scanners: + - vuln + - secret -```yaml -cloud: - # whether to force a cache update for every scan - update-cache: false - - # how old cached results can be before being invalidated - max-cache-age: 24h - - # aws-specific cloud settings - aws: - # the aws region to use - region: us-east-1 - - # the aws endpoint to use (not required for general use) - endpoint: https://my.custom.aws.endpoint - - # the aws account to use (this will be determined from your environment when not set) - account: 123456789012 - - # the aws specific services - service: - - s3 - - ec2 - - # the aws specific arn - arn: arn:aws:s3:::example-bucket - - # skip the aws specific services - skip-service: - - s3 - - ec2 -``` + # Same as '--skip-dirs' + skip-dirs: [] + + # Same as '--skip-files' + skip-files: [] -## Module Options -Available for modules + # Same as '--slow' + slow: false + +``` +## Secret options ```yaml -module: - # Same as '--module-dir' - # Default is '$HOME/.trivy/modules' - dir: $HOME/.trivy/modules +secret: + # Same as '--secret-config' + config: "trivy-secret.yaml" - # Same as '--enable-modules' - # Default is empty - enable-modules: - - trivy-module-spring4shell - - trivy-module-wordpress ``` +## Vulnerability options + +```yaml +vulnerability: + # Same as '--ignore-status' + ignore-status: [] -[example]: https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/examples/trivy-conf/trivy.yaml + # Same as '--ignore-unfixed' + ignore-unfixed: false + + # Same as '--skip-vex-repo-update' + skip-vex-repo-update: false + + # Same as '--vex' + vex: [] + +``` +[example]: https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/examples/trivy-conf/trivy.yaml \ No newline at end of file diff --git a/magefiles/docs.go b/magefiles/docs.go index 1a59007de229..7d09d37e1092 100644 --- a/magefiles/docs.go +++ b/magefiles/docs.go @@ -3,7 +3,11 @@ package main import ( + "cmp" + "fmt" "os" + "slices" + "strings" "github.com/spf13/cobra/doc" @@ -12,6 +16,15 @@ import ( "github.com/aquasecurity/trivy/pkg/log" ) +const ( + title = "Config file" + description = "Trivy can be customized by tweaking a `trivy.yaml` file.\n" + + "The config path can be overridden by the `--config` flag.\n\n" + + "An example is [here][example].\n\n" + + "These samples contain default values for flags." + footer = "[example]: https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/examples/trivy-conf/trivy.yaml" +) + // Generate CLI references func main() { // Set a dummy path for the documents @@ -26,4 +39,116 @@ func main() { if err := doc.GenMarkdownTree(cmd, "./docs/docs/references/configuration/cli"); err != nil { log.Fatal("Fatal error", log.Err(err)) } + if err := generateConfigDocs("./docs/docs/references/configuration/config-file.md"); err != nil { + log.Fatal("Fatal error in config file generation", log.Err(err)) + } +} + +// generateConfigDocs creates markdown file for Trivy config. +func generateConfigDocs(filename string) error { + // remoteFlags should contain Client and Server flags. + // NewClientFlags doesn't initialize `Listen` field + remoteFlags := flag.NewClientFlags() + remoteFlags.Listen = flag.ServerListenFlag.Clone() + + // These flags don't work from config file. + // Clear configName to skip them later. + globalFlags := flag.NewGlobalFlagGroup() + globalFlags.ConfigFile.ConfigName = "" + globalFlags.ShowVersion.ConfigName = "" + globalFlags.GenerateDefaultConfig.ConfigName = "" + + var allFlagGroups = []flag.FlagGroup{ + globalFlags, + flag.NewCacheFlagGroup(), + flag.NewCleanFlagGroup(), + remoteFlags, + flag.NewDBFlagGroup(), + flag.NewImageFlagGroup(), + flag.NewK8sFlagGroup(), + flag.NewLicenseFlagGroup(), + flag.NewMisconfFlagGroup(), + flag.NewModuleFlagGroup(), + flag.NewRegistryFlagGroup(), + flag.NewRegoFlagGroup(), + flag.NewReportFlagGroup(), + flag.NewRepoFlagGroup(), + flag.NewScanFlagGroup(), + flag.NewSecretFlagGroup(), + flag.NewVulnerabilityFlagGroup(), + } + + f, err := os.Create(filename) + if err != nil { + return err + } + defer f.Close() + f.WriteString("# " + title + "\n\n") + f.WriteString(description + "\n") + + for _, group := range allFlagGroups { + f.WriteString("## " + group.Name() + " options\n") + writeFlags(group, f) + } + + f.WriteString(footer) + return nil +} + +func writeFlags(group flag.FlagGroup, w *os.File) { + flags := group.Flags() + // Sort flags to avoid duplicates of non-last parts of config file + slices.SortFunc(flags, func(a, b flag.Flagger) int { + return cmp.Compare(a.GetConfigName(), b.GetConfigName()) + }) + w.WriteString("\n```yaml\n") + + var lastParts []string + for _, flg := range flags { + if flg.GetConfigName() == "" { + continue + } + // We need to split the config name on `.` to make the indentations needed in yaml. + parts := strings.Split(flg.GetConfigName(), ".") + for i := range parts { + // Skip already added part + if len(lastParts) >= i+1 && parts[i] == lastParts[i] { + continue + } + ind := strings.Repeat(" ", i) + // We need to add a comment and example values only for the last part of the config name. + isLastPart := i == len(parts)-1 + if isLastPart { + // Some `Flags` don't support flag for CLI. (e.g.`LicenseForbidden`). + if flg.GetName() != "" { + fmt.Fprintf(w, "%s# Same as '--%s'\n", ind, flg.GetName()) + } + } + w.WriteString(ind + parts[i] + ":") + if isLastPart { + writeFlagValue(flg.GetDefaultValue(), ind, w) + } + w.WriteString("\n") + } + lastParts = parts + } + w.WriteString("```\n") +} + +func writeFlagValue(val any, ind string, w *os.File) { + switch v := val.(type) { + case []string: + if len(v) > 0 { + w.WriteString("\n") + for _, vv := range v { + fmt.Fprintf(w, "%s - %s\n", ind, vv) + } + } else { + w.WriteString(" []\n") + } + case string: + fmt.Fprintf(w, " %q\n", v) + default: + fmt.Fprintf(w, " %v\n", v) + } } diff --git a/pkg/flag/global_flags.go b/pkg/flag/global_flags.go index ebd79bd5a06c..2d20611b8b72 100644 --- a/pkg/flag/global_flags.go +++ b/pkg/flag/global_flags.go @@ -106,7 +106,7 @@ func NewGlobalFlagGroup() *GlobalFlagGroup { } func (f *GlobalFlagGroup) Name() string { - return "global" + return "Global" } func (f *GlobalFlagGroup) Flags() []Flagger { diff --git a/pkg/flag/options.go b/pkg/flag/options.go index e5c46c81c64f..9bbcef79690b 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -196,6 +196,14 @@ func (f *Flag[T]) GetName() string { return f.Name } +func (f *Flag[T]) GetConfigName() string { + return f.ConfigName +} + +func (f *Flag[T]) GetDefaultValue() any { + return f.Default +} + func (f *Flag[T]) GetAliases() []Alias { return f.Aliases } @@ -302,6 +310,8 @@ type FlagGroup interface { type Flagger interface { GetName() string + GetConfigName() string + GetDefaultValue() any GetAliases() []Alias Parse() error From bb2e26a0ab707b718f6a890cbc87e2492298b6e5 Mon Sep 17 00:00:00 2001 From: Alberto Donato Date: Tue, 6 Aug 2024 02:54:58 +0200 Subject: [PATCH 274/352] fix(terraform): add aws_region name to presets (#7184) --- .../scanners/terraform/parser/parser_test.go | 39 +++++++++++++++++++ pkg/iac/terraform/presets.go | 5 ++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index 10232b007fd9..a754d48d3bb2 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -1745,3 +1745,42 @@ func TestTFVarsFileDoesNotExist(t *testing.T) { _, _, err := parser.EvaluateAll(context.TODO()) assert.ErrorContains(t, err, "file does not exist") } + +func Test_AWSRegionNameDefined(t *testing.T) { + + fs := testutil.CreateFS(t, map[string]string{ + "code/test.tf": ` +data "aws_region" "current" {} + +data "aws_region" "other" { + name = "us-east-2" +} + +resource "something" "blah" { + r1 = data.aws_region.current.name + r2 = data.aws_region.other.name +} +`, + }) + + parser := New(fs, "", OptionStopOnHCLError(true)) + require.NoError(t, parser.ParseFS(context.TODO(), "code")) + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + require.Len(t, modules, 1) + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("something") + require.Len(t, blocks, 1) + block := blocks[0] + + r1 := block.GetAttribute("r1") + require.NotNil(t, r1) + assert.True(t, r1.IsResolvable()) + assert.Equal(t, "current-region", r1.Value().AsString()) + + r2 := block.GetAttribute("r2") + require.NotNil(t, r2) + assert.True(t, r2.IsResolvable()) + assert.Equal(t, "us-east-2", r2.Value().AsString()) +} diff --git a/pkg/iac/terraform/presets.go b/pkg/iac/terraform/presets.go index d10d795a86d7..b29a5fc9c181 100644 --- a/pkg/iac/terraform/presets.go +++ b/pkg/iac/terraform/presets.go @@ -19,13 +19,16 @@ func createPresetValues(b *Block) map[string]cty.Value { presets["arn"] = cty.StringVal(b.ID()) } - // workaround for weird iam feature switch b.TypeLabel() { + // workaround for weird iam feature case "aws_iam_policy_document": presets["json"] = cty.StringVal(b.ID()) // If the user leaves the name blank, Terraform will automatically generate a unique name case "aws_launch_template": presets["name"] = cty.StringVal(uuid.New().String()) + // allow referencing the current region name + case "aws_region": + presets["name"] = cty.StringVal("current-region") } return presets From 85dadf56265647c000191561db10b08a4948c140 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 6 Aug 2024 11:13:28 +0700 Subject: [PATCH 275/352] perf(misconf): do not convert contents of a YAML file to string (#7292) Signed-off-by: nikpivkin --- pkg/iac/detection/detect.go | 10 +++++----- pkg/iac/scanners/yaml/parser/parser.go | 11 +++++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/pkg/iac/detection/detect.go b/pkg/iac/detection/detect.go index 787b82cb0b6a..6a7eb1f8ca3d 100644 --- a/pkg/iac/detection/detect.go +++ b/pkg/iac/detection/detect.go @@ -221,15 +221,15 @@ func init() { } data := buf.Bytes() - marker := "\n---\n" - altMarker := "\r\n---\r\n" - if bytes.Contains(data, []byte(altMarker)) { + marker := []byte("\n---\n") + altMarker := []byte("\r\n---\r\n") + if bytes.Contains(data, altMarker) { marker = altMarker } - for _, partial := range strings.Split(string(data), marker) { + for _, partial := range bytes.Split(data, marker) { var result map[string]any - if err := yaml.Unmarshal([]byte(partial), &result); err != nil { + if err := yaml.Unmarshal(partial, &result); err != nil { continue } match := true diff --git a/pkg/iac/scanners/yaml/parser/parser.go b/pkg/iac/scanners/yaml/parser/parser.go index d8b50faf2437..febcfb100008 100644 --- a/pkg/iac/scanners/yaml/parser/parser.go +++ b/pkg/iac/scanners/yaml/parser/parser.go @@ -6,7 +6,6 @@ import ( "io" "io/fs" "path/filepath" - "strings" "gopkg.in/yaml.v3" @@ -77,15 +76,15 @@ func (p *Parser) ParseFile(_ context.Context, fsys fs.FS, path string) ([]any, e var results []any - marker := "\n---\n" - altMarker := "\r\n---\r\n" - if bytes.Contains(contents, []byte(altMarker)) { + marker := []byte("\n---\n") + altMarker := []byte("\r\n---\r\n") + if bytes.Contains(contents, altMarker) { marker = altMarker } - for _, partial := range strings.Split(string(contents), marker) { + for _, partial := range bytes.Split(contents, marker) { var target any - if err := yaml.Unmarshal([]byte(partial), &target); err != nil { + if err := yaml.Unmarshal(partial, &target); err != nil { return nil, err } results = append(results, target) From 13789b718d50df967ca3e3142a1a3295af750642 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 6 Aug 2024 11:14:06 +0700 Subject: [PATCH 276/352] refactor(misconf): remove unused universal scanner (#7293) Signed-off-by: nikpivkin --- pkg/iac/scanners/universal/scanner.go | 63 --------------------------- 1 file changed, 63 deletions(-) delete mode 100644 pkg/iac/scanners/universal/scanner.go diff --git a/pkg/iac/scanners/universal/scanner.go b/pkg/iac/scanners/universal/scanner.go deleted file mode 100644 index 3687da85fc3d..000000000000 --- a/pkg/iac/scanners/universal/scanner.go +++ /dev/null @@ -1,63 +0,0 @@ -package universal - -import ( - "context" - "io/fs" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners" - "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation" - "github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile" - "github.com/aquasecurity/trivy/pkg/iac/scanners/helm" - "github.com/aquasecurity/trivy/pkg/iac/scanners/json" - "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" - "github.com/aquasecurity/trivy/pkg/iac/scanners/toml" - "github.com/aquasecurity/trivy/pkg/iac/scanners/yaml" -) - -type nestableFSScanners interface { - scanners.FSScanner - options.ConfigurableScanner -} - -var _ scanners.FSScanner = (*Scanner)(nil) - -type Scanner struct { - fsScanners []nestableFSScanners -} - -func New(opts ...options.ScannerOption) *Scanner { - s := &Scanner{ - fsScanners: []nestableFSScanners{ - terraform.New(opts...), - cloudformation.New(opts...), - dockerfile.NewScanner(opts...), - kubernetes.NewScanner(opts...), - json.NewScanner(opts...), - yaml.NewScanner(opts...), - toml.NewScanner(opts...), - helm.New(opts...), - arm.New(opts...), - }, - } - return s -} - -func (s *Scanner) Name() string { - return "Universal" -} - -func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) { - var results scan.Results - for _, inner := range s.fsScanners { - innerResults, err := inner.ScanFS(ctx, fsys, dir) - if err != nil { - return nil, err - } - results = append(results, innerResults...) - } - return results, nil -} From c766831069e188226efafeec184e41498685ed85 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 7 Aug 2024 00:06:24 +0700 Subject: [PATCH 277/352] perf(misconf): use json.Valid to check validity of JSON (#7308) Signed-off-by: nikpivkin --- pkg/iac/detection/detect.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/iac/detection/detect.go b/pkg/iac/detection/detect.go index 6a7eb1f8ca3d..cec90a79cdca 100644 --- a/pkg/iac/detection/detect.go +++ b/pkg/iac/detection/detect.go @@ -45,8 +45,8 @@ func init() { return true } - var content any - return json.NewDecoder(r).Decode(&content) == nil + b, err := io.ReadAll(r) + return err == nil && json.Valid(b) } matchers[FileTypeYAML] = func(name string, r io.ReadSeeker) bool { From a4180bddd43d86e479edf0afe0c362021d071482 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 7 Aug 2024 00:29:16 +0700 Subject: [PATCH 278/352] fix(misconf): load only submodule if it is specified in source (#7112) Signed-off-by: nikpivkin --- .../terraform/parser/resolvers/cache.go | 4 +- .../resolvers/cache_integration_test.go | 19 +++++---- .../terraform/parser/resolvers/registry.go | 2 +- .../terraform/parser/resolvers/remote.go | 4 +- .../terraform/parser/resolvers/source.go | 7 ++-- .../terraform/parser/resolvers/source_test.go | 42 +++++++++++-------- 6 files changed, 45 insertions(+), 33 deletions(-) diff --git a/pkg/iac/scanners/terraform/parser/resolvers/cache.go b/pkg/iac/scanners/terraform/parser/resolvers/cache.go index e08c43ff3ba0..c8b2f660ed33 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/cache.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/cache.go @@ -51,7 +51,7 @@ func (r *cacheResolver) Resolve(_ context.Context, _ fs.FS, opt Options) (filesy return nil, "", "", false, nil } - src := removeSubdirFromSource(opt.Source) + src, subdir := splitPackageSubdirRaw(opt.Source) key := cacheKey(src, opt.Version) opt.Debug("Trying to resolve: %s", key) @@ -62,7 +62,7 @@ func (r *cacheResolver) Resolve(_ context.Context, _ fs.FS, opt Options) (filesy return nil, "", "", true, err } - return os.DirFS(filepath.Join(cacheDir, key)), opt.OriginalSource, ".", true, nil + return os.DirFS(filepath.Join(cacheDir, key)), opt.OriginalSource, subdir, true, nil } return nil, "", "", false, nil } diff --git a/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go b/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go index a7c6704b6708..43ad7f06b15b 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go @@ -21,9 +21,10 @@ func TestResolveModuleFromCache(t *testing.T) { } tests := []struct { - name string - opts resolvers.Options - firstResolver moduleResolver + name string + opts resolvers.Options + firstResolver moduleResolver + expectedSubdir string }{ { name: "registry", @@ -32,7 +33,8 @@ func TestResolveModuleFromCache(t *testing.T) { Source: "terraform-aws-modules/s3-bucket/aws", Version: "4.1.2", }, - firstResolver: resolvers.Registry, + firstResolver: resolvers.Registry, + expectedSubdir: ".", }, { name: "registry with subdir", @@ -41,7 +43,8 @@ func TestResolveModuleFromCache(t *testing.T) { Source: "terraform-aws-modules/s3-bucket/aws//modules/object", Version: "4.1.2", }, - firstResolver: resolvers.Registry, + firstResolver: resolvers.Registry, + expectedSubdir: "modules/object", }, { name: "remote", @@ -49,7 +52,8 @@ func TestResolveModuleFromCache(t *testing.T) { Name: "bucket", Source: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git?ref=v4.1.2", }, - firstResolver: resolvers.Remote, + firstResolver: resolvers.Remote, + expectedSubdir: ".", }, { name: "remote with subdir", @@ -57,7 +61,8 @@ func TestResolveModuleFromCache(t *testing.T) { Name: "object", Source: "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git//modules/object?ref=v4.1.2", }, - firstResolver: resolvers.Remote, + firstResolver: resolvers.Remote, + expectedSubdir: "modules/object", }, } diff --git a/pkg/iac/scanners/terraform/parser/resolvers/registry.go b/pkg/iac/scanners/terraform/parser/resolvers/registry.go index bcdd4734974a..8f2ab2ecdde1 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/registry.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/registry.go @@ -45,7 +45,7 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option } inputVersion := opt.Version - source := removeSubdirFromSource(opt.Source) + source, _ := splitPackageSubdirRaw(opt.Source) parts := strings.Split(source, "/") if len(parts) < 3 || len(parts) > 4 { return diff --git a/pkg/iac/scanners/terraform/parser/resolvers/remote.go b/pkg/iac/scanners/terraform/parser/resolvers/remote.go index df94c775da78..790b3509fd33 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/remote.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/remote.go @@ -38,7 +38,7 @@ func (r *remoteResolver) Resolve(ctx context.Context, _ fs.FS, opt Options) (fil return nil, "", "", false, nil } - src := removeSubdirFromSource(opt.OriginalSource) + src, subdir := splitPackageSubdirRaw(opt.OriginalSource) key := cacheKey(src, opt.OriginalVersion) opt.Debug("Storing with cache key %s", key) @@ -54,7 +54,7 @@ func (r *remoteResolver) Resolve(ctx context.Context, _ fs.FS, opt Options) (fil r.incrementCount(opt) opt.Debug("Successfully downloaded %s from %s", opt.Name, opt.Source) opt.Debug("Module '%s' resolved via remote download.", opt.Name) - return os.DirFS(cacheDir), opt.Source, filepath.Join(".", opt.RelativePath), true, nil + return os.DirFS(cacheDir), opt.Source, subdir, true, nil } func (r *remoteResolver) download(ctx context.Context, opt Options, dst string) error { diff --git a/pkg/iac/scanners/terraform/parser/resolvers/source.go b/pkg/iac/scanners/terraform/parser/resolvers/source.go index d7637ffaf8a5..ee5662d6c7d0 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/source.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/source.go @@ -2,7 +2,7 @@ package resolvers import "strings" -func removeSubdirFromSource(src string) string { +func splitPackageSubdirRaw(src string) (string, string) { stop := len(src) if idx := strings.Index(src, "?"); idx > -1 { stop = idx @@ -18,7 +18,7 @@ func removeSubdirFromSource(src string) string { // First see if we even have an explicit subdir idx := strings.Index(src[offset:stop], "//") if idx == -1 { - return src + return src, "." } idx += offset @@ -29,8 +29,9 @@ func removeSubdirFromSource(src string) string { // URL. if idx = strings.Index(subdir, "?"); idx > -1 { query := subdir[idx:] + subdir = subdir[:idx] src += query } - return src + return src, subdir } diff --git a/pkg/iac/scanners/terraform/parser/resolvers/source_test.go b/pkg/iac/scanners/terraform/parser/resolvers/source_test.go index 3f57ab68b6d3..46df7db700dc 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/source_test.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/source_test.go @@ -6,39 +6,45 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRemoveSubdirFromSource(t *testing.T) { +func TestSplitPackageSubdirRaw(t *testing.T) { tests := []struct { - name string - source string - expected string + name string + source string + expectedPkg string + expectedSubdir string }{ { - name: "address with scheme and query string", - source: "git::https://github.com/aquasecurity/terraform-modules.git//modules/ecs-service?ref=v0.1.0", - expected: "git::https://github.com/aquasecurity/terraform-modules.git?ref=v0.1.0", + name: "address with scheme and query string", + source: "git::https://github.com/aquasecurity/terraform-modules.git//modules/ecs-service?ref=v0.1.0", + expectedPkg: "git::https://github.com/aquasecurity/terraform-modules.git?ref=v0.1.0", + expectedSubdir: "modules/ecs-service", }, { - name: "address with scheme", - source: "git::https://github.com/aquasecurity/terraform-modules.git//modules/ecs-service", - expected: "git::https://github.com/aquasecurity/terraform-modules.git", + name: "address with scheme", + source: "git::https://github.com/aquasecurity/terraform-modules.git//modules/ecs-service", + expectedPkg: "git::https://github.com/aquasecurity/terraform-modules.git", + expectedSubdir: "modules/ecs-service", }, { - name: "registry address", - source: "hashicorp/consul/aws//modules/consul-cluster", - expected: "hashicorp/consul/aws", + name: "registry address", + source: "hashicorp/consul/aws//modules/consul-cluster", + expectedPkg: "hashicorp/consul/aws", + expectedSubdir: "modules/consul-cluster", }, { - name: "without subdir", - source: `hashicorp/consul/aws`, - expected: `hashicorp/consul/aws`, + name: "without subdir", + source: `hashicorp/consul/aws`, + expectedPkg: `hashicorp/consul/aws`, + expectedSubdir: ".", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got := removeSubdirFromSource(test.source) - assert.Equal(t, test.expected, got) + pkgAddr, subdir := splitPackageSubdirRaw(test.source) + assert.Equal(t, test.expectedPkg, pkgAddr) + assert.Equal(t, test.expectedSubdir, subdir) }) } } From a817fae85b7272b391b737ec86673a7cab722bae Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 7 Aug 2024 06:42:31 +0700 Subject: [PATCH 279/352] feat(misconf): support for policy and bucket grants (#7284) Signed-off-by: nikpivkin --- .../adapters/cloudformation/aws/s3/bucket.go | 35 ++++++++++++ .../adapters/cloudformation/aws/s3/s3_test.go | 33 ++++++++++++ .../adapters/terraform/aws/s3/adapt_test.go | 54 +++++++++++++++++++ pkg/iac/adapters/terraform/aws/s3/bucket.go | 50 +++++++++++++++++ pkg/iac/providers/aws/s3/bucket.go | 13 +++++ pkg/iac/rego/schemas/cloud.json | 44 +++++++++++++++ 6 files changed, 229 insertions(+) diff --git a/pkg/iac/adapters/cloudformation/aws/s3/bucket.go b/pkg/iac/adapters/cloudformation/aws/s3/bucket.go index 5f5329fc6714..ec56894dbee4 100644 --- a/pkg/iac/adapters/cloudformation/aws/s3/bucket.go +++ b/pkg/iac/adapters/cloudformation/aws/s3/bucket.go @@ -7,7 +7,9 @@ import ( "strings" s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/liamg/iamgo" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" @@ -37,6 +39,7 @@ func getBuckets(cfFile parser.FileContext) []s3.Bucket { Website: getWebsite(r), BucketLocation: iacTypes.String("", r.Metadata()), Objects: nil, + BucketPolicies: getBucketPolicies(cfFile, r), } buckets = append(buckets, s3b) @@ -156,3 +159,35 @@ func getWebsite(r *parser.Resource) *s3.Website { } } } + +func getBucketPolicies(fctx parser.FileContext, r *parser.Resource) []iam.Policy { + + var policies []iam.Policy + for _, bucketPolicy := range fctx.GetResourcesByType("AWS::S3::BucketPolicy") { + bucket := bucketPolicy.GetStringProperty("Bucket") + if bucket.NotEqualTo(r.GetStringProperty("BucketName").Value()) && bucket.NotEqualTo(r.ID()) { + continue + } + + doc := bucketPolicy.GetProperty("PolicyDocument") + if doc.IsNil() { + continue + } + + parsed, err := iamgo.Parse(doc.GetJsonBytes()) + if err != nil { + continue + } + policies = append(policies, iam.Policy{ + Metadata: doc.Metadata(), + Name: iacTypes.StringDefault("", doc.Metadata()), + Document: iam.Document{ + Metadata: doc.Metadata(), + Parsed: *parsed, + }, + Builtin: iacTypes.Bool(false, doc.Metadata()), + }) + } + + return policies +} diff --git a/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go b/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go index ee8fffb39f75..707aae028f7f 100644 --- a/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go +++ b/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go @@ -3,7 +3,10 @@ package s3 import ( "testing" + "github.com/liamg/iamgo" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" "github.com/aquasecurity/trivy/pkg/iac/types" ) @@ -57,6 +60,19 @@ Resources: Status: Enabled WebsiteConfiguration: IndexDocument: index.html + SampleBucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref Bucket + PolicyDocument: + Version: 2012-10-17 + Statement: + - Action: + - 's3:GetObject' + Effect: Allow + Resource: !Join + - 'arn:aws:s3:::testbucket/*' + Principal: '*' `, expected: s3.S3{ Buckets: []s3.Bucket{ @@ -91,6 +107,23 @@ Resources: Enabled: types.BoolTest(true), }, Website: &s3.Website{}, + BucketPolicies: []iam.Policy{ + { + Document: iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithStatement( + iamgo.NewStatementBuilder(). + WithActions([]string{"s3:GetObject"}). + WithAllPrincipals(true). + WithEffect("Allow"). + WithResources([]string{"arn:aws:s3:::testbucket/*"}). + Build(), + ). + WithVersion("2012-10-17T00:00:00Z"). + Build(), + }, + }, + }, }, }, }, diff --git a/pkg/iac/adapters/terraform/aws/s3/adapt_test.go b/pkg/iac/adapters/terraform/aws/s3/adapt_test.go index 1cd1dec76270..ea12206f312b 100644 --- a/pkg/iac/adapters/terraform/aws/s3/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/s3/adapt_test.go @@ -149,6 +149,15 @@ func Test_Adapt(t *testing.T) { resource "aws_s3_bucket_acl" "example" { bucket = aws_s3_bucket.example.id acl = "private" + access_control_policy { + grant { + grantee { + type = "Group" + uri = "http://acs.amazonaws.com/groups/s3/LogDelivery" + } + permission = "READ_ACP" + } + } } resource "aws_s3_bucket_server_side_encryption_configuration" "example" { @@ -250,6 +259,51 @@ func Test_Adapt(t *testing.T) { TargetBucket: iacTypes.String("aws_s3_bucket.example", iacTypes.NewTestMetadata()), }, ACL: iacTypes.String("private", iacTypes.NewTestMetadata()), + Grants: []s3.Grant{ + { + Metadata: iacTypes.NewTestMetadata(), + Grantee: s3.Grantee{ + Type: iacTypes.StringTest("Group"), + URI: iacTypes.StringTest("http://acs.amazonaws.com/groups/s3/LogDelivery"), + }, + Permissions: iacTypes.StringValueList{ + iacTypes.StringTest("READ_ACP"), + }, + }, + }, + }, + }, + }, + }, + { + name: "bucket with grants", + terraform: ` +resource "aws_s3_bucket" "this" { + bucket = "test" + + grant { + type = "Group" + uri = "http://acs.amazonaws.com/groups/s3/LogDelivery" + permissions = ["FULL_CONTROL"] + } +} +`, + expected: s3.S3{ + Buckets: []s3.Bucket{ + { + Name: iacTypes.StringTest("test"), + ACL: iacTypes.StringTest("private"), + Grants: []s3.Grant{ + { + Grantee: s3.Grantee{ + Type: iacTypes.StringTest("Group"), + URI: iacTypes.StringTest("http://acs.amazonaws.com/groups/s3/LogDelivery"), + }, + Permissions: iacTypes.StringValueList{ + iacTypes.StringTest("FULL_CONTROL"), + }, + }, + }, }, }, }, diff --git a/pkg/iac/adapters/terraform/aws/s3/bucket.go b/pkg/iac/adapters/terraform/aws/s3/bucket.go index 5ecf7e9ba21b..206b0cbdabdf 100644 --- a/pkg/iac/adapters/terraform/aws/s3/bucket.go +++ b/pkg/iac/adapters/terraform/aws/s3/bucket.go @@ -26,6 +26,7 @@ func (a *adapter) adaptBuckets() []s3.Bucket { Versioning: getVersioning(block, a), Logging: getLogging(block, a), ACL: getBucketAcl(block, a), + Grants: getGrants(block, a), AccelerateConfigurationStatus: getAccelerateStatus(block, a), BucketLocation: block.GetAttribute("region").AsStringValueOrDefault("", block), LifecycleConfiguration: getLifecycle(block, a), @@ -193,6 +194,55 @@ func getBucketAcl(block *terraform.Block, a *adapter) iacTypes.StringValue { return iacTypes.StringDefault("private", block.GetMetadata()) } +func getGrants(block *terraform.Block, a *adapter) []s3.Grant { + if val, ok := applyForBucketRelatedResource(a, block, "aws_s3_bucket_acl", func(resource *terraform.Block) []s3.Grant { + var grants []s3.Grant + + if acessControlPolicy := resource.GetBlock("access_control_policy"); acessControlPolicy.IsNotNil() { + for _, grantBlock := range acessControlPolicy.GetBlocks("grant") { + grant := s3.Grant{ + Metadata: grantBlock.GetMetadata(), + Permissions: iacTypes.StringValueList{ + grantBlock.GetAttribute("permission").AsStringValueOrDefault("", grantBlock), + }, + } + + if granteeBlock := grantBlock.GetBlock("grantee"); granteeBlock.IsNotNil() { + grant.Grantee = s3.Grantee{ + Metadata: granteeBlock.GetMetadata(), + Type: granteeBlock.GetAttribute("type").AsStringValueOrDefault("", granteeBlock), + URI: granteeBlock.GetAttribute("uri").AsStringValueOrDefault("", granteeBlock), + } + } + + grants = append(grants, grant) + } + } + + return grants + + }); ok { + return val + } + + var grants []s3.Grant + for _, grantBlock := range block.GetBlocks("grant") { + grant := s3.Grant{ + Metadata: grantBlock.GetMetadata(), + Permissions: grantBlock.GetAttribute("permissions").AsStringValueSliceOrEmpty(), + Grantee: s3.Grantee{ + Metadata: grantBlock.GetMetadata(), + Type: grantBlock.GetAttribute("type").AsStringValueOrDefault("", grantBlock), + URI: grantBlock.GetAttribute("uri").AsStringValueOrDefault("", grantBlock), + }, + } + + grants = append(grants, grant) + } + + return grants +} + func isEncrypted(sseConfgihuration *terraform.Block) iacTypes.BoolValue { return terraform.MapNestedAttribute( sseConfgihuration, diff --git a/pkg/iac/providers/aws/s3/bucket.go b/pkg/iac/providers/aws/s3/bucket.go index e884bbd4a7ff..ccabe6974c99 100755 --- a/pkg/iac/providers/aws/s3/bucket.go +++ b/pkg/iac/providers/aws/s3/bucket.go @@ -14,6 +14,7 @@ type Bucket struct { Versioning Versioning Logging Logging ACL iacTypes.StringValue + Grants []Grant BucketLocation iacTypes.StringValue AccelerateConfigurationStatus iacTypes.StringValue LifecycleConfiguration []Rules @@ -65,3 +66,15 @@ type Contents struct { type Website struct { Metadata iacTypes.Metadata } + +type Grant struct { + Metadata iacTypes.Metadata + Grantee Grantee + Permissions iacTypes.StringValueList +} + +type Grantee struct { + Metadata iacTypes.Metadata + URI iacTypes.StringValue + Type iacTypes.StringValue +} diff --git a/pkg/iac/rego/schemas/cloud.json b/pkg/iac/rego/schemas/cloud.json index ad81b6488ae8..a4bab9423d38 100644 --- a/pkg/iac/rego/schemas/cloud.json +++ b/pkg/iac/rego/schemas/cloud.json @@ -3647,6 +3647,13 @@ "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Encryption" }, + "grants": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Grant" + } + }, "lifecycleconfiguration": { "type": "array", "items": { @@ -3713,6 +3720,43 @@ } } }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Grant": { + "type": "object", + "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, + "grantee": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Grantee" + }, + "permissions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + } + }, + "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Grantee": { + "type": "object", + "properties": { + "__defsec_metadata": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" + }, + "type": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, + "uri": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + } + } + }, "github.com.aquasecurity.trivy.pkg.iac.providers.aws.s3.Logging": { "type": "object", "properties": { From f0ed5e4ced7e60af35c88d5d084aa4b7237f4973 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 7 Aug 2024 07:11:59 +0700 Subject: [PATCH 280/352] fix(misconf): do not set default value for default_cache_behavior (#7234) Signed-off-by: nikpivkin --- pkg/iac/adapters/terraform/aws/cloudfront/adapt.go | 6 +++--- pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/iac/adapters/terraform/aws/cloudfront/adapt.go b/pkg/iac/adapters/terraform/aws/cloudfront/adapt.go index a981241a83a4..721ecf991d55 100644 --- a/pkg/iac/adapters/terraform/aws/cloudfront/adapt.go +++ b/pkg/iac/adapters/terraform/aws/cloudfront/adapt.go @@ -33,7 +33,7 @@ func adaptDistribution(resource *terraform.Block) cloudfront.Distribution { }, DefaultCacheBehaviour: cloudfront.CacheBehaviour{ Metadata: resource.GetMetadata(), - ViewerProtocolPolicy: types.String("allow-all", resource.GetMetadata()), + ViewerProtocolPolicy: types.StringDefault("", resource.GetMetadata()), }, OrdererCacheBehaviours: nil, ViewerCertificate: cloudfront.ViewerCertificate{ @@ -53,13 +53,13 @@ func adaptDistribution(resource *terraform.Block) cloudfront.Distribution { if defaultCacheBlock := resource.GetBlock("default_cache_behavior"); defaultCacheBlock.IsNotNil() { distribution.DefaultCacheBehaviour.Metadata = defaultCacheBlock.GetMetadata() viewerProtocolPolicyAttr := defaultCacheBlock.GetAttribute("viewer_protocol_policy") - distribution.DefaultCacheBehaviour.ViewerProtocolPolicy = viewerProtocolPolicyAttr.AsStringValueOrDefault("allow-all", defaultCacheBlock) + distribution.DefaultCacheBehaviour.ViewerProtocolPolicy = viewerProtocolPolicyAttr.AsStringValueOrDefault("", defaultCacheBlock) } orderedCacheBlocks := resource.GetBlocks("ordered_cache_behavior") for _, orderedCacheBlock := range orderedCacheBlocks { viewerProtocolPolicyAttr := orderedCacheBlock.GetAttribute("viewer_protocol_policy") - viewerProtocolPolicyVal := viewerProtocolPolicyAttr.AsStringValueOrDefault("allow-all", orderedCacheBlock) + viewerProtocolPolicyVal := viewerProtocolPolicyAttr.AsStringValueOrDefault("", orderedCacheBlock) distribution.OrdererCacheBehaviours = append(distribution.OrdererCacheBehaviours, cloudfront.CacheBehaviour{ Metadata: orderedCacheBlock.GetMetadata(), ViewerProtocolPolicy: viewerProtocolPolicyVal, diff --git a/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go b/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go index d20520cd4651..fd1bf65e9cc5 100644 --- a/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go +++ b/pkg/iac/adapters/terraform/aws/cloudfront/adapt_test.go @@ -83,7 +83,7 @@ func Test_adaptDistribution(t *testing.T) { }, DefaultCacheBehaviour: cloudfront.CacheBehaviour{ Metadata: iacTypes.NewTestMetadata(), - ViewerProtocolPolicy: iacTypes.String("allow-all", iacTypes.NewTestMetadata()), + ViewerProtocolPolicy: iacTypes.String("", iacTypes.NewTestMetadata()), }, ViewerCertificate: cloudfront.ViewerCertificate{ From fe9207255a4f7f984ec1447f8a9219ae60e560c4 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 7 Aug 2024 07:33:56 +0700 Subject: [PATCH 281/352] feat(misconf): iterator argument support for dynamic blocks (#7236) Signed-off-by: nikpivkin Co-authored-by: simar7 <1254783+simar7@users.noreply.github.com> --- .../scanners/terraform/parser/evaluator.go | 12 +++++++ .../scanners/terraform/parser/parser_test.go | 36 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/pkg/iac/scanners/terraform/parser/evaluator.go b/pkg/iac/scanners/terraform/parser/evaluator.go index bf3e4f4ffc13..0e26103e8c96 100644 --- a/pkg/iac/scanners/terraform/parser/evaluator.go +++ b/pkg/iac/scanners/terraform/parser/evaluator.go @@ -340,6 +340,18 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool ctx.Set(idx, block.TypeLabel(), "key") ctx.Set(val, block.TypeLabel(), "value") + if isDynamic { + if iterAttr := block.GetAttribute("iterator"); iterAttr.IsNotNil() { + refs := iterAttr.AllReferences() + if len(refs) == 1 { + ctx.Set(idx, refs[0].TypeLabel(), "key") + ctx.Set(val, refs[0].TypeLabel(), "value") + } else { + e.debug.Log("Ignoring iterator attribute in dynamic block, expected one reference but got %d", len(refs)) + } + } + } + forEachFiltered = append(forEachFiltered, clone) values := clone.Values() diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index a754d48d3bb2..e7a8041aa7ff 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -1746,6 +1746,42 @@ func TestTFVarsFileDoesNotExist(t *testing.T) { assert.ErrorContains(t, err, "file does not exist") } +func TestDynamicWithIterator(t *testing.T) { + fsys := fstest.MapFS{ + "main.tf": &fstest.MapFile{ + Data: []byte(`resource "aws_s3_bucket" "this" { + dynamic versioning { + for_each = [true] + iterator = ver + + content { + enabled = ver.value + } + } +}`), + }, + } + + parser := New( + fsys, "", + OptionStopOnHCLError(true), + OptionWithDownloads(false), + ) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + + assert.Len(t, modules, 1) + + buckets := modules.GetResourcesByType("aws_s3_bucket") + assert.Len(t, buckets, 1) + + attr, _ := buckets[0].GetNestedAttribute("versioning.enabled") + + assert.True(t, attr.Value().True()) +} + func Test_AWSRegionNameDefined(t *testing.T) { fs := testutil.CreateFS(t, map[string]string{ From ac3eb9d59cd43c707975bf39b455536bb99928e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:51:29 +0400 Subject: [PATCH 282/352] chore(deps): bump the common group across 1 directory with 7 updates (#7305) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 34 ++++++++++++------------ go.sum | 81 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 57 insertions(+), 58 deletions(-) diff --git a/go.mod b/go.mod index 70277514b22a..ac6f232878bc 100644 --- a/go.mod +++ b/go.mod @@ -32,9 +32,9 @@ require ( github.com/aws/aws-sdk-go-v2 v1.30.3 github.com/aws/aws-sdk-go-v2/config v1.27.27 github.com/aws/aws-sdk-go-v2/credentials v1.17.27 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.172.0 - github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3 - github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 + github.com/aws/aws-sdk-go-v2/service/ecr v1.31.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect github.com/aws/smithy-go v1.20.3 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c @@ -62,7 +62,7 @@ require ( github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/golang-lru/v2 v2.0.7 - github.com/hashicorp/hc-install v0.7.0 + github.com/hashicorp/hc-install v0.8.0 github.com/hashicorp/hcl/v2 v2.21.0 github.com/hashicorp/terraform-exec v0.21.0 github.com/in-toto/in-toto-golang v0.9.0 @@ -88,7 +88,7 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 github.com/moby/buildkit v0.15.1 - github.com/open-policy-agent/opa v0.66.0 + github.com/open-policy-agent/opa v0.67.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/openvex/discovery v0.1.0 @@ -119,9 +119,9 @@ require ( go.etcd.io/bbolt v1.3.10 golang.org/x/crypto v0.25.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect - golang.org/x/mod v0.19.0 + golang.org/x/mod v0.20.0 golang.org/x/net v0.27.0 - golang.org/x/sync v0.7.0 + golang.org/x/sync v0.8.0 golang.org/x/term v0.22.0 golang.org/x/text v0.16.0 golang.org/x/vuln v1.1.3 @@ -184,7 +184,7 @@ require ( github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/briandowns/spinner v1.23.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/cgroups/v3 v3.0.2 // indirect @@ -261,7 +261,6 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect @@ -368,12 +367,11 @@ require ( go.mongodb.org/mongo-driver v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect - go.opentelemetry.io/otel v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.27.0 // indirect - go.opentelemetry.io/otel/sdk v1.27.0 // indirect - go.opentelemetry.io/otel/trace v1.27.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect @@ -384,9 +382,9 @@ require ( golang.org/x/tools v0.23.0 // indirect google.golang.org/api v0.172.0 // indirect google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect - google.golang.org/grpc v1.64.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.65.0 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index a5dde364799f..a24b98453b2c 100644 --- a/go.sum +++ b/go.sum @@ -381,10 +381,10 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.172.0 h1:lJjLKG92RyKIIYujVvulR3JpVjr3yxaU34nwXCq8K2o= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.172.0/go.mod h1:o6QDjdVKpP5EF0dp/VlvqckzuSDATr1rLdHt3A5m0YY= -github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3 h1:+v2hv29pWaVDASIScHuUhDC93nqJGVlGf6cujrJMHZE= -github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3/go.mod h1:RhaP7Wil0+uuuhiE4FzOOEFZwkmFAk1ZflXzK+O3ptU= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 h1:ta62lid9JkIpKZtZZXSj6rP2AqY5x1qYGq53ffxqD9Q= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0/go.mod h1:o6QDjdVKpP5EF0dp/VlvqckzuSDATr1rLdHt3A5m0YY= +github.com/aws/aws-sdk-go-v2/service/ecr v1.31.0 h1:vi/MwojjLGATEEUFn2GEdLiom7CFlB+qCIx4tDWqKfQ= +github.com/aws/aws-sdk-go-v2/service/ecr v1.31.0/go.mod h1:RhaP7Wil0+uuuhiE4FzOOEFZwkmFAk1ZflXzK+O3ptU= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 h1:PpbXaecV3sLAS6rjQiaKw4/jyq3Z8gNzmoJupHAoBp0= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2/go.mod h1:fUHpGXr4DrXkEDpGAjClPsviWf+Bszeb0daKE0blxv8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= @@ -393,8 +393,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrx github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 h1:yS0JkEdV6h9JOo8sy2JSpjX+i7vsKifU8SIeHrqiDhU= github.com/aws/aws-sdk-go-v2/service/kms v1.30.0/go.mod h1:+I8VUUSVD4p5ISQtzpgSva4I8cJ4SQ4b1dcBcof7O+g= -github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALwfMWpd64tONS/NE= -github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 h1:hT8ZAZRIfqBqHbzKTII+CIiY8G2oC9OpLedkZ51DWl8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= @@ -445,8 +445,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= @@ -714,8 +714,8 @@ github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= -github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -864,6 +864,7 @@ github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= @@ -901,8 +902,8 @@ github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk= -github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= +github.com/hashicorp/hc-install v0.8.0 h1:LdpZeXkZYMQhoKPCecJHlKvUkQFixN/nvyR1CdfOLjI= +github.com/hashicorp/hc-install v0.8.0/go.mod h1:+MwJYjDfCruSD/udvBmRB22Nlkwwkwf5sAB6uTIhSaU= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= @@ -1142,8 +1143,8 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= -github.com/open-policy-agent/opa v0.66.0 h1:DbrvfJQja0FBRcPOB3Z/BOckocN+M4ApNWyNhSRJt0w= -github.com/open-policy-agent/opa v0.66.0/go.mod h1:EIgNnJcol7AvQR/IcWLwL13k64gHVbNAVG46b2G+/EY= +github.com/open-policy-agent/opa v0.67.0 h1:FOdsO9yNhfmrh+72oVK7ImWmzruG+VSpfbr5IBqEWVs= +github.com/open-policy-agent/opa v0.67.0/go.mod h1:aqKlHc8E2VAAylYE9x09zJYr/fYzGX+JKne89UGqFzk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -1426,25 +1427,25 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= -go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= -go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= -go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= -go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= -go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= -go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= -go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.step.sm/crypto v0.44.2 h1:t3p3uQ7raP2jp2ha9P6xkQF85TJZh+87xmjSLaib+jk= @@ -1512,8 +1513,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1619,8 +1620,8 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1980,10 +1981,10 @@ google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= -google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -2019,8 +2020,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 7278abd4e42d5d7379a7df7e7a8f028aa73a895f Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 7 Aug 2024 14:06:40 +0700 Subject: [PATCH 283/352] docs: update client/server docs for misconf and license scanning (#7277) Signed-off-by: nikpivkin Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- docs/docs/references/modes/client-server.md | 21 ++++++-- pkg/commands/artifact/run.go | 53 ++++++++++----------- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/docs/docs/references/modes/client-server.md b/docs/docs/references/modes/client-server.md index 518fe45efc8a..7aa90abf0b22 100644 --- a/docs/docs/references/modes/client-server.md +++ b/docs/docs/references/modes/client-server.md @@ -2,9 +2,22 @@ Trivy has client/server mode. Trivy server has vulnerability database and Trivy client doesn't have to download vulnerability database. It is useful if you want to scan images or files at multiple locations and do not want to download the database at every location. -| Client/Server Mode | Image | Rootfs | Filesystem | Repository | Config | AWS | K8s | -|:---------------------:|:-----:|:------:|:----------:|:----------:|:------:|:---:|:---:| -| Supported | ✅ | ✅ | ✅ | ✅ | ✅ | X | X | +| Client/Server Mode | Image | Rootfs | Filesystem | Repository | Config | K8s | +|:------------------:|:-----:|:------:|:----------:|:----------:|:------:|:---:| +| Supported | ✅ | ✅ | ✅ | ✅ | - | - | + +Some scanners run on the client side, even in client/server mode. + +| Scanner | Run on Client or Server | +|:----------------:|:-----------------------:| +| Vulnerability | Server | +| Misconfiguration | Client[^1] | +| Secret | Client[^2] | +| License | Server | + +!!! note + Scanning of misconfigurations and licenses is performed on the client side (as in standalone mode). + Otherwise, the client would need to send files to the server that may contain sensitive information. ## Server At first, you need to launch Trivy server. It downloads vulnerability database automatically and continue to fetch the latest DB in the background. @@ -338,3 +351,5 @@ Returns the `200 OK` status if the request was successful. ![architecture](../../../imgs/client-server.png) +[^1]: The checks bundle is also downloaded on the client side. +[^2]: The scan result with masked secrets is sent to the server \ No newline at end of file diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 6528ec2c3d36..3cd1b9af74b5 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -40,7 +40,6 @@ const ( TargetFilesystem TargetKind = "fs" TargetRootfs TargetKind = "rootfs" TargetRepository TargetKind = "repo" - TargetImageArchive TargetKind = "archive" TargetSBOM TargetKind = "sbom" TargetVM TargetKind = "vm" ) @@ -345,6 +344,15 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err } }() + if opts.ServerAddr != "" && opts.Scanners.AnyEnabled(types.MisconfigScanner, types.SecretScanner) { + log.WarnContext(ctx, + fmt.Sprintf( + "Trivy runs in client/server mode, but misconfiguration and license scanning will be done on the client side, see %s", + doc.URL("/docs/references/modes/client-server", ""), + ), + ) + } + if opts.GenerateDefaultConfig { log.Info("Writing the default config to trivy-default.yaml...") return viper.SafeWriteConfigAs("trivy-default.yaml") @@ -359,32 +367,23 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err } defer r.Close(ctx) - var report types.Report - switch targetKind { - case TargetContainerImage, TargetImageArchive: - if report, err = r.ScanImage(ctx, opts); err != nil { - return xerrors.Errorf("image scan error: %w", err) - } - case TargetFilesystem: - if report, err = r.ScanFilesystem(ctx, opts); err != nil { - return xerrors.Errorf("filesystem scan error: %w", err) - } - case TargetRootfs: - if report, err = r.ScanRootfs(ctx, opts); err != nil { - return xerrors.Errorf("rootfs scan error: %w", err) - } - case TargetRepository: - if report, err = r.ScanRepository(ctx, opts); err != nil { - return xerrors.Errorf("repository scan error: %w", err) - } - case TargetSBOM: - if report, err = r.ScanSBOM(ctx, opts); err != nil { - return xerrors.Errorf("sbom scan error: %w", err) - } - case TargetVM: - if report, err = r.ScanVM(ctx, opts); err != nil { - return xerrors.Errorf("vm scan error: %w", err) - } + scans := map[TargetKind]func(context.Context, flag.Options) (types.Report, error){ + TargetContainerImage: r.ScanImage, + TargetFilesystem: r.ScanFilesystem, + TargetRootfs: r.ScanRootfs, + TargetRepository: r.ScanRepository, + TargetSBOM: r.ScanSBOM, + TargetVM: r.ScanVM, + } + + scanFunction, exists := scans[targetKind] + if !exists { + return xerrors.Errorf("unknown target kind: %s", targetKind) + } + + report, err := scanFunction(ctx, opts) + if err != nil { + return xerrors.Errorf("%s scan error: %w", targetKind, err) } report, err = r.Filter(ctx, opts, report) From 65d991cee7c5149095c5a0931595bb74725c61fc Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 8 Aug 2024 13:00:05 +0700 Subject: [PATCH 284/352] docs: update links to packaging.python.org (#7318) Signed-off-by: nikpivkin --- docs/docs/coverage/language/python.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/coverage/language/python.md b/docs/docs/coverage/language/python.md index 15cadd6f96c3..7a697b87d250 100644 --- a/docs/docs/coverage/language/python.md +++ b/docs/docs/coverage/language/python.md @@ -42,7 +42,7 @@ Trivy parses your files generated by package managers in filesystem/repository s ### pip #### Dependency detection -Trivy only parses [version specifiers](https://packaging.python.org/en/latest/specifications/version-specifiers/#id4) with `==` comparison operator and without `.*`. +Trivy only parses [version specifiers](https://packaging.python.org/en/latest/specifications/version-specifiers/#id5) with `==` comparison operator and without `.*`. To convert unsupported version specifiers - use the `pip freeze` command. ```bash @@ -119,7 +119,7 @@ License detection is not supported for `Poetry`. ## Packaging Trivy parses the manifest files of installed packages in container image scanning and so on. -See [here](https://packaging.python.org/en/latest/discussions/wheel-vs-egg/) for the detail. +See [here](https://packaging.python.org/en/latest/discussions/package-formats/) for the detail. ### Egg Trivy looks for `*.egg-info`, `*.egg-info/PKG-INFO`, `*.egg` and `EGG-INFO/PKG-INFO` to identify Python packages. From 2b6d8d9227fb6ecc9386a14333964c23c0370a52 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Fri, 9 Aug 2024 05:09:36 +0700 Subject: [PATCH 285/352] perf(misconf): optimize work with context (#6968) Signed-off-by: nikpivkin --- .../scanners/terraform/parser/evaluator.go | 56 ++++++++++++++----- pkg/iac/terraform/block.go | 5 +- pkg/iac/terraform/context/context.go | 49 ++++++++++------ pkg/iac/terraform/context/context_test.go | 37 ++++++++++++ 4 files changed, 113 insertions(+), 34 deletions(-) diff --git a/pkg/iac/scanners/terraform/parser/evaluator.go b/pkg/iac/scanners/terraform/parser/evaluator.go index 0e26103e8c96..e0231d2311a5 100644 --- a/pkg/iac/scanners/terraform/parser/evaluator.go +++ b/pkg/iac/scanners/terraform/parser/evaluator.go @@ -96,9 +96,8 @@ func (e *evaluator) evaluateStep() { e.ctx.Set(e.getValuesByBlockType("locals"), "local") e.ctx.Set(e.getValuesByBlockType("provider"), "provider") - resources := e.getValuesByBlockType("resource") - for key, resource := range resources.AsValueMap() { - e.ctx.Set(resource, key) + for typ, resource := range e.getResources() { + e.ctx.Set(resource, typ) } e.ctx.Set(e.getValuesByBlockType("data"), "data") @@ -224,10 +223,12 @@ func (e *evaluator) evaluateSteps() { var lastContext hcl.EvalContext for i := 0; i < maxContextIterations; i++ { + e.debug.Log("Starting iteration %d", i) e.evaluateStep() // if ctx matches the last evaluation, we can bail, nothing left to resolve if i > 0 && reflect.DeepEqual(lastContext.Variables, e.ctx.Inner().Variables) { + e.debug.Log("Context unchanged at i=%d", i) break } if len(e.ctx.Inner().Variables) != len(lastContext.Variables) { @@ -330,15 +331,16 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool } clone := block.Clone(idx) - ctx := clone.Context() - e.copyVariables(block, clone) - ctx.SetByDot(idx, "each.key") - ctx.SetByDot(val, "each.value") - ctx.Set(idx, block.TypeLabel(), "key") - ctx.Set(val, block.TypeLabel(), "value") + eachObj := cty.ObjectVal(map[string]cty.Value{ + "key": idx, + "value": val, + }) + + ctx.Set(eachObj, "each") + ctx.Set(eachObj, block.TypeLabel()) if isDynamic { if iterAttr := block.GetAttribute("iterator"); iterAttr.IsNotNil() { @@ -354,9 +356,7 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool forEachFiltered = append(forEachFiltered, clone) - values := clone.Values() - clones[idx.AsString()] = values - e.ctx.SetByDot(values, clone.GetMetadata().Reference()) + clones[idx.AsString()] = clone.Values() }) metadata := block.GetMetadata() @@ -434,11 +434,12 @@ func (e *evaluator) copyVariables(from, to *terraform.Block) { return } - srcValue := e.ctx.Root().Get(fromBase, fromRel) + rootCtx := e.ctx.Root() + srcValue := rootCtx.Get(fromBase, fromRel) if srcValue == cty.NilVal { return } - e.ctx.Root().Set(srcValue, fromBase, toRel) + rootCtx.Set(srcValue, fromBase, toRel) } func (e *evaluator) evaluateVariable(b *terraform.Block) (cty.Value, error) { @@ -530,7 +531,7 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value { continue } values[b.Label()] = b.Values() - case "resource", "data": + case "data": if len(b.Labels()) < 2 { continue } @@ -553,3 +554,28 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value { return cty.ObjectVal(values) } + +func (e *evaluator) getResources() map[string]cty.Value { + values := make(map[string]map[string]cty.Value) + + for _, b := range e.blocks { + if b.Type() != "resource" { + continue + } + + if len(b.Labels()) < 2 { + continue + } + + val, exists := values[b.Labels()[0]] + if !exists { + val = make(map[string]cty.Value) + values[b.Labels()[0]] = val + } + val[b.Labels()[1]] = b.Values() + } + + return lo.MapValues(values, func(v map[string]cty.Value, _ string) cty.Value { + return cty.ObjectVal(v) + }) +} diff --git a/pkg/iac/terraform/block.go b/pkg/iac/terraform/block.go index 8b49225b6e69..9245ad6c38a8 100644 --- a/pkg/iac/terraform/block.go +++ b/pkg/iac/terraform/block.go @@ -85,7 +85,7 @@ func NewBlock(hclBlock *hcl.Block, ctx *context.Context, moduleBlock *Block, par } b := Block{ - id: uuid.New().String(), + id: uuid.NewString(), context: ctx, hclBlock: hclBlock, moduleBlock: moduleBlock, @@ -446,6 +446,9 @@ func (b *Block) Attributes() map[string]*Attribute { func (b *Block) Values() cty.Value { values := createPresetValues(b) for _, attribute := range b.GetAttributes() { + if attribute.Name() == "for_each" { + continue + } values[attribute.Name()] = attribute.Value() } return cty.ObjectVal(postProcessValues(b, values)) diff --git a/pkg/iac/terraform/context/context.go b/pkg/iac/terraform/context/context.go index 0f4a58de9ac9..14e29a9a8378 100644 --- a/pkg/iac/terraform/context/context.go +++ b/pkg/iac/terraform/context/context.go @@ -46,17 +46,29 @@ func (c *Context) Get(parts ...string) cty.Value { if len(parts) == 0 { return cty.NilVal } - src := c.ctx.Variables - for i, part := range parts { - if i == len(parts)-1 { - return src[part] + + curr := c.ctx.Variables[parts[0]] + if len(parts) == 1 { + return curr + } + + for i, part := range parts[1:] { + if !curr.Type().HasAttribute(part) { + return cty.NilVal + } + + attr := curr.GetAttr(part) + + if i == len(parts)-2 { // iteration from the first element + return attr } - nextPart := src[part] - if nextPart == cty.NilVal { + + if !(attr.IsKnown() && attr.Type().IsObjectType()) { return cty.NilVal } - src = nextPart.AsValueMap() + curr = attr } + return cty.NilVal } @@ -97,13 +109,12 @@ func mergeVars(src cty.Value, parts []string, value cty.Value) cty.Value { } data := make(map[string]cty.Value) - if src.Type().IsObjectType() && !src.IsNull() && src.LengthInt() > 0 { + if isNotEmptyObject(src) { data = src.AsValueMap() - tmp, ok := src.AsValueMap()[parts[0]] - if !ok { - src = cty.ObjectVal(make(map[string]cty.Value)) + if attr, ok := data[parts[0]]; ok { + src = attr } else { - src = tmp + src = cty.EmptyObjectVal } } @@ -118,14 +129,16 @@ func mergeObjects(a, b cty.Value) cty.Value { for key, val := range a.AsValueMap() { output[key] = val } - for key, val := range b.AsValueMap() { - old, exists := output[key] - if exists && isNotEmptyObject(old) && isNotEmptyObject(val) { - output[key] = mergeObjects(old, val) + b.ForEachElement(func(key, val cty.Value) (stop bool) { + k := key.AsString() + old := output[k] + if old.IsKnown() && isNotEmptyObject(old) && isNotEmptyObject(val) { + output[k] = mergeObjects(old, val) } else { - output[key] = val + output[k] = val } - } + return false + }) return cty.ObjectVal(output) } diff --git a/pkg/iac/terraform/context/context_test.go b/pkg/iac/terraform/context/context_test.go index 8185d7b9892d..dfd8e05e5fac 100644 --- a/pkg/iac/terraform/context/context_test.go +++ b/pkg/iac/terraform/context/context_test.go @@ -52,6 +52,43 @@ func Test_ContextVariablesPreservation(t *testing.T) { } +func Test_SetWithMerge(t *testing.T) { + hctx := hcl.EvalContext{ + Variables: map[string]cty.Value{ + "my": cty.ObjectVal(map[string]cty.Value{ + "someValue": cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("test"), + "bar": cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("test"), + }), + }), + }), + }, + } + + ctx := NewContext(&hctx, nil) + + val := cty.ObjectVal(map[string]cty.Value{ + "foo2": cty.StringVal("test2"), + "bar": cty.ObjectVal(map[string]cty.Value{ + "foo2": cty.StringVal("test2"), + }), + }) + + ctx.Set(val, "my", "someValue") + got := ctx.Get("my", "someValue") + expected := cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("test"), + "foo2": cty.StringVal("test2"), + "bar": cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("test"), + "foo2": cty.StringVal("test2"), + }), + }) + + assert.Equal(t, expected, got) +} + func Test_ContextVariablesPreservationByDot(t *testing.T) { underlying := &hcl.EvalContext{} From 59c154144e4f33402e97039779c221c0e0bc296f Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Fri, 9 Aug 2024 13:13:30 +0700 Subject: [PATCH 286/352] refactor: replace ftypes.Gradle with packageurl.TypeGradle (#7323) Signed-off-by: nikpivkin --- pkg/purl/purl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index ba19d40c26a9..d9bff7b11e15 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -84,7 +84,7 @@ func New(t ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) (*Pac var qs packageurl.Qualifiers name, namespace, qs = parseApk(name, metadata.OS) qualifiers = append(qualifiers, qs...) - case packageurl.TypeMaven, string(ftypes.Gradle): // TODO: replace with packageurl.TypeGradle once they add it. + case packageurl.TypeMaven, packageurl.TypeGradle: namespace, name = parseMaven(name) case packageurl.TypePyPi: name = parsePyPI(name) From 08cc14bd2171afdc1973c6d614dd0d1fb82b7623 Mon Sep 17 00:00:00 2001 From: Itay Shakury Date: Fri, 9 Aug 2024 09:30:53 +0300 Subject: [PATCH 287/352] docs: update air-gapped docs (#7160) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- docs/docs/advanced/air-gap.md | 228 ++++++++++-------- docs/docs/compliance/contrib-compliance.md | 2 +- docs/docs/references/troubleshooting.md | 6 +- .../scanner/misconfiguration/check/builtin.md | 25 +- .../scanner/misconfiguration/custom/data.md | 22 +- .../scanner/misconfiguration/custom/index.md | 8 +- .../scanner/misconfiguration/custom/schema.md | 2 +- docs/docs/scanner/misconfiguration/index.md | 21 +- docs/docs/scanner/vulnerability.md | 43 +--- .../misconfiguration/custom-checks.md | 2 +- mkdocs.yml | 2 +- 11 files changed, 178 insertions(+), 183 deletions(-) diff --git a/docs/docs/advanced/air-gap.md b/docs/docs/advanced/air-gap.md index 171b80249eac..cf1df06f1bfe 100644 --- a/docs/docs/advanced/air-gap.md +++ b/docs/docs/advanced/air-gap.md @@ -1,142 +1,162 @@ -# Air-Gapped Environment +# Advanced Network Scenarios -Trivy can be used in air-gapped environments. Note that an allowlist is [here][allowlist]. +Trivy needs to connect to the internet occasionally in order to download relevant content. This document explains the network connectivity requirements of Trivy and setting up Trivy in particular scenarios. -## Air-Gapped Environment for vulnerabilities +## Network requirements -### Download the vulnerability database -At first, you need to download the vulnerability database for use in air-gapped environments. +Trivy's databases are distributed as OCI images via GitHub Container registry (GHCR): -=== "Trivy" +- +- +- - ``` - TRIVY_TEMP_DIR=$(mktemp -d) - trivy --cache-dir $TRIVY_TEMP_DIR image --download-db-only - tar -cf ./db.tar.gz -C $TRIVY_TEMP_DIR/db metadata.json trivy.db - rm -rf $TRIVY_TEMP_DIR - ``` +The following hosts are required in order to fetch them: -=== "oras >= v0.13.0" - Please follow [oras installation instruction][oras]. +- `ghcr.io` +- `pkg-containers.githubusercontent.com` - Download `db.tar.gz`: +The databases are pulled by Trivy using the [OCI Distribution](https://github.com/opencontainers/distribution-spec) specification, which is a simple HTTPS-based protocol. - ``` - $ oras pull ghcr.io/aquasecurity/trivy-db:2 - ``` +[VEX Hub](https://github.com/aquasecurity/vexhub) is distributed from GitHub over HTTPS. +The following hosts are required in order to fetch it: -=== "oras < v0.13.0" - Please follow [oras installation instruction][oras]. +- `api.github.com` +- `codeload.github.com` - Download `db.tar.gz`: +## Running Trivy in air-gapped environment - ``` - $ oras pull -a ghcr.io/aquasecurity/trivy-db:2 - ``` +An air-gapped environment refers to situations where the network connectivity from the machine Trivy runs on is blocked or restricted. -### Download the Java index database[^1] -Java users also need to download the Java index database for use in air-gapped environments. +In an air-gapped environment it is your responsibility to update the Trivy databases on a regular basis. -!!! note - You container image may contain JAR files even though you don't use Java directly. - In that case, you also need to download the Java index database. +## Offline Mode -=== "Trivy" +By default, Trivy will attempt to download latest databases. If it fails, the scan might fail. To avoid this behavior, you can tell Trivy to not attempt to download database files: - ``` - TRIVY_TEMP_DIR=$(mktemp -d) - trivy --cache-dir $TRIVY_TEMP_DIR image --download-java-db-only - tar -cf ./javadb.tar.gz -C $TRIVY_TEMP_DIR/java-db metadata.json trivy-java.db - rm -rf $TRIVY_TEMP_DIR - ``` -=== "oras >= v0.13.0" - Please follow [oras installation instruction][oras]. +- `--skip-db-update` to skip updating the main vulnerability database. +- `--skip-java-db-update` to skip updating the Java vulnerability database. +- `--skip-check-update` to skip updating the misconfiguration database. - Download `javadb.tar.gz`: +```shell +trivy image --skip-db-update --skip-java-db-update --offline-scan --skip-check-update myimage +``` + +## Self-Hosting - ``` - $ oras pull ghcr.io/aquasecurity/trivy-java-db:1 - ``` +### OCI Databases -=== "oras < v0.13.0" - Please follow [oras installation instruction][oras]. +You can host the databases on your own local OCI registry. - Download `javadb.tar.gz`: +First, make a copy of the databases in a container registry that is accessible to Trivy. The databases are in: - ``` - $ oras pull -a ghcr.io/aquasecurity/trivy-java-db:1 - ``` +- `ghcr.io/aquasecurity/trivy-db:2` +- `ghcr.io/aquasecurity/trivy-java-db:1` +- `ghcr.io/aquasecurity/trivy-checks:0` +Then, tell Trivy to use the local registry: -### Transfer the DB files into the air-gapped environment -The way of transfer depends on the environment. +```shell +trivy image \ + --db-repository myregistry.local/trivy-db \ + --java-db-repository myregistry.local/trivy-java-db \ + --checks-bundle-repository myregistry.local/trivy-checks \ + myimage +``` -=== "Vulnerability db" - ``` - $ rsync -av -e ssh /path/to/db.tar.gz [user]@[host]:dst - ``` +#### Authentication -=== "Java index db[^1]" - ``` - $ rsync -av -e ssh /path/to/javadb.tar.gz [user]@[host]:dst - ``` +If the registry requires authentication, you can configure it as described in the [private registry authentication document](../advanced/private-registries/index.md). -### Put the DB files in Trivy's cache directory -You have to know where to put the DB files. The following command shows the default cache directory. +### VEX Hub +You can host a copy of VEX Hub on your own internal server. + +First, make a copy of VEX Hub in a location that is accessible to Trivy. + +1. Download the [VEX Hub](https://github.com/aquasecurity/vexhub) archive from: . +1. Download the [VEX Hub Repository Manifest](https://github.com/aquasecurity/vex-repo-spec#2-repository-manifest) file from: . +1. Create or identify an internal HTTP server that can serve the VEX Hub repository in your environment (e.g `https://server.local`). +1. Make the downloaded archive file available for serving from your server (e.g `https://server.local/main.zip`). +1. Modify the downloaded manifest file's [Location URL](https://github.com/aquasecurity/vex-repo-spec?tab=readme-ov-file#locations-subfields) field to the URL of the archive file on your server (e.g `url: https://server.local/main.zip`). +1. Make the manifest file available for serving from your server under the `/.well-known` path (e.g `https://server.local/.well-known/vex-repository.json`). + +Then, tell Trivy to use the local VEX Repository: + +1. Locate your [Trivy VEX configuration file](../supply-chain/vex/repo/#configuration-file) by running `trivy vex repo init`. Make the following changes to the file. +1. Disable the default VEX Hub repo (`enabled: false`) +1. Add your internal VEX Hub repository as a [custom repository](../supply-chain/vex/repo/#custom-repositories) with the URL pointing to your local server (e.g `url: https://server.local`). + +#### Authentication + +If your server requires authentication, you can configure it as described in the [VEX Repository Authentication document](../supply-chain/vex/repo/#authentication). + +## Manual cache population + +You can also download the databases files manually and surgically populate the Trivy cache directory with them. + +### Downloading the DB files + +On a machine with internet access, pull the database container archive from the public registry into your local workspace: + +Note that these examples operate in the current working directory. + +=== "Using ORAS" +This example uses [ORAS](https://oras.land), but you can use any other container registry manipulation tool. + +```shell +oras pull ghcr.io/aquasecurity/trivy-db:2 ``` -$ ssh user@host -$ trivy -h | grep cache - --cache-dir value cache directory (default: "/home/myuser/.cache/trivy") [$TRIVY_CACHE_DIR] -``` -=== "Vulnerability db" - Put the DB file in the cache directory + `/db`. - - ``` - $ mkdir -p /home/myuser/.cache/trivy/db - $ cd /home/myuser/.cache/trivy/db - $ tar xvf /path/to/db.tar.gz -C /home/myuser/.cache/trivy/db - x trivy.db - x metadata.json - $ rm /path/to/db.tar.gz - ``` - -=== "Java index db[^1]" - Put the DB file in the cache directory + `/java-db`. - - ``` - $ mkdir -p /home/myuser/.cache/trivy/java-db - $ cd /home/myuser/.cache/trivy/java-db - $ tar xvf /path/to/javadb.tar.gz -C /home/myuser/.cache/trivy/java-db - x trivy-java.db - x metadata.json - $ rm /path/to/javadb.tar.gz - ``` - - - -In an air-gapped environment it is your responsibility to update the Trivy databases on a regular basis, so that the scanner can detect recently-identified vulnerabilities. - -### Run Trivy with the specific flags. -In an air-gapped environment, you have to specify `--skip-db-update` and `--skip-java-db-update`[^1] so that Trivy doesn't attempt to download the latest database files. -In addition, if you want to scan `pom.xml` dependencies, you need to specify `--offline-scan` since Trivy tries to issue API requests for scanning Java applications by default. +You should now have a file called `db.tar.gz`. Next, extract it to reveal the db files: + +```shell +tar -xzf db.tar.gz ``` -$ trivy image --skip-db-update --skip-java-db-update --offline-scan alpine:3.12 + +You should now have 2 new files, `metadata.json` and `trivy.db`. These are the Trivy DB files. + +=== "Using Trivy" +This example uses Trivy to pull the database container archive. The `--cache-dir` flag makes Trivy download the database files into our current working directory. The `--download-db-only` flag tells Trivy to only download the database files, not to scan any images. + +```shell +trivy image --cache-dir . --download-db-only ``` -## Air-Gapped Environment for misconfigurations +You should now have 2 new files, `metadata.json` and `trivy.db`. These are the Trivy DB files, copy them over to the air-gapped environment. -No special measures are required to detect misconfigurations in an air-gapped environment. +### Populating the Trivy Cache -### Run Trivy with `--skip-check-update` option -In an air-gapped environment, specify `--skip-check-update` so that Trivy doesn't attempt to download the latest misconfiguration checks. +In order to populate the cache, you need to identify the location of the cache directory. If it is under the default location, you can run the following command to find it: +```shell +trivy -h | grep cache ``` -$ trivy conf --skip-policy-update /path/to/conf + +For the example, we will assume the `TRIVY_CACHE_DIR` variable holds the cache location: + +```shell +TRIVY_CACHE_DIR=/home/user/.cache/trivy ``` -[allowlist]: ../references/troubleshooting.md -[oras]: https://oras.land/docs/installation +Put the Trivy DB files in the Trivy cache directory under a `db` subdirectory: + +```shell +# ensure cache db directory exists +mkdir -p ${TRIVY_CACHE_DIR}/db +# copy the db files +cp /path/to/trivy.db /path/to/metadata.json ${TRIVY_CACHE_DIR}/db/ +``` + +### Java DB + +For Java DB the process is the same, except for the following: + +1. Image location is `ghcr.io/aquasecurity/trivy-java-db:1` +2. Archive file name is `javadb.tar.gz` +3. DB file name is `trivy-java.db` + +## Misconfigurations scanning + +Note that the misconfigurations checks bundle is also embedded in the Trivy binary (at build time), and will be used as a fallback if the external database is not available. This means that you can still scan for misconfigurations in an air-gapped environment using the Checks from the time of the Trivy release you are using. -[^1]: This is only required to scan `jar` files. More information about `Java index db` [here](../coverage/language/java.md) +The misconfiguration scanner can be configured to load checks from a local directory, using the `--config-check` flag. In an air-gapped scenario you can copy the checks library from [Trivy checks repository](https://github.com/aquasecurity/trivy-checks) into a local directory, and load it with this flag. See more in the [Misconfiguration scanner documentation](../scanner/misconfiguration/index.md). diff --git a/docs/docs/compliance/contrib-compliance.md b/docs/docs/compliance/contrib-compliance.md index a848b1bf4367..0b83b688b664 100644 --- a/docs/docs/compliance/contrib-compliance.md +++ b/docs/docs/compliance/contrib-compliance.md @@ -35,7 +35,7 @@ Additional information is provided below. #### 1. Referencing a check that is already part of Trivy -Trivy has a comprehensive list of checks as part of its misconfiguration scanning. These can be found in the `trivy-policies/checks` directory ([Link](https://github.com/aquasecurity/trivy-checks/tree/main/checks)). If the check is present, the `AVD_ID` and other information from the check has to be used. +Trivy has a comprehensive list of checks as part of its misconfiguration scanning. These can be found in the `trivy-checks/checks` directory ([Link](https://github.com/aquasecurity/trivy-checks/tree/main/checks)). If the check is present, the `AVD_ID` and other information from the check has to be used. Note: Take a look at the more generic compliance specs that are already available in Trivy. If you are adding new compliance spec to Kubernetes e.g. AWS EKS CIS Benchmarks, chances are high that the check you would like to add to the new spec has already been defined in the general `k8s-ci-v.000.yaml` compliance spec. The same applies for creating specific Cloud Provider Compliance Specs and the [generic compliance specs](https://github.com/aquasecurity/trivy-checks/tree/main/specs/compliance) available. diff --git a/docs/docs/references/troubleshooting.md b/docs/docs/references/troubleshooting.md index d271882c5ecb..2c9a74a0e89a 100644 --- a/docs/docs/references/troubleshooting.md +++ b/docs/docs/references/troubleshooting.md @@ -203,10 +203,7 @@ Trivy v0.23.0 or later requires Trivy DB v2. Please update your local database o !!! error FATAL failed to download vulnerability DB -If trivy is running behind corporate firewall, you have to add the following urls to your allowlist. - -- ghcr.io -- pkg-containers.githubusercontent.com +If Trivy is running behind corporate firewall, refer to the necessary connectivity requirements as described [here][network]. ### Denied @@ -271,4 +268,5 @@ $ trivy clean --all ``` [air-gapped]: ../advanced/air-gap.md +[network]: ../advanced/air-gap.md#network-requirements [redis-cache]: ../../vulnerability/examples/cache/#cache-backend diff --git a/docs/docs/scanner/misconfiguration/check/builtin.md b/docs/docs/scanner/misconfiguration/check/builtin.md index 8b513f47607a..c4ca18e79006 100644 --- a/docs/docs/scanner/misconfiguration/check/builtin.md +++ b/docs/docs/scanner/misconfiguration/check/builtin.md @@ -1,21 +1,20 @@ # Built-in Checks -## Check Sources -Built-in checks are mainly written in [Rego][rego] and Go. -Those checks are managed under [trivy-checks repository][trivy-checks]. +## Checks Sources +Trivy has an extensive library of misconfiguration checks that is maintained at . +Trivy checks are mainly written in [Rego][rego], while some checks are written in Go. See [here](../../../coverage/iac/index.md) for the list of supported config types. -For suggestions or issues regarding policy content, please open an issue under the [trivy-checks][trivy-checks] repository. +## Checks Bundle +When performing a misconfiguration scan, Trivy will automatically download the relevant Checks bundle. The bundle is cached locally and Trivy will reuse it for subsequent scans on the same machine. Trivy takes care of updating the cache automatically, so normally users can be oblivious to it. -## Check Distribution -Trivy checks are distributed as an OPA bundle on [GitHub Container Registry][ghcr] (GHCR). -When misconfiguration detection is enabled, Trivy pulls the OPA bundle from GHCR as an OCI artifact and stores it in the cache. -Those checks are then loaded into Trivy OPA engine and used for detecting misconfigurations. -If Trivy is unable to pull down newer checks, it will use the embedded set of checks as a fallback. This is also the case in air-gap environments where `--skip-policy-update` might be passed. - -## Update Interval +## Checks Distribution +Trivy checks are distributed as an [OPA bundle](opa-bundle) hosted in the following GitHub Container Registry: . Trivy checks for updates to OPA bundle on GHCR every 24 hours and pulls it if there are any updates. +### External connectivity +Trivy needs to connect to the internet to download the bundle. If you are running Trivy in an air-gapped environment, or an tightly controlled network, please refer to the [Advanced Network Scenarios document](../../../advanced/air-gap.md). +The Checks bundle is also embedded in the Trivy binary (at build time), and will be used as a fallback if Trivy is unable to download the bundle. This means that you can still scan for misconfigurations in an air-gapped environment using the Checks from the time of the Trivy release you are using. + [rego]: https://www.openpolicyagent.org/docs/latest/policy-language/ -[trivy-checks]: https://github.com/aquasecurity/trivy-checks -[ghcr]: https://github.com/aquasecurity/trivy-checks/pkgs/container/trivy-checks \ No newline at end of file +[opa-bundle]: https://www.openpolicyagent.org/docs/latest/management-bundles/ diff --git a/docs/docs/scanner/misconfiguration/custom/data.md b/docs/docs/scanner/misconfiguration/custom/data.md index 51af206b4c63..2ccd9f1a2bfd 100644 --- a/docs/docs/scanner/misconfiguration/custom/data.md +++ b/docs/docs/scanner/misconfiguration/custom/data.md @@ -1,15 +1,14 @@ # Custom Data -Custom checks may require additional data in order to determine an answer. +Custom checks may require additional data in order to make a resolution. You can pass arbitrary data files to Trivy to be used when evaluating rego checks using the `--data` flag. +Trivy recursively searches the specified data paths for JSON (`*.json`) and YAML (`*.yaml`) files. -For example, an allowed list of resources that can be created. -Instead of hardcoding this information inside your policy, Trivy allows passing paths to data files with the `--data` flag. +For example, consider an allowed list of resources that can be created. +Instead of hardcoding this information inside your policy, you can maintain the list in a separate file. -Given the following yaml file: +Example data file: -```bash -$ cd examples/misconf/custom-data -$ cat data/ports.yaml [~/src/github.com/aquasecurity/trivy/examples/misconf/custom-data] +```yaml services: ports: - "20" @@ -19,7 +18,7 @@ services: - "23/tcp" ``` -This can be imported into your policy: +Example usage in a Rego check: ```rego import data.services @@ -27,9 +26,8 @@ import data.services ports := services.ports ``` -Then, you need to pass data paths through `--data` option. -Trivy recursively searches the specified paths for JSON (`*.json`) and YAML (`*.yaml`) files. +Example loading the data file: ```bash -$ trivy conf --policy ./policy --data data --namespaces user ./configs -``` \ No newline at end of file +trivy config --config-check ./checks --data ./data --namespaces user ./configs +``` diff --git a/docs/docs/scanner/misconfiguration/custom/index.md b/docs/docs/scanner/misconfiguration/custom/index.md index 925b72cedf09..7f471d873e8b 100644 --- a/docs/docs/scanner/misconfiguration/custom/index.md +++ b/docs/docs/scanner/misconfiguration/custom/index.md @@ -2,10 +2,10 @@ ## Overview You can write custom checks in [Rego][rego]. -Once you finish writing custom checks, you can pass the policy files or the directory where those policies are stored with `--policy` option. +Once you finish writing custom checks, you can pass the check files or the directory where those checks are stored with --config-check` option. ``` bash -trivy conf --policy /path/to/policy.rego --policy /path/to/custom_policies --namespaces user /path/to/config_dir +trivy conf --config-check /path/to/policy.rego --config-check /path/to/custom_checks --namespaces user /path/to/config_dir ``` As for `--namespaces` option, the detail is described as below. @@ -93,7 +93,7 @@ By default, only `builtin.*` packages will be evaluated. If you define custom packages, you have to specify the package prefix via `--namespaces` option. By default, Trivy only runs in its own namespace, unless specified by the user. Note that the custom namespace does not have to be `user` as in this example. It could be anything user-defined. ``` bash -trivy conf --policy /path/to/custom_policies --namespaces user /path/to/config_dir +trivy conf --config-check /path/to/custom_checks --namespaces user /path/to/config_dir ``` In this case, `user.*` will be evaluated. @@ -135,7 +135,7 @@ correct and do not reference incorrect properties/values. #### custom.avd_id and custom.id -The AVD_ID can be used to link the check to the Aqua Vulnerability Database (AVD) entry. For example, the `avd_id` `AVD-AWS-0176` is the ID of the check in the [AWS Vulnerability Database](https://avd.aquasec.com/). If you are [contributing your check to trivy-policies](../../../../community/contribute/checks/overview.md), you need to generate an ID using `make id` in the [trivy-checks](https://github.com/aquasecurity/trivy-checks) repository. The output of the command will provide you the next free IDs for the different providers in Trivy. +The AVD_ID can be used to link the check to the Aqua Vulnerability Database (AVD) entry. For example, the `avd_id` `AVD-AWS-0176` is the ID of the check in the [AWS Vulnerability Database](https://avd.aquasec.com/). If you are [contributing your check to trivy-checks](../../../../community/contribute/checks/overview.md), you need to generate an ID using `make id` in the [trivy-checks](https://github.com/aquasecurity/trivy-checks) repository. The output of the command will provide you the next free IDs for the different providers in Trivy. The ID is based on the AVD_ID. For instance if the `avd_id` is `AVD-AWS-0176`, the ID is `ID0176`. diff --git a/docs/docs/scanner/misconfiguration/custom/schema.md b/docs/docs/scanner/misconfiguration/custom/schema.md index 612025d38866..7b305cc10dbf 100644 --- a/docs/docs/scanner/misconfiguration/custom/schema.md +++ b/docs/docs/scanner/misconfiguration/custom/schema.md @@ -1,7 +1,7 @@ # Input Schema ## Overview -Policies can be defined with custom schemas that allow inputs to be verified against them. Adding a policy schema +Checks can be defined with custom schemas that allow inputs to be verified against them. Adding a policy schema enables Trivy to show more detailed error messages when an invalid input is encountered. In Trivy we have been able to define a schema for a [Dockerfile](https://github.com/aquasecurity/trivy/tree/main/pkg/iac/rego/schemas) diff --git a/docs/docs/scanner/misconfiguration/index.md b/docs/docs/scanner/misconfiguration/index.md index 701d469d658f..0726e7312417 100644 --- a/docs/docs/scanner/misconfiguration/index.md +++ b/docs/docs/scanner/misconfiguration/index.md @@ -315,6 +315,9 @@ Failures: 2 (MEDIUM: 2, HIGH: 0, CRITICAL: 0) This section describes misconfiguration-specific configuration. Other common options are documented [here](../../configuration/index.md). +### External connectivity +Trivy needs to connect to the internet to download the checks bundle. If you are running Trivy in an air-gapped environment, or an tightly controlled network, please refer to the [Advanced Network Scenarios document](../../advanced/air-gap.md). + ### Enabling a subset of misconfiguration scanners It's possible to only enable certain misconfiguration scanners if you prefer. You can do so by passing the `--misconfig-scanners` option. @@ -326,19 +329,19 @@ trivy config --misconfig-scanners=terraform,dockerfile . Will only scan for misconfigurations that pertain to Terraform and Dockerfiles. -### Passing custom checks -You can pass policy files or directories including your custom checks through `--policy` option. +### Loading custom checks +You can load check files or directories including your custom checks using the `--config-check` flag. This can be repeated for specifying multiple files or directories. ```bash -cd examplex/misconf/ -trivy conf --policy custom-policy/policy --policy combine/policy --policy policy.rego --namespaces user misconf/mixed +trivy conf --config-check custom-policy/policy --config-check combine/policy --config-check policy.rego --namespaces user myapp ``` -For more details, see [Custom Checks](./custom/index.md). +You can load checks bundle as OCI Image from a Container Registry using the `--checks-bundle-repository` flag. -!!! tip -You also need to specify `--namespaces` option. +```bash +trivy conf --checks-bundle-repository myregistry.local/mychecks --namespaces user myapp +``` ### Passing custom data You can pass directories including your custom data through `--data` option. @@ -346,7 +349,7 @@ This can be repeated for specifying multiple directories. ```bash cd examples/misconf/custom-data -trivy conf --policy ./policy --data ./data --namespaces user ./configs +trivy conf --config-check ./policy --data ./data --namespaces user ./configs ``` For more details, see [Custom Data](./custom/data.md). @@ -357,7 +360,7 @@ If you want to evaluate custom checks in other packages, you have to specify pac This can be repeated for specifying multiple packages. ``` bash -trivy conf --policy ./policy --namespaces main --namespaces user ./configs +trivy conf --config-check ./policy --namespaces main --namespaces user ./configs ``` ### Private terraform registries diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md index 00a9594ead62..ba5d0014d6bf 100644 --- a/docs/docs/scanner/vulnerability.md +++ b/docs/docs/scanner/vulnerability.md @@ -158,45 +158,22 @@ Trivy can detect vulnerabilities in Kubernetes clusters and components by scanni [^1]: Some manual triage and correction has been made. -## Database -Trivy downloads [the vulnerability database](https://github.com/aquasecurity/trivy-db) every 6 hours. -Trivy uses two types of databases for vulnerability detection: - -- Vulnerability Database -- Java Index Database - -This page provides detailed information about these databases. - -### Vulnerability Database -Trivy utilizes a database containing vulnerability information. -This database is built every six hours on [GitHub](https://github.com/aquasecurity/trivy-db) and is distributed via [GitHub Container registry (GHCR)](https://ghcr.io/aquasecurity/trivy-db). -The database is cached and updated as needed. -As Trivy updates the database automatically during execution, users don't need to be concerned about it. +## Databases +Trivy utilizes several databases containing information relevant for vulnerability scanning. +When performing a vulnerability scan, Trivy will automatically downloads the relevant databases. The databases are cached locally and Trivy will reuse them for subsequent scans on the same machine. Trivy takes care of updating the databases cache automatically, so normally users can be oblivious to it. For CLI flags related to the database, please refer to [this page](../configuration/db.md). -#### Private Hosting -If you host the database on your own OCI registry, you can specify a different repository with the `--db-repository` flag. -The default is `ghcr.io/aquasecurity/trivy-db`. - -```shell -$ trivy image --db-repository YOUR_REPO YOUR_IMAGE -``` - -If authentication is required, it can be configured in the same way as for private images. -Please refer to [the documentation](../advanced/private-registries/index.md) for more details. +### Vulnerability Database +This is Trivy's main database which contains vulnerability information, as collected from the datasources mentioned above. +It is built every six hours on [GitHub](https://github.com/aquasecurity/trivy-db). ### Java Index Database -This database is only downloaded when scanning JAR files so that Trivy can identify the groupId, artifactId, and version of JAR files. -It is built once a day on [GitHub](https://github.com/aquasecurity/trivy-java-db) and distributed via [GitHub Container registry (GHCR)](https://ghcr.io/aquasecurity/trivy-java-db). -Like the vulnerability database, it is automatically downloaded and updated when needed, so users don't need to worry about it. - -#### Private Hosting -If you host the database on your own OCI registry, you can specify a different repository with the `--java-db-repository` flag. -The default is `ghcr.io/aquasecurity/trivy-java-db`. +When scanning JAR files, Trivy relies on a dedicated database for identifying the groupId, artifactId, and version of the scanned JAR files. This database is only used when scanning JAR files, however your scanned artifacts might contain JAR files that you're not aware of. +This database is built once a day on [GitHub](https://github.com/aquasecurity/trivy-java-db). -If authentication is required, you need to run `docker login YOUR_REGISTRY`. -Currently, specifying a username and password is not supported. +### External connectivity +Trivy needs to connect to the internet to download the databases. If you are running Trivy in an air-gapped environment, or an tightly controlled network, please refer to the [Advanced Network Scenarios document](../advanced/air-gap.md). ## Detection Behavior Trivy prioritizes precision in vulnerability detection, aiming to minimize false positives while potentially accepting some false negatives. diff --git a/docs/tutorials/misconfiguration/custom-checks.md b/docs/tutorials/misconfiguration/custom-checks.md index 97ab67a7f649..0058595c0dde 100644 --- a/docs/tutorials/misconfiguration/custom-checks.md +++ b/docs/tutorials/misconfiguration/custom-checks.md @@ -93,7 +93,7 @@ Note that Rego Ensure that you have Trivy installed and run the following command: ```bash -trivy fs --scanners misconf --policy ./docker-check.rego --namespaces custom ./Dockerfile +trivy fs --scanners misconf --config-check ./docker-check.rego --namespaces custom ./Dockerfile ``` Please replace: diff --git a/mkdocs.yml b/mkdocs.yml index 9585c8341f94..60ed74306674 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -141,7 +141,7 @@ nav: - Developer guide: docs/plugin/developer-guide.md - Advanced: - Modules: docs/advanced/modules.md - - Air-Gapped Environment: docs/advanced/air-gap.md + - Advanced Network Scenarios: docs/advanced/air-gap.md - Container Image: - Embed in Dockerfile: docs/advanced/container/embed-in-dockerfile.md - Unpacked container image filesystem: docs/advanced/container/unpacked-filesystem.md From ee339b5ed714b8b9edb52444a42ff5350ac3bc97 Mon Sep 17 00:00:00 2001 From: simar7 <1254783+simar7@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:40:25 -0600 Subject: [PATCH 288/352] docs(misconf): Update callsites to use correct naming (#7335) --- docs/docs/configuration/filtering.md | 2 +- docs/docs/coverage/iac/cloudformation.md | 2 +- docs/docs/coverage/iac/helm.md | 6 +++--- docs/docs/coverage/iac/terraform.md | 8 ++++---- docs/docs/scanner/misconfiguration/custom/debug.md | 2 +- docs/docs/scanner/misconfiguration/custom/index.md | 4 ++-- docs/docs/scanner/misconfiguration/index.md | 10 +++++----- docs/tutorials/misconfiguration/terraform.md | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/docs/configuration/filtering.md b/docs/docs/configuration/filtering.md index abe8e84ff7e3..030813bd1bce 100644 --- a/docs/docs/configuration/filtering.md +++ b/docs/docs/configuration/filtering.md @@ -101,7 +101,7 @@ Total: 1785 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 1680, CRITICAL: 105) ```bash -trivy conf --severity HIGH,CRITICAL examples/misconf/mixed +trivy config --severity HIGH,CRITICAL examples/misconf/mixed ```
diff --git a/docs/docs/coverage/iac/cloudformation.md b/docs/docs/coverage/iac/cloudformation.md index 5665e1e77acd..7faea9769d7d 100644 --- a/docs/docs/coverage/iac/cloudformation.md +++ b/docs/docs/coverage/iac/cloudformation.md @@ -21,7 +21,7 @@ It evaluates properties, functions, and other elements within CloudFormation fil You can provide `cf-params` with path to [CloudFormation Parameters] file to Trivy to scan your CloudFormation code with parameters. ```bash -trivy conf --cf-params params.json ./infrastructure/cf +trivy config --cf-params params.json ./infrastructure/cf ``` You can check a [CloudFormation Parameters Example] diff --git a/docs/docs/coverage/iac/helm.md b/docs/docs/coverage/iac/helm.md index 8d0352fc42f1..0c213e1fd901 100644 --- a/docs/docs/coverage/iac/helm.md +++ b/docs/docs/coverage/iac/helm.md @@ -21,7 +21,7 @@ When override values are passed to the Helm scanner, the values will be used dur Overrides can be set inline on the command line ```bash -trivy conf --helm-set securityContext.runAsUser=0 ./charts/mySql +trivy config --helm-set securityContext.runAsUser=0 ./charts/mySql ``` #### Setting value file overrides @@ -35,7 +35,7 @@ securityContext: ``` ```bash -trivy conf --helm-values overrides.yaml ./charts/mySql +trivy config --helm-values overrides.yaml ./charts/mySql ``` #### Setting value as explicit string @@ -49,7 +49,7 @@ trivy config --helm-set-string name=false ./infrastructure/tf Specific override values can come from specific files ```bash -trivy conf --helm-set-file environment=dev.values.yaml ./charts/mySql +trivy config --helm-set-file environment=dev.values.yaml ./charts/mySql ``` ## Secret diff --git a/docs/docs/coverage/iac/terraform.md b/docs/docs/coverage/iac/terraform.md index e190c901cf05..55f1936cb040 100644 --- a/docs/docs/coverage/iac/terraform.md +++ b/docs/docs/coverage/iac/terraform.md @@ -18,13 +18,13 @@ It supports the following formats: Trivy can scan Terraform Plan files (snapshots) or their JSON representations. To create a Terraform Plan and scan it, run the following command: ```bash terraform plan --out tfplan -trivy conf tfplan +trivy config tfplan ``` To scan a Terraform Plan representation in JSON format, run the following command: ```bash terraform show -json tfplan > tfplan.json -trivy conf tfplan.json +trivy config tfplan.json ``` ## Misconfiguration @@ -35,7 +35,7 @@ It also evaluates variables, imports, and other elements within Terraform files You can provide `tf-vars` files to Trivy to override default values specified in the Terraform HCL code. ```bash -trivy conf --tf-vars dev.terraform.tfvars ./infrastructure/tf +trivy config --tf-vars dev.terraform.tfvars ./infrastructure/tf ``` ### Exclude Downloaded Terraform Modules @@ -43,7 +43,7 @@ By default, downloaded modules are also scanned. If you don't want to scan them, you can use the `--tf-exclude-downloaded-modules` flag. ```bash -trivy conf --tf-exclude-downloaded-modules ./configs +trivy config --tf-exclude-downloaded-modules ./configs ``` ## Secret diff --git a/docs/docs/scanner/misconfiguration/custom/debug.md b/docs/docs/scanner/misconfiguration/custom/debug.md index 751e43633efc..54ec9fd65273 100644 --- a/docs/docs/scanner/misconfiguration/custom/debug.md +++ b/docs/docs/scanner/misconfiguration/custom/debug.md @@ -7,7 +7,7 @@ This will output a large trace from Open Policy Agent like the following: Only failed checks show traces. If you want to debug a passed check, you need to make it fail on purpose. ```shell -$ trivy conf --trace configs/ +$ trivy config --trace configs/ 2022-05-16T13:47:58.853+0100 INFO Detected config files: 1 Dockerfile (dockerfile) diff --git a/docs/docs/scanner/misconfiguration/custom/index.md b/docs/docs/scanner/misconfiguration/custom/index.md index 7f471d873e8b..9598089b8562 100644 --- a/docs/docs/scanner/misconfiguration/custom/index.md +++ b/docs/docs/scanner/misconfiguration/custom/index.md @@ -5,7 +5,7 @@ You can write custom checks in [Rego][rego]. Once you finish writing custom checks, you can pass the check files or the directory where those checks are stored with --config-check` option. ``` bash -trivy conf --config-check /path/to/policy.rego --config-check /path/to/custom_checks --namespaces user /path/to/config_dir +trivy config --config-check /path/to/policy.rego --config-check /path/to/custom_checks --namespaces user /path/to/config_dir ``` As for `--namespaces` option, the detail is described as below. @@ -93,7 +93,7 @@ By default, only `builtin.*` packages will be evaluated. If you define custom packages, you have to specify the package prefix via `--namespaces` option. By default, Trivy only runs in its own namespace, unless specified by the user. Note that the custom namespace does not have to be `user` as in this example. It could be anything user-defined. ``` bash -trivy conf --config-check /path/to/custom_checks --namespaces user /path/to/config_dir +trivy config --config-check /path/to/custom_checks --namespaces user /path/to/config_dir ``` In this case, `user.*` will be evaluated. diff --git a/docs/docs/scanner/misconfiguration/index.md b/docs/docs/scanner/misconfiguration/index.md index 0726e7312417..eae768456029 100644 --- a/docs/docs/scanner/misconfiguration/index.md +++ b/docs/docs/scanner/misconfiguration/index.md @@ -101,7 +101,7 @@ For example, the following example holds IaC files for Terraform, CloudFormation ``` bash $ ls iac/ Dockerfile deployment.yaml main.tf mysql-8.8.26.tar -$ trivy conf --severity HIGH,CRITICAL ./iac +$ trivy config --severity HIGH,CRITICAL ./iac ```
@@ -334,13 +334,13 @@ You can load check files or directories including your custom checks using the ` This can be repeated for specifying multiple files or directories. ```bash -trivy conf --config-check custom-policy/policy --config-check combine/policy --config-check policy.rego --namespaces user myapp +trivy config --config-check custom-policy/policy --config-check combine/policy --config-check policy.rego --namespaces user myapp ``` You can load checks bundle as OCI Image from a Container Registry using the `--checks-bundle-repository` flag. ```bash -trivy conf --checks-bundle-repository myregistry.local/mychecks --namespaces user myapp +trivy config --checks-bundle-repository myregistry.local/mychecks --namespaces user myapp ``` ### Passing custom data @@ -349,7 +349,7 @@ This can be repeated for specifying multiple directories. ```bash cd examples/misconf/custom-data -trivy conf --config-check ./policy --data ./data --namespaces user ./configs +trivy config --config-check ./my-check --data ./data --namespaces user ./configs ``` For more details, see [Custom Data](./custom/data.md). @@ -360,7 +360,7 @@ If you want to evaluate custom checks in other packages, you have to specify pac This can be repeated for specifying multiple packages. ``` bash -trivy conf --config-check ./policy --namespaces main --namespaces user ./configs +trivy config --config-check ./my-check --namespaces main --namespaces user ./configs ``` ### Private terraform registries diff --git a/docs/tutorials/misconfiguration/terraform.md b/docs/tutorials/misconfiguration/terraform.md index 24b8eebfa69a..c51f2edfe181 100644 --- a/docs/tutorials/misconfiguration/terraform.md +++ b/docs/tutorials/misconfiguration/terraform.md @@ -86,7 +86,7 @@ trivy config --severity CRITICAL, MEDIUM terraform-infra You can pass terraform values to Trivy to override default values found in the Terraform HCL code. More information are provided [in the documentation.](https://aquasecurity.github.io/trivy/latest/docs/coverage/iac/terraform/#value-overrides) ``` -trivy conf --tf-vars terraform.tfvars ./ +trivy config --tf-vars terraform.tfvars ./ ``` ### Custom Checks From 0047dbf361eaea3d5b09f3e29102eb53450e20c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:54:11 +0400 Subject: [PATCH 289/352] chore(deps): bump the common group with 9 updates (#7333) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 22 +++++++++++----------- go.sum | 44 ++++++++++++++++++++++---------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index ac6f232878bc..31315bc0f550 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.4 require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 github.com/BurntSushi/toml v1.4.0 github.com/CycloneDX/cyclonedx-go v0.9.0 @@ -51,7 +51,7 @@ require ( github.com/go-openapi/strfmt v0.23.0 github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/go-containerregistry v0.20.1 + github.com/google/go-containerregistry v0.20.2 github.com/google/go-github/v62 v62.0.0 github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/uuid v1.6.0 @@ -88,7 +88,7 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 github.com/moby/buildkit v0.15.1 - github.com/open-policy-agent/opa v0.67.0 + github.com/open-policy-agent/opa v0.67.1 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/openvex/discovery v0.1.0 @@ -103,7 +103,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/sosedoff/gitkit v0.4.0 github.com/spdx/tools-golang v0.5.5 // v0.5.3 with necessary changes. Can be upgraded to version 0.5.4 after release. - github.com/spf13/cast v1.6.0 + github.com/spf13/cast v1.7.0 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 @@ -117,13 +117,13 @@ require ( github.com/zclconf/go-cty v1.15.0 github.com/zclconf/go-cty-yaml v1.0.3 go.etcd.io/bbolt v1.3.10 - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.26.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/mod v0.20.0 - golang.org/x/net v0.27.0 + golang.org/x/net v0.28.0 golang.org/x/sync v0.8.0 - golang.org/x/term v0.22.0 - golang.org/x/text v0.16.0 + golang.org/x/term v0.23.0 + golang.org/x/text v0.17.0 golang.org/x/vuln v1.1.3 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/protobuf v1.34.2 @@ -131,7 +131,7 @@ require ( helm.sh/helm/v3 v3.15.3 k8s.io/api v0.30.3 k8s.io/utils v0.0.0-20231127182322-b307cd553661 - modernc.org/sqlite v1.31.1 + modernc.org/sqlite v1.32.0 sigs.k8s.io/yaml v1.4.0 ) @@ -207,7 +207,7 @@ require ( github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect - github.com/docker/cli v27.0.3+incompatible // indirect + github.com/docker/cli v27.1.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect @@ -376,7 +376,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/sys v0.23.0 // indirect golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.23.0 // indirect diff --git a/go.sum b/go.sum index a24b98453b2c..c34a319faf06 100644 --- a/go.sum +++ b/go.sum @@ -203,8 +203,8 @@ github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= @@ -542,8 +542,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/docker/cli v27.0.3+incompatible h1:usGs0/BoBW8MWxGeEtqPMkzOY56jZ6kYlSN5BLDioCQ= -github.com/docker/cli v27.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= +github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= @@ -782,8 +782,8 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0= -github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo= +github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8= github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg= github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA= github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= @@ -1143,8 +1143,8 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= -github.com/open-policy-agent/opa v0.67.0 h1:FOdsO9yNhfmrh+72oVK7ImWmzruG+VSpfbr5IBqEWVs= -github.com/open-policy-agent/opa v0.67.0/go.mod h1:aqKlHc8E2VAAylYE9x09zJYr/fYzGX+JKne89UGqFzk= +github.com/open-policy-agent/opa v0.67.1 h1:rzy26J6g1X+CKknAcx0Vfbt41KqjuSzx4E0A8DAZf3E= +github.com/open-policy-agent/opa v0.67.1/go.mod h1:aqKlHc8E2VAAylYE9x09zJYr/fYzGX+JKne89UGqFzk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -1296,8 +1296,8 @@ github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYec github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -1470,8 +1470,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1574,8 +1574,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1713,8 +1713,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 h1:FemxDzfMUcK2f3YY4H+05K9CDzbSVr2+q/JKN45pey0= golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1726,8 +1726,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1742,8 +1742,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2127,8 +2127,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= -modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= From aadb09078843250c66087f46db9a2aa48094a118 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 15 Aug 2024 20:32:50 +0600 Subject: [PATCH 290/352] fix(misconf): change default TLS values for the Azure storage account (#7345) Signed-off-by: nikpivkin --- pkg/iac/adapters/arm/storage/adapt.go | 2 +- pkg/iac/adapters/arm/storage/adapt_test.go | 2 +- pkg/iac/adapters/terraform/azure/storage/adapt.go | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/iac/adapters/arm/storage/adapt.go b/pkg/iac/adapters/arm/storage/adapt.go index 1b10ebbe9ad8..018949e24e10 100644 --- a/pkg/iac/adapters/arm/storage/adapt.go +++ b/pkg/iac/adapters/arm/storage/adapt.go @@ -59,7 +59,7 @@ func adaptAccounts(deployment azure.Deployment) []storage.Account { Metadata: resource.Properties.GetMetadata(), EnableLogging: types.BoolDefault(false, resource.Properties.GetMetadata()), }, - MinimumTLSVersion: resource.Properties.GetMapValue("minimumTlsVersion").AsStringValue("TLS1_0", resource.Properties.GetMetadata()), + MinimumTLSVersion: resource.Properties.GetMapValue("minimumTlsVersion").AsStringValue("", resource.Properties.GetMetadata()), Queues: queues, } accounts = append(accounts, account) diff --git a/pkg/iac/adapters/arm/storage/adapt_test.go b/pkg/iac/adapters/arm/storage/adapt_test.go index d1e124e2449e..f4fd81f47ad2 100644 --- a/pkg/iac/adapters/arm/storage/adapt_test.go +++ b/pkg/iac/adapters/arm/storage/adapt_test.go @@ -26,7 +26,7 @@ func Test_AdaptStorageDefaults(t *testing.T) { require.Len(t, output.Accounts, 1) account := output.Accounts[0] - assert.Equal(t, "TLS1_0", account.MinimumTLSVersion.Value()) + assert.Equal(t, "", account.MinimumTLSVersion.Value()) assert.False(t, account.EnforceHTTPS.Value()) } diff --git a/pkg/iac/adapters/terraform/azure/storage/adapt.go b/pkg/iac/adapters/terraform/azure/storage/adapt.go index edc5f0029be7..6a51cf1fca2b 100644 --- a/pkg/iac/adapters/terraform/azure/storage/adapt.go +++ b/pkg/iac/adapters/terraform/azure/storage/adapt.go @@ -6,6 +6,8 @@ import ( iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) +const minimumTlsVersionOneTwo = "TLS1_2" + func Adapt(modules terraform.Modules) storage.Storage { accounts, containers, networkRules := adaptAccounts(modules) @@ -106,7 +108,7 @@ func adaptAccount(resource *terraform.Block) storage.Account { Metadata: resource.GetMetadata(), EnableLogging: iacTypes.BoolDefault(false, resource.GetMetadata()), }, - MinimumTLSVersion: iacTypes.StringDefault("TLS1_2", resource.GetMetadata()), + MinimumTLSVersion: iacTypes.StringDefault(minimumTlsVersionOneTwo, resource.GetMetadata()), } networkRulesBlocks := resource.GetBlocks("network_rules") @@ -127,7 +129,7 @@ func adaptAccount(resource *terraform.Block) storage.Account { } minTLSVersionAttr := resource.GetAttribute("min_tls_version") - account.MinimumTLSVersion = minTLSVersionAttr.AsStringValueOrDefault("TLS1_0", resource) + account.MinimumTLSVersion = minTLSVersionAttr.AsStringValueOrDefault(minimumTlsVersionOneTwo, resource) return account } From 0c6687d5ba0df5de2632aad26c21ea99565bd9e1 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Fri, 16 Aug 2024 04:58:27 +0600 Subject: [PATCH 291/352] refactor(misconf): highlight only affected rows (#7310) Signed-off-by: nikpivkin --- .../helm_testchart.overridden.json.golden | 2 +- pkg/iac/scan/code.go | 315 ++++++++++-------- pkg/iac/scan/code_test.go | 288 +++++++++++----- pkg/iac/scan/highlighting.go | 12 +- pkg/iac/types/range.go | 7 + 5 files changed, 396 insertions(+), 228 deletions(-) diff --git a/integration/testdata/helm_testchart.overridden.json.golden b/integration/testdata/helm_testchart.overridden.json.golden index 1b1ada2cd9a3..f4c984daa458 100644 --- a/integration/testdata/helm_testchart.overridden.json.golden +++ b/integration/testdata/helm_testchart.overridden.json.golden @@ -527,7 +527,7 @@ "IsCause": true, "Annotation": "", "Truncated": false, - "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsUser\u001b[0m: \u001b[38;5;37m0", + "Highlighted": "\u001b[0m \u001b[38;5;33mrunAsUser\u001b[0m: \u001b[38;5;37m0\u001b[0m", "FirstCause": false, "LastCause": true } diff --git a/pkg/iac/scan/code.go b/pkg/iac/scan/code.go index e61b547988c6..8388dd4dbf6e 100644 --- a/pkg/iac/scan/code.go +++ b/pkg/iac/scan/code.go @@ -2,7 +2,6 @@ package scan import ( "bufio" - "bytes" "fmt" "io/fs" "path/filepath" @@ -15,6 +14,44 @@ type Code struct { Lines []Line } +func (c *Code) truncateLines(maxLines int) { + previouslyTruncated := maxLines-1 > 0 && c.Lines[maxLines-2].Truncated + if maxLines-1 > 0 && c.Lines[maxLines-1].LastCause { + c.Lines[maxLines-2].LastCause = true + } + c.Lines[maxLines-1] = Line{ + Truncated: true, + Number: c.Lines[maxLines-1].Number, + } + if previouslyTruncated { + c.Lines = c.Lines[:maxLines-1] + } else { + c.Lines = c.Lines[:maxLines] + } +} + +func (c *Code) markFirstAndLastCauses() { + var isFirst bool + var isLast bool + + for i, line := range c.Lines { + if line.IsCause && !isFirst { + c.Lines[i].FirstCause = true + isFirst = true + } + + if isFirst && !line.IsCause && i > 0 { + c.Lines[i-1].LastCause = true + isLast = true + break + } + } + + if !isLast && len(c.Lines) > 0 { + c.Lines[len(c.Lines)-1].LastCause = true + } +} + type Line struct { Number int `json:"Number"` Content string `json:"Content"` @@ -96,199 +133,187 @@ func OptionCodeWithHighlighted(include bool) CodeOption { } } -func validateRange(r iacTypes.Range) error { - if r.GetStartLine() < 0 || r.GetStartLine() > r.GetEndLine() || r.GetEndLine() < 0 { - return fmt.Errorf("invalid range: %s", r.String()) - } - return nil -} - -// nolint func (r *Result) GetCode(opts ...CodeOption) (*Code, error) { - settings := defaultCodeSettings for _, opt := range opts { opt(&settings) } - srcFS := r.Metadata().Range().GetFS() - if srcFS == nil { + fsys := r.Metadata().Range().GetFS() + if fsys == nil { return nil, fmt.Errorf("code unavailable: result was not mapped to a known filesystem") } - innerRange := r.Range() - outerRange := innerRange - metadata := r.Metadata() - for { - if parent := metadata.Parent(); parent != nil && - parent.Range().GetFilename() == metadata.Range().GetFilename() && - parent.Range().GetStartLine() > 0 { - outerRange = parent.Range() - metadata = *parent - continue - } - break + innerRange := r.metadata.Range() + if err := innerRange.Validate(); err != nil { + return nil, err } - if err := validateRange(innerRange); err != nil { - return nil, err + if innerRange.GetStartLine() == 0 { + return nil, fmt.Errorf("inner range has invalid start line: %s", innerRange.String()) } - if err := validateRange(outerRange); err != nil { + + outerRange := r.getOuterRange() + if err := outerRange.Validate(); err != nil { return nil, err } - slashed := filepath.ToSlash(r.fsPath) - slashed = strings.TrimPrefix(slashed, "/") - - content, err := fs.ReadFile(srcFS, slashed) + filePath := strings.TrimPrefix(filepath.ToSlash(r.fsPath), "/") + rawLines, err := readLinesFromFile(fsys, filePath, outerRange.GetStartLine(), outerRange.GetEndLine()) if err != nil { - return nil, fmt.Errorf("failed to read file from result filesystem (%#v): %w", srcFS, err) + return nil, err } - hasAnnotation := r.Annotation() != "" - - code := Code{ - Lines: nil, + if outerRange.GetEndLine()-outerRange.GetStartLine() > len(rawLines) { + return nil, fmt.Errorf("invalid outer range: %s", outerRange.String()) } - var rawLines []string - bs := bufio.NewScanner(bytes.NewReader(content)) - for bs.Scan() { - rawLines = append(rawLines, bs.Text()) - } - if bs.Err() != nil { - return nil, fmt.Errorf("failed to scan file : %w", err) - } + highlightedLines := r.getHighlightedLines(outerRange, innerRange, rawLines, settings) - var highlightedLines []string - if settings.includeHighlighted { - highlightedLines = highlight(iacTypes.CreateFSKey(innerRange.GetFS()), innerRange.GetLocalFilename(), content, settings.theme) - if len(highlightedLines) < len(rawLines) { - highlightedLines = rawLines - } + var code Code + + shrink := settings.allowTruncation && outerRange.LineCount() > (innerRange.LineCount()+10) + + if shrink { + code.Lines = r.getTruncatedLines(outerRange, innerRange, rawLines, highlightedLines) } else { - highlightedLines = make([]string, len(rawLines)) + code.Lines = r.getAllLines(outerRange, innerRange, rawLines, highlightedLines) } - if outerRange.GetEndLine()-1 >= len(rawLines) || innerRange.GetStartLine() == 0 { - return nil, fmt.Errorf("invalid line number") + if settings.allowTruncation && len(code.Lines) > settings.maxLines && settings.maxLines > 0 { + code.truncateLines(settings.maxLines) } - shrink := settings.allowTruncation && outerRange.LineCount() > (innerRange.LineCount()+10) + code.markFirstAndLastCauses() - if shrink { + return &code, nil +} - if outerRange.GetStartLine() < innerRange.GetStartLine() { - code.Lines = append( - code.Lines, - Line{ - Content: rawLines[outerRange.GetStartLine()-1], - Highlighted: highlightedLines[outerRange.GetStartLine()-1], - Number: outerRange.GetStartLine(), - }, - ) - if outerRange.GetStartLine()+1 < innerRange.GetStartLine() { - code.Lines = append( - code.Lines, - Line{ - Truncated: true, - Number: outerRange.GetStartLine() + 1, - }, - ) - } - } +func (r *Result) getHighlightedLines(outerRange, innerRange iacTypes.Range, rawLines []string, settings codeSettings) []string { - for lineNo := innerRange.GetStartLine(); lineNo <= innerRange.GetEndLine(); lineNo++ { + highlightedLines := make([]string, len(rawLines)) + if !settings.includeHighlighted { + return highlightedLines + } - if lineNo-1 >= len(rawLines) || lineNo-1 >= len(highlightedLines) { - break - } + content := strings.Join(rawLines, "\n") + fsKey := iacTypes.CreateFSKey(innerRange.GetFS()) + highlightedLines = highlight(fsKey, innerRange.GetLocalFilename(), + outerRange.GetStartLine(), outerRange.GetEndLine(), content, settings.theme) - line := Line{ - Number: lineNo, - Content: strings.TrimSuffix(rawLines[lineNo-1], "\r"), - Highlighted: strings.TrimSuffix(highlightedLines[lineNo-1], "\r"), - IsCause: true, - } + if len(highlightedLines) < len(rawLines) { + return rawLines + } - if hasAnnotation && lineNo == innerRange.GetStartLine() { - line.Annotation = r.Annotation() - } + return highlightedLines +} - code.Lines = append(code.Lines, line) - } +func (r *Result) getOuterRange() iacTypes.Range { + outer := r.Metadata().Range() + for parent := r.Metadata().Parent(); parent != nil && + parent.Range().GetFilename() == outer.GetFilename() && + parent.Range().GetStartLine() > 0; parent = parent.Parent() { + outer = parent.Range() + } + return outer +} - if outerRange.GetEndLine() > innerRange.GetEndLine() { - if outerRange.GetEndLine() > innerRange.GetEndLine()+1 { - code.Lines = append( - code.Lines, - Line{ - Truncated: true, - Number: outerRange.GetEndLine() - 1, - }, - ) - } - code.Lines = append( - code.Lines, - Line{ - Content: rawLines[outerRange.GetEndLine()-1], - Highlighted: highlightedLines[outerRange.GetEndLine()-1], - Number: outerRange.GetEndLine(), - }, - ) +func (r *Result) getTruncatedLines(outerRange, innerRange iacTypes.Range, rawLines, highlightedLines []string) []Line { + var lines []Line + + if outerRange.GetStartLine() < innerRange.GetStartLine() { + lines = append(lines, Line{ + Content: rawLines[0], + Highlighted: highlightedLines[0], + Number: outerRange.GetStartLine(), + }) + if outerRange.GetStartLine()+1 < innerRange.GetStartLine() { + lines = append(lines, Line{ + Truncated: true, + Number: outerRange.GetStartLine() + 1, + }) + } + } + for lineNo := innerRange.GetStartLine() - outerRange.GetStartLine(); lineNo <= innerRange.GetEndLine()-outerRange.GetStartLine(); lineNo++ { + if lineNo >= len(rawLines) || lineNo >= len(highlightedLines) { + break } - } else { - for lineNo := outerRange.GetStartLine(); lineNo <= outerRange.GetEndLine(); lineNo++ { + line := Line{ + Number: lineNo + outerRange.GetStartLine(), + Content: strings.TrimSuffix(rawLines[lineNo], "\r"), + Highlighted: strings.TrimSuffix(highlightedLines[lineNo], "\r"), + IsCause: true, + } - line := Line{ - Number: lineNo, - Content: strings.TrimSuffix(rawLines[lineNo-1], "\r"), - Highlighted: strings.TrimSuffix(highlightedLines[lineNo-1], "\r"), - IsCause: lineNo >= innerRange.GetStartLine() && lineNo <= innerRange.GetEndLine(), - } + if r.Annotation() != "" && lineNo == innerRange.GetStartLine()-outerRange.GetStartLine()-1 { + line.Annotation = r.Annotation() + } - if hasAnnotation && lineNo == innerRange.GetStartLine() { - line.Annotation = r.Annotation() - } + lines = append(lines, line) + } - code.Lines = append(code.Lines, line) + if outerRange.GetEndLine() > innerRange.GetEndLine() { + if outerRange.GetEndLine() > innerRange.GetEndLine()+1 { + lines = append(lines, Line{ + Truncated: true, + Number: outerRange.GetEndLine() - 1, + }) } + lines = append(lines, Line{ + Content: rawLines[outerRange.GetEndLine()-outerRange.GetStartLine()], + Highlighted: highlightedLines[outerRange.GetEndLine()-outerRange.GetStartLine()], + Number: outerRange.GetEndLine(), + }) } - if settings.allowTruncation && len(code.Lines) > settings.maxLines && settings.maxLines > 0 { - previouslyTruncated := settings.maxLines-1 > 0 && code.Lines[settings.maxLines-2].Truncated - if settings.maxLines-1 > 0 && code.Lines[settings.maxLines-1].LastCause { - code.Lines[settings.maxLines-2].LastCause = true - } - code.Lines[settings.maxLines-1] = Line{ - Truncated: true, - Number: code.Lines[settings.maxLines-1].Number, + return lines +} + +func (r *Result) getAllLines(outerRange, innerRange iacTypes.Range, rawLines, highlightedLines []string) []Line { + lines := make([]Line, 0, outerRange.GetEndLine()-outerRange.GetStartLine()+1) + + for lineNo := 0; lineNo <= outerRange.GetEndLine()-outerRange.GetStartLine(); lineNo++ { + line := Line{ + Number: lineNo + outerRange.GetStartLine(), + Content: strings.TrimSuffix(rawLines[lineNo], "\r"), + Highlighted: strings.TrimSuffix(highlightedLines[lineNo], "\r"), + IsCause: lineNo >= innerRange.GetStartLine()-outerRange.GetStartLine() && + lineNo <= innerRange.GetEndLine()-outerRange.GetStartLine(), } - if previouslyTruncated { - code.Lines = code.Lines[:settings.maxLines-1] - } else { - code.Lines = code.Lines[:settings.maxLines] + + if r.Annotation() != "" && lineNo == innerRange.GetStartLine()-outerRange.GetStartLine()-1 { + line.Annotation = r.Annotation() } + + lines = append(lines, line) } - var first, last bool - for i, line := range code.Lines { - if line.IsCause && !first { - code.Lines[i].FirstCause = true - first = true - continue - } - if first && !line.IsCause && i > 0 { - code.Lines[i-1].LastCause = true - last = true - break + return lines +} + +func readLinesFromFile(fsys fs.FS, path string, from, to int) ([]string, error) { + slashedPath := strings.TrimPrefix(filepath.ToSlash(path), "/") + + file, err := fsys.Open(slashedPath) + if err != nil { + return nil, fmt.Errorf("failed to read file from result filesystem: %w", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + rawLines := make([]string, 0, to-from+1) + + for lineNum := 0; scanner.Scan() && lineNum < to; lineNum++ { + if lineNum >= from-1 { + rawLines = append(rawLines, scanner.Text()) } } - if !last && len(code.Lines) > 0 { - code.Lines[len(code.Lines)-1].LastCause = true + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to scan file: %w", err) } - return &code, nil + return rawLines, nil } diff --git a/pkg/iac/scan/code_test.go b/pkg/iac/scan/code_test.go index c3ffe3725ef1..559f1d9dfe69 100644 --- a/pkg/iac/scan/code_test.go +++ b/pkg/iac/scan/code_test.go @@ -1,11 +1,10 @@ package scan import ( - "os" "strings" "testing" + "testing/fstest" - "github.com/liamg/memoryfs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -13,19 +12,18 @@ import ( ) func TestResult_GetCode(t *testing.T) { - + const line = "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind." tests := []struct { - name string - source string - filename string - start int - end int - outerStart int - outerEnd int - expected []Line - options []CodeOption - wantErr bool - annotation string + name string + source string + filename string + startInnerLine int + endInnerLine int + startOuterLine int + endOuterLine int + expected []Line + options []CodeOption + wantErr bool }{ { name: "basic w/ defaults", @@ -33,9 +31,9 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "test.txt", - start: 2, - end: 3, + filename: "test.txt", + startInnerLine: 2, + endInnerLine: 3, expected: []Line{ { Number: 2, @@ -60,12 +58,12 @@ func TestResult_GetCode(t *testing.T) { source: `resource "aws_s3_bucket" "something" { bucket = "something" }`, - filename: "main.tf", - start: 2, - end: 2, - outerStart: 1, - outerEnd: 3, - options: []CodeOption{OptionCodeWithHighlighted(false)}, + filename: "main.tf", + startInnerLine: 2, + endInnerLine: 2, + startOuterLine: 1, + endOuterLine: 3, + options: []CodeOption{OptionCodeWithHighlighted(false)}, expected: []Line{ { Number: 1, @@ -90,10 +88,10 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "", - start: 2, - end: 3, - wantErr: true, + filename: "", + startInnerLine: 2, + endInnerLine: 3, + wantErr: true, }, { name: "no line numbers", @@ -101,10 +99,10 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "test.txt", - start: 0, - end: 0, - wantErr: true, + filename: "test.txt", + startInnerLine: 0, + endInnerLine: 0, + wantErr: true, }, { name: "negative line numbers", @@ -112,10 +110,10 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "test.txt", - start: -2, - end: -1, - wantErr: true, + filename: "test.txt", + startInnerLine: -2, + endInnerLine: -1, + wantErr: true, }, { name: "invalid line numbers", @@ -123,17 +121,17 @@ func TestResult_GetCode(t *testing.T) { 2 3 4`, - filename: "test.txt", - start: 5, - end: 6, - wantErr: true, + filename: "test.txt", + startInnerLine: 5, + endInnerLine: 6, + wantErr: true, }, { - name: "syntax highlighting", - source: `FROM ubuntu`, - filename: "Dockerfile", - start: 1, - end: 1, + name: "syntax highlighting", + source: `FROM ubuntu`, + filename: "Dockerfile", + startInnerLine: 1, + endInnerLine: 1, expected: []Line{ { Number: 1, @@ -146,81 +144,81 @@ func TestResult_GetCode(t *testing.T) { }, }, { - name: "truncation", - source: strings.Repeat("If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.\n", 100), - filename: "longfile.txt", - start: 1, - end: 100, + name: "truncation", + source: strings.Repeat(line+"\n", 100), + filename: "longfile.txt", + startInnerLine: 1, + endInnerLine: 100, expected: []Line{ { Number: 1, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: true, LastCause: false, }, { Number: 2, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 3, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 4, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 5, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 6, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 7, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 8, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: false, }, { Number: 9, - Content: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Content: line, IsCause: true, - Highlighted: "If you can do a half-assed job of anything, you're a one-eyed man in a kingdom of the blind.", + Highlighted: line, FirstCause: false, LastCause: true, }, @@ -230,26 +228,97 @@ func TestResult_GetCode(t *testing.T) { }, }, }, + { + name: "invalid inner range", + source: `Test`, + filename: "test.txt", + startInnerLine: 0, + endInnerLine: 0, + wantErr: true, + }, + { + name: "invalid outer range", + source: `Test`, + filename: "test.txt", + startInnerLine: 10, + endInnerLine: 12, + startOuterLine: 5, + endOuterLine: 3, + wantErr: true, + }, + { + name: "truncate with outer range", + source: strings.Repeat(line+"\n", 100), + filename: "longfile.txt", + startOuterLine: 1, + endOuterLine: 100, + startInnerLine: 10, + endInnerLine: 12, + options: []CodeOption{OptionCodeWithTruncation(true)}, + expected: []Line{ + { + Number: 1, + Content: line, + Highlighted: line, + }, + { + Number: 2, + Truncated: true, + }, + { + Number: 10, + Content: line, + IsCause: true, + FirstCause: true, + Highlighted: line, + }, + { + Number: 11, + Content: line, + IsCause: true, + Highlighted: line, + }, + { + Number: 12, + Content: line, + IsCause: true, + LastCause: true, + Highlighted: line, + }, + { + Number: 99, + Truncated: true, + }, + { + Number: 100, + Content: line, + Highlighted: line, + }, + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - system := memoryfs.New() - require.NoError(t, system.WriteFile(test.filename, []byte(test.source), os.ModePerm)) + fsys := fstest.MapFS{ + test.filename: &fstest.MapFile{ + Data: []byte(test.source), + }, + } + meta := iacTypes.NewMetadata( - iacTypes.NewRange(test.filename, test.start, test.end, "", system), + iacTypes.NewRange(test.filename, test.startInnerLine, test.endInnerLine, "", fsys), "", ) - if test.outerStart > 0 { + if test.startOuterLine > 0 { meta = meta.WithParent(iacTypes.NewMetadata( - iacTypes.NewRange(test.filename, test.outerStart, test.outerEnd, "", system), + iacTypes.NewRange(test.filename, test.startOuterLine, test.endOuterLine, "", fsys), "", )) } result := &Result{ - annotation: test.annotation, - metadata: meta, - fsPath: test.filename, + metadata: meta, + fsPath: test.filename, } code, err := result.GetCode(test.options...) if test.wantErr { @@ -262,3 +331,72 @@ func TestResult_GetCode(t *testing.T) { } } + +func TestCode_IsCauseMultiline(t *testing.T) { + + tests := []struct { + name string + code Code + expected bool + }{ + { + name: "no cause", + code: Code{ + Lines: []Line{ + { + Number: 1, + Content: "Test", + Highlighted: "Test", + }, + }, + }, + expected: false, + }, + { + name: "one cause", + code: Code{ + Lines: []Line{ + { + Number: 1, + Content: "Test", + IsCause: true, + Highlighted: "Test", + }, + }, + }, + expected: false, + }, + { + name: "multiple causes", + code: Code{ + Lines: []Line{ + { + Number: 1, + Content: "Test", + IsCause: true, + Highlighted: "Test", + }, + { + Number: 2, + Content: "Test", + IsCause: true, + Highlighted: "Test", + }, + { + Number: 3, + Content: "Test", + IsCause: true, + Highlighted: "Test", + }, + }, + }, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.code.IsCauseMultiline()) + }) + } +} diff --git a/pkg/iac/scan/highlighting.go b/pkg/iac/scan/highlighting.go index 12bd3fe086cf..df260130d599 100644 --- a/pkg/iac/scan/highlighting.go +++ b/pkg/iac/scan/highlighting.go @@ -34,9 +34,9 @@ var globalCache = &cache{ data: make(map[string][]string), } -func highlight(fsKey, filename string, input []byte, theme string) []string { +func highlight(fsKey, filename string, startLine, endLine int, input, theme string) []string { - key := fmt.Sprintf("%s|%s", fsKey, filename) + key := fmt.Sprintf("%s|%s|%d-%d", fsKey, filename, startLine, endLine) if lines, ok := globalCache.Get(key); ok { return lines } @@ -56,15 +56,13 @@ func highlight(fsKey, filename string, input []byte, theme string) []string { formatter = formatters.Fallback } - // replace windows line endings - input = bytes.ReplaceAll(input, []byte{0x0d}, []byte{}) - iterator, err := lexer.Tokenise(nil, string(input)) + iterator, err := lexer.Tokenise(nil, input) if err != nil { return nil } - buffer := bytes.NewBuffer([]byte{}) - if err := formatter.Format(buffer, style, iterator); err != nil { + var buffer bytes.Buffer + if err := formatter.Format(&buffer, style, iterator); err != nil { return nil } diff --git a/pkg/iac/types/range.go b/pkg/iac/types/range.go index c06a2bf6fe1a..754ee29c9675 100755 --- a/pkg/iac/types/range.go +++ b/pkg/iac/types/range.go @@ -146,3 +146,10 @@ func (r Range) GetFS() fs.FS { func (r Range) GetSourcePrefix() string { return r.sourcePrefix } + +func (r Range) Validate() error { + if r.startLine < 0 || r.endLine < 0 || r.startLine > r.endLine { + return fmt.Errorf("invalid range: %s", r.String()) + } + return nil +} From c5c62d5ff05420321f9cdbfb93e2591e0866a342 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 20 Aug 2024 10:48:57 +0600 Subject: [PATCH 292/352] fix(misconf): wrap Azure PortRange in iac types (#7357) Signed-off-by: nikpivkin --- pkg/iac/adapters/arm/network/adapt.go | 8 ++++---- pkg/iac/adapters/terraform/azure/network/adapt.go | 12 ++++++------ .../adapters/terraform/azure/network/adapt_test.go | 8 ++++---- pkg/iac/providers/azure/network/network.go | 6 +++--- pkg/iac/rego/schemas/cloud.json | 6 ++++-- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/pkg/iac/adapters/arm/network/adapt.go b/pkg/iac/adapters/arm/network/adapt.go index 5201b84761e7..214cf75c3c90 100644 --- a/pkg/iac/adapters/arm/network/adapt.go +++ b/pkg/iac/adapters/arm/network/adapt.go @@ -27,11 +27,11 @@ func adaptSecurityGroups(deployment azure.Deployment) (sgs []network.SecurityGro func adaptSecurityGroup(resource azure.Resource, deployment azure.Deployment) network.SecurityGroup { return network.SecurityGroup{ Metadata: resource.Metadata, - Rules: adaptSecurityGroupRules(resource, deployment), + Rules: adaptSecurityGroupRules(deployment), } } -func adaptSecurityGroupRules(resource azure.Resource, deployment azure.Deployment) (rules []network.SecurityGroupRule) { +func adaptSecurityGroupRules(deployment azure.Deployment) (rules []network.SecurityGroupRule) { for _, resource := range deployment.GetResourcesByType("Microsoft.Network/networkSecurityGroups/securityRules") { rules = append(rules, adaptSecurityGroupRule(resource)) } @@ -120,7 +120,7 @@ func expandRange(r string, m iacTypes.Metadata) network.PortRange { return network.PortRange{ Metadata: m, - Start: start, - End: end, + Start: iacTypes.Int(start, m), + End: iacTypes.Int(end, m), } } diff --git a/pkg/iac/adapters/terraform/azure/network/adapt.go b/pkg/iac/adapters/terraform/azure/network/adapt.go index b2866cd9100a..4bbcca6c5fd2 100644 --- a/pkg/iac/adapters/terraform/azure/network/adapt.go +++ b/pkg/iac/adapters/terraform/azure/network/adapt.go @@ -136,8 +136,8 @@ func (a *adapter) adaptSource(ruleBlock *terraform.Block, rule *network.Security f := sourcePortRangeAttr.AsNumber() rule.SourcePorts = append(rule.SourcePorts, network.PortRange{ Metadata: sourcePortRangeAttr.GetMetadata(), - Start: int(f), - End: int(f), + Start: iacTypes.Int(int(f), sourcePortRangeAttr.GetMetadata()), + End: iacTypes.Int(int(f), sourcePortRangeAttr.GetMetadata()), }) } } @@ -160,8 +160,8 @@ func (a *adapter) adaptDestination(ruleBlock *terraform.Block, rule *network.Sec f := destPortRangeAttr.AsNumber() rule.DestinationPorts = append(rule.DestinationPorts, network.PortRange{ Metadata: destPortRangeAttr.GetMetadata(), - Start: int(f), - End: int(f), + Start: iacTypes.Int(int(f), destPortRangeAttr.GetMetadata()), + End: iacTypes.Int(int(f), destPortRangeAttr.GetMetadata()), }) } } @@ -189,8 +189,8 @@ func expandRange(r string, m iacTypes.Metadata) network.PortRange { return network.PortRange{ Metadata: m, - Start: start, - End: end, + Start: iacTypes.Int(start, m), + End: iacTypes.Int(end, m), } } diff --git a/pkg/iac/adapters/terraform/azure/network/adapt_test.go b/pkg/iac/adapters/terraform/azure/network/adapt_test.go index 15b966b06ffc..99931b6b2d3e 100644 --- a/pkg/iac/adapters/terraform/azure/network/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/network/adapt_test.go @@ -65,15 +65,15 @@ func Test_Adapt(t *testing.T) { SourcePorts: []network.PortRange{ { Metadata: iacTypes.NewTestMetadata(), - Start: 0, - End: 65535, + Start: iacTypes.IntTest(0), + End: iacTypes.IntTest(65535), }, }, DestinationPorts: []network.PortRange{ { Metadata: iacTypes.NewTestMetadata(), - Start: 3389, - End: 3389, + Start: iacTypes.IntTest(3389), + End: iacTypes.IntTest(3389), }, }, Protocol: iacTypes.String("TCP", iacTypes.NewTestMetadata()), diff --git a/pkg/iac/providers/azure/network/network.go b/pkg/iac/providers/azure/network/network.go index 71c56b62b465..4fdc56e44e86 100755 --- a/pkg/iac/providers/azure/network/network.go +++ b/pkg/iac/providers/azure/network/network.go @@ -27,12 +27,12 @@ type SecurityGroupRule struct { type PortRange struct { Metadata iacTypes.Metadata - Start int - End int + Start iacTypes.IntValue + End iacTypes.IntValue } func (r PortRange) Includes(port int) bool { - return port >= r.Start && port <= r.End + return port >= r.Start.Value() && port <= r.End.Value() } type NetworkWatcherFlowLog struct { diff --git a/pkg/iac/rego/schemas/cloud.json b/pkg/iac/rego/schemas/cloud.json index a4bab9423d38..530ba5bfaa1f 100644 --- a/pkg/iac/rego/schemas/cloud.json +++ b/pkg/iac/rego/schemas/cloud.json @@ -5207,10 +5207,12 @@ "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata" }, "end": { - "type": "integer" + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" }, "start": { - "type": "integer" + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.IntValue" } } }, From efdbd8f19ab0ab0c3b48293d43e51c81b7b03b89 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 20 Aug 2024 10:55:45 +0600 Subject: [PATCH 293/352] feat(misconf): scanning support for YAML and JSON (#7311) Signed-off-by: nikpivkin --- docs/docs/coverage/iac/index.md | 3 + .../configuration/cli/trivy_config.md | 1 + .../configuration/cli/trivy_filesystem.md | 1 + .../configuration/cli/trivy_image.md | 1 + .../configuration/cli/trivy_kubernetes.md | 1 + .../configuration/cli/trivy_repository.md | 1 + .../configuration/cli/trivy_rootfs.md | 1 + .../references/configuration/cli/trivy_vm.md | 1 + .../references/configuration/config-file.md | 3 + docs/docs/scanner/misconfiguration/index.md | 63 ++- pkg/commands/artifact/run.go | 84 +-- pkg/fanal/analyzer/analyzer.go | 6 +- pkg/fanal/analyzer/config/all/import.go | 2 + .../analyzer/config/azurearm/azurearm.go | 4 +- .../config/cloudformation/cloudformation.go | 4 +- pkg/fanal/analyzer/config/config.go | 7 +- pkg/fanal/analyzer/config/config_test.go | 17 +- .../analyzer/config/dockerfile/docker.go | 4 +- pkg/fanal/analyzer/config/helm/helm.go | 4 +- pkg/fanal/analyzer/config/json/json.go | 36 ++ pkg/fanal/analyzer/config/k8s/k8s.go | 4 +- .../analyzer/config/terraform/terraform.go | 3 +- .../config/terraformplan/json/json.go | 4 +- .../config/terraformplan/snapshot/snapshot.go | 4 +- pkg/fanal/analyzer/config/yaml/yaml.go | 36 ++ pkg/fanal/analyzer/const.go | 4 + .../analyzer/imgconf/dockerfile/dockerfile.go | 3 +- pkg/fanal/artifact/image/image_test.go | 110 ++-- pkg/fanal/artifact/local/fs_test.go | 513 ++++++++++++++---- .../multiple-failures/rego/policy.rego | 25 +- .../azurearm/no-results/rego/policy.rego | 25 +- .../azurearm/passed/rego/policy.rego | 25 +- .../azurearm/single-failure/rego/policy.rego | 25 +- .../multiple-failures/rego/policy.rego | 25 +- .../no-results/rego/policy.rego | 25 +- .../params/code/rego/policy.rego | 4 +- .../cloudformation/passed/rego/policy.rego | 25 +- .../single-failure/rego/policy.rego | 25 +- .../misconfig/json/passed/checks/test.rego | 17 + .../misconfig/json/passed/src/test1.json | 3 + .../misconfig/json/passed/src/test2.json | 3 + .../json/with-schema/checks/test.rego | 17 + .../json/with-schema/schemas/test.json | 9 + .../misconfig/json/with-schema/src/test1.json | 3 + .../misconfig/json/with-schema/src/test2.json | 3 + .../multiple-failures/rego/policy.rego | 25 +- .../kubernetes/no-results/rego/policy.rego | 25 +- .../kubernetes/passed/rego/policy.rego | 25 +- .../single-failure/rego/policy.rego | 25 +- .../testdata/misconfig/mixed/rego/policy.rego | 25 +- .../misconfig/yaml/passed/checks/test.rego | 17 + .../misconfig/yaml/passed/src/test1.yaml | 1 + .../misconfig/yaml/passed/src/test2.yml | 1 + .../yaml/with-schema/checks/test.rego | 17 + .../yaml/with-schema/schemas/test.json | 9 + .../misconfig/yaml/with-schema/src/test1.yaml | 1 + .../misconfig/yaml/with-schema/src/test2.yml | 1 + pkg/fanal/artifact/repo/git_test.go | 4 +- pkg/fanal/types/const.go | 1 + pkg/flag/misconf_flags.go | 18 +- pkg/iac/detection/detect.go | 48 +- pkg/iac/detection/detect_test.go | 138 +++++ pkg/iac/rego/build.go | 10 +- pkg/iac/rego/embed.go | 2 +- pkg/iac/rego/load.go | 2 +- pkg/iac/rego/scanner.go | 6 + pkg/iac/rego/scanner_test.go | 70 +++ pkg/iac/scanners/azure/arm/scanner.go | 3 +- pkg/iac/scanners/cloudformation/scanner.go | 3 +- pkg/iac/scanners/dockerfile/scanner.go | 3 +- pkg/iac/scanners/helm/scanner.go | 3 +- pkg/iac/scanners/json/scanner.go | 3 +- pkg/iac/scanners/kubernetes/scanner.go | 3 +- pkg/iac/scanners/options/scanner.go | 7 + pkg/iac/scanners/terraform/scanner.go | 3 +- .../scanners/terraformplan/tfjson/scanner.go | 3 +- pkg/iac/scanners/toml/scanner.go | 3 +- pkg/iac/scanners/yaml/scanner.go | 3 +- pkg/misconf/config_schema.go | 74 +++ pkg/misconf/config_schema_test.go | 41 ++ pkg/misconf/scanner.go | 98 ++-- pkg/misconf/scanner_test.go | 26 +- pkg/misconf/testdata/schemas/no-schema.file | 1 + .../schemas/schema-with-bad-regex.json | 9 + pkg/misconf/testdata/schemas/schema1.json | 9 + pkg/misconf/testdata/schemas/schema2.json | 9 + 86 files changed, 1516 insertions(+), 443 deletions(-) create mode 100644 pkg/fanal/analyzer/config/json/json.go create mode 100644 pkg/fanal/analyzer/config/yaml/yaml.go create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/json/passed/checks/test.rego create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test1.json create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test2.json create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/checks/test.rego create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/schemas/test.json create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test1.json create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test2.json create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/checks/test.rego create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test1.yaml create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test2.yml create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/checks/test.rego create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/schemas/test.json create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test1.yaml create mode 100644 pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test2.yml create mode 100644 pkg/misconf/config_schema.go create mode 100644 pkg/misconf/config_schema_test.go create mode 100644 pkg/misconf/testdata/schemas/no-schema.file create mode 100644 pkg/misconf/testdata/schemas/schema-with-bad-regex.json create mode 100644 pkg/misconf/testdata/schemas/schema1.json create mode 100644 pkg/misconf/testdata/schemas/schema2.json diff --git a/docs/docs/coverage/iac/index.md b/docs/docs/coverage/iac/index.md index 168c3dd650fa..963e9b11b8c5 100644 --- a/docs/docs/coverage/iac/index.md +++ b/docs/docs/coverage/iac/index.md @@ -17,6 +17,9 @@ Trivy scans Infrastructure as Code (IaC) files for | [CloudFormation](cloudformation.md) | \*.yml, \*.yaml, \*.json | | [Azure ARM Template](azure-arm.md) | \*.json | | [Helm](helm.md) | \*.yaml, \*.tpl, \*.tar.gz, etc. | +| [YAML][json-and-yaml] | \*.yaml, \*.yml | +| [JSON][json-and-yaml] | \*.json | [misconf]: ../../scanner/misconfiguration/index.md [secret]: ../../scanner/secret.md +[json-and-yaml]: ../../scanner/misconfiguration/index.md#scan-arbitrary-json-and-yaml-configurations diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 0176c09ea58f..b32e74c1c752 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -17,6 +17,7 @@ trivy config [flags] DIR --compliance string compliance report to generate --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded + --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking --enable-modules strings [EXPERIMENTAL] module names to enable --exit-code int specify exit code when any security issues are found --file-patterns strings specify config file patterns diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 2202bc27f518..16c5909549e3 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -27,6 +27,7 @@ trivy filesystem [flags] PATH --compliance string compliance report to generate --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded + --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index a1de0595a7bc..bbd75e690cfc 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -41,6 +41,7 @@ trivy image [flags] IMAGE_NAME --compliance string compliance report to generate (docker-cis-1.6.0) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded + --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 4516509d5834..2bc84e905282 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -37,6 +37,7 @@ trivy kubernetes [flags] [CONTEXT] --compliance string compliance report to generate (k8s-nsa-1.0,k8s-cis-1.23,eks-cis-1.4,rke2-cis-1.24,k8s-pss-baseline-0.1,k8s-pss-restricted-0.1) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded + --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages --detection-priority string specify the detection priority: diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 5d4bc5ce4161..eeef161725a8 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -27,6 +27,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --commit string pass the commit hash to be scanned --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded + --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 60fdf4e623fa..88f5bd197779 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -29,6 +29,7 @@ trivy rootfs [flags] ROOTDIR --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded + --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 4bae981c7577..fef17624222d 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -25,6 +25,7 @@ trivy vm [flags] VM_IMAGE --cache-ttl duration cache TTL when using redis as cache backend --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "ghcr.io/aquasecurity/trivy-checks:0") --compliance string compliance report to generate + --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking --custom-headers strings custom headers in client mode --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db:2") --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index b3876d6ad225..f3edb6a8e2b6 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -386,6 +386,9 @@ misconfiguration: # Same as '--cf-params' params: [] + # Same as '--config-file-schemas' + config-file-schemas: [] + helm: # Same as '--helm-api-versions' api-versions: [] diff --git a/docs/docs/scanner/misconfiguration/index.md b/docs/docs/scanner/misconfiguration/index.md index eae768456029..7615f3342265 100644 --- a/docs/docs/scanner/misconfiguration/index.md +++ b/docs/docs/scanner/misconfiguration/index.md @@ -107,7 +107,7 @@ $ trivy config --severity HIGH,CRITICAL ./iac
Result -``` +```bash 2022-06-06T11:01:21.142+0100 INFO Detected config files: 8 Dockerfile (dockerfile) @@ -343,6 +343,61 @@ You can load checks bundle as OCI Image from a Container Registry using the `--c trivy config --checks-bundle-repository myregistry.local/mychecks --namespaces user myapp ``` + +### Scan arbitrary JSON and YAML configurations +By default, scanning JSON and YAML configurations is disabled, since Trivy does not contain built-in checks for these configurations. To enable it, pass the `json` or `yaml` to `--misconfig-scanners`. See [Enabling a subset of misconfiguration scanners](#enabling-a-subset-of-misconfiguration-scanners) for more information. Trivy will pass each file as is to the checks input. + + +!!! example +```bash +$ cat iac/serverless.yaml +service: serverless-rest-api-with-pynamodb + +frameworkVersion: ">=2.24.0" + +plugins: + - serverless-python-requirements +... + +$ cat serverless.rego +# METADATA +# title: Serverless Framework service name not starting with "aws-" +# description: Ensure that Serverless Framework service names start with "aws-" +# schemas: +# - input: schema["serverless-schema"] +# custom: +# id: SF001 +# severity: LOW +package user.serverless001 + +deny[res] { + not startswith(input.service, "aws-") + res := result.new( + sprintf("Service name %q is not allowed", [input.service]), + input.service + ) +} + +$ trivy config --misconfig-scanners=json,yaml --config-check ./serverless.rego --check-namespaces user ./iac +serverless.yaml (yaml) + +Tests: 4 (SUCCESSES: 3, FAILURES: 1, EXCEPTIONS: 0) +Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0) + +LOW: Service name "serverless-rest-api-with-pynamodb" is not allowed +═════════════════════════════════════════════════════════════════════════════════════════════════════════ +Ensure that Serverless Framework service names start with "aws-" +``` + +You can also pass schemas using the `config-file-schemas` flag. Trivy will use these schemas for file filtering and type checking in Rego checks. If the file does not match any of the passed schemas, it will be ignored. + +!!! example +```bash +$ trivy config --misconfig-scanners=json,yaml --config-check ./serverless.rego --check-namespaces user --config-file-schemas ./serverless-schema.json ./iac +``` + +If the schema is specified in the check metadata and is in the directory specified in the `--config-check` argument, it will be automatically loaded as specified [here](./custom/schema.md#custom-checks-with-custom-schemas), and will only be used for type checking in Rego. + ### Passing custom data You can pass directories including your custom data through `--data` option. This can be repeated for specifying multiple directories. @@ -363,12 +418,12 @@ This can be repeated for specifying multiple packages. trivy config --config-check ./my-check --namespaces main --namespaces user ./configs ``` -### Private terraform registries -Trivy can download terraform code from private registries. +### Private Terraform registries +Trivy can download Terraform code from private registries. To pass credentials you must use the `TF_TOKEN_` environment variables. You cannot use a `.terraformrc` or `terraform.rc` file, these are not supported by trivy yet. -From the terraform [docs](https://developer.hashicorp.com/terraform/cli/config/config-file#environment-variable-credentials): +From the Terraform [docs](https://developer.hashicorp.com/terraform/cli/config/config-file#environment-variable-credentials): > Environment variable names should have the prefix TF_TOKEN_ added to the domain name, with periods encoded as underscores. > For example, the value of a variable named `TF_TOKEN_app_terraform_io` will be used as a bearer authorization token when the CLI makes service requests to the hostname `app.terraform.io`. diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 3cd1b9af74b5..3edc54dc723c 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -501,43 +501,13 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan log.WithPrefix(log.PrefixVulnerability).Info("Vulnerability scanning is enabled") } - // ScannerOption is filled only when config scanning is enabled. + // Misconfig ScannerOption is filled only when config scanning is enabled. var configScannerOptions misconf.ScannerOption if opts.Scanners.Enabled(types.MisconfigScanner) || opts.ImageConfigScanners.Enabled(types.MisconfigScanner) { - logger := log.WithPrefix(log.PrefixMisconfiguration) - logger.Info("Misconfiguration scanning is enabled") - - var downloadedPolicyPaths []string - var disableEmbedded bool - - downloadedPolicyPaths, err := operation.InitBuiltinPolicies(context.Background(), opts.CacheDir, opts.Quiet, opts.SkipCheckUpdate, opts.MisconfOptions.ChecksBundleRepository, opts.RegistryOpts()) + var err error + configScannerOptions, err = initMisconfScannerOption(opts) if err != nil { - if !opts.SkipCheckUpdate { - logger.Error("Falling back to embedded checks", log.Err(err)) - } - } else { - logger.Debug("Policies successfully loaded from disk") - disableEmbedded = true - } - configScannerOptions = misconf.ScannerOption{ - Debug: opts.Debug, - Trace: opts.Trace, - Namespaces: append(opts.CheckNamespaces, rego.BuiltinNamespaces()...), - PolicyPaths: append(opts.CheckPaths, downloadedPolicyPaths...), - DataPaths: opts.DataPaths, - HelmValues: opts.HelmValues, - HelmValueFiles: opts.HelmValueFiles, - HelmFileValues: opts.HelmFileValues, - HelmStringValues: opts.HelmStringValues, - HelmAPIVersions: opts.HelmAPIVersions, - HelmKubeVersion: opts.HelmKubeVersion, - TerraformTFVars: opts.TerraformTFVars, - CloudFormationParamVars: opts.CloudFormationParamVars, - K8sVersion: opts.K8sVersion, - DisableEmbeddedPolicies: disableEmbedded, - DisableEmbeddedLibraries: disableEmbedded, - IncludeDeprecatedChecks: opts.IncludeDeprecatedChecks, - TfExcludeDownloaded: opts.TfExcludeDownloaded, + return ScannerConfig{}, types.ScanOptions{}, err } } @@ -650,3 +620,49 @@ func (r *runner) scan(ctx context.Context, opts flag.Options, initializeScanner } return report, nil } + +func initMisconfScannerOption(opts flag.Options) (misconf.ScannerOption, error) { + logger := log.WithPrefix(log.PrefixMisconfiguration) + logger.Info("Misconfiguration scanning is enabled") + + var downloadedPolicyPaths []string + var disableEmbedded bool + + downloadedPolicyPaths, err := operation.InitBuiltinPolicies(context.Background(), opts.CacheDir, opts.Quiet, opts.SkipCheckUpdate, opts.MisconfOptions.ChecksBundleRepository, opts.RegistryOpts()) + if err != nil { + if !opts.SkipCheckUpdate { + logger.Error("Falling back to embedded checks", log.Err(err)) + } + } else { + logger.Debug("Checks successfully loaded from disk") + disableEmbedded = true + } + + configSchemas, err := misconf.LoadConfigSchemas(opts.ConfigFileSchemas) + if err != nil { + return misconf.ScannerOption{}, xerrors.Errorf("load schemas error: %w", err) + } + + return misconf.ScannerOption{ + Debug: opts.Debug, + Trace: opts.Trace, + Namespaces: append(opts.CheckNamespaces, rego.BuiltinNamespaces()...), + PolicyPaths: append(opts.CheckPaths, downloadedPolicyPaths...), + DataPaths: opts.DataPaths, + HelmValues: opts.HelmValues, + HelmValueFiles: opts.HelmValueFiles, + HelmFileValues: opts.HelmFileValues, + HelmStringValues: opts.HelmStringValues, + HelmAPIVersions: opts.HelmAPIVersions, + HelmKubeVersion: opts.HelmKubeVersion, + TerraformTFVars: opts.TerraformTFVars, + CloudFormationParamVars: opts.CloudFormationParamVars, + K8sVersion: opts.K8sVersion, + DisableEmbeddedPolicies: disableEmbedded, + DisableEmbeddedLibraries: disableEmbedded, + IncludeDeprecatedChecks: opts.IncludeDeprecatedChecks, + TfExcludeDownloaded: opts.TfExcludeDownloaded, + FilePatterns: opts.FilePatterns, + ConfigFileSchemas: configSchemas, + }, nil +} diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go index b5bb8e629acf..abedb4b90638 100644 --- a/pkg/fanal/analyzer/analyzer.go +++ b/pkg/fanal/analyzer/analyzer.go @@ -214,7 +214,11 @@ func (r *AnalysisResult) Sort() { // Misconfigurations sort.Slice(r.Misconfigurations, func(i, j int) bool { - return r.Misconfigurations[i].FilePath < r.Misconfigurations[j].FilePath + if r.Misconfigurations[i].FileType != r.Misconfigurations[j].FileType { + return r.Misconfigurations[i].FileType < r.Misconfigurations[j].FileType + } else { + return r.Misconfigurations[i].FilePath < r.Misconfigurations[j].FilePath + } }) // Secrets diff --git a/pkg/fanal/analyzer/config/all/import.go b/pkg/fanal/analyzer/config/all/import.go index b171ab5e8a7f..74ba00ba49a8 100644 --- a/pkg/fanal/analyzer/config/all/import.go +++ b/pkg/fanal/analyzer/config/all/import.go @@ -5,8 +5,10 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/cloudformation" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/dockerfile" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/helm" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/json" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/k8s" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraform" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraformplan/json" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/terraformplan/snapshot" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/yaml" ) diff --git a/pkg/fanal/analyzer/config/azurearm/azurearm.go b/pkg/fanal/analyzer/config/azurearm/azurearm.go index 3c0c4b9828f1..ecd7826173a0 100644 --- a/pkg/fanal/analyzer/config/azurearm/azurearm.go +++ b/pkg/fanal/analyzer/config/azurearm/azurearm.go @@ -6,7 +6,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -25,7 +25,7 @@ type azureARMConfigAnalyzer struct { } func newAzureARMConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewAzureARMScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeAzureARM, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/cloudformation/cloudformation.go b/pkg/fanal/analyzer/config/cloudformation/cloudformation.go index 06f7e458a859..02f281cbbb8a 100644 --- a/pkg/fanal/analyzer/config/cloudformation/cloudformation.go +++ b/pkg/fanal/analyzer/config/cloudformation/cloudformation.go @@ -3,7 +3,7 @@ package cloudformation import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -22,7 +22,7 @@ type cloudFormationConfigAnalyzer struct { } func newCloudFormationConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewCloudFormationScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeCloudFormation, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/config.go b/pkg/fanal/analyzer/config/config.go index b5f3569d0ab4..020ae1ae3562 100644 --- a/pkg/fanal/analyzer/config/config.go +++ b/pkg/fanal/analyzer/config/config.go @@ -9,6 +9,7 @@ import ( "k8s.io/utils/strings/slices" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/misconf" ) @@ -26,10 +27,8 @@ type Analyzer struct { scanner *misconf.Scanner } -type NewScanner func([]string, misconf.ScannerOption) (*misconf.Scanner, error) - -func NewAnalyzer(t analyzer.Type, version int, newScanner NewScanner, opts analyzer.AnalyzerOptions) (*Analyzer, error) { - s, err := newScanner(opts.FilePatterns, opts.MisconfScannerOption) +func NewAnalyzer(t analyzer.Type, version int, fileType detection.FileType, opts analyzer.AnalyzerOptions) (*Analyzer, error) { + s, err := misconf.NewScanner(fileType, opts.MisconfScannerOption) if err != nil { return nil, xerrors.Errorf("%s scanner init error: %w", t, err) } diff --git a/pkg/fanal/analyzer/config/config_test.go b/pkg/fanal/analyzer/config/config_test.go index 147b1f4d3201..34503bde2995 100644 --- a/pkg/fanal/analyzer/config/config_test.go +++ b/pkg/fanal/analyzer/config/config_test.go @@ -12,14 +12,15 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/misconf" ) func TestAnalyzer_PostAnalyze(t *testing.T) { type fields struct { - typ analyzer.Type - newScanner config.NewScanner - opts analyzer.AnalyzerOptions + typ analyzer.Type + fileType detection.FileType + opts analyzer.AnalyzerOptions } tests := []struct { name string @@ -31,8 +32,8 @@ func TestAnalyzer_PostAnalyze(t *testing.T) { { name: "dockerfile", fields: fields{ - typ: analyzer.TypeDockerfile, - newScanner: misconf.NewDockerfileScanner, + typ: analyzer.TypeDockerfile, + fileType: detection.FileTypeDockerfile, opts: analyzer.AnalyzerOptions{ MisconfScannerOption: misconf.ScannerOption{ Namespaces: []string{"user"}, @@ -74,8 +75,8 @@ func TestAnalyzer_PostAnalyze(t *testing.T) { { name: "non-existent dir", fields: fields{ - typ: analyzer.TypeDockerfile, - newScanner: misconf.NewDockerfileScanner, + typ: analyzer.TypeDockerfile, + fileType: detection.FileTypeDockerfile, opts: analyzer.AnalyzerOptions{ MisconfScannerOption: misconf.ScannerOption{ Namespaces: []string{"user"}, @@ -90,7 +91,7 @@ func TestAnalyzer_PostAnalyze(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - a, err := config.NewAnalyzer(tt.fields.typ, 0, tt.fields.newScanner, tt.fields.opts) + a, err := config.NewAnalyzer(tt.fields.typ, 0, tt.fields.fileType, tt.fields.opts) require.NoError(t, err) got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ diff --git a/pkg/fanal/analyzer/config/dockerfile/docker.go b/pkg/fanal/analyzer/config/dockerfile/docker.go index 353cef4eb62a..0c4463dd528e 100644 --- a/pkg/fanal/analyzer/config/dockerfile/docker.go +++ b/pkg/fanal/analyzer/config/dockerfile/docker.go @@ -7,7 +7,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -28,7 +28,7 @@ type dockerConfigAnalyzer struct { } func newDockerfileConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewDockerfileScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeDockerfile, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/helm/helm.go b/pkg/fanal/analyzer/config/helm/helm.go index 14ea6aff63de..42542ce6c835 100644 --- a/pkg/fanal/analyzer/config/helm/helm.go +++ b/pkg/fanal/analyzer/config/helm/helm.go @@ -7,7 +7,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -29,7 +29,7 @@ type helmConfigAnalyzer struct { } func newHelmConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewHelmScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeHelm, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/json/json.go b/pkg/fanal/analyzer/config/json/json.go new file mode 100644 index 000000000000..c2a6eaa73d63 --- /dev/null +++ b/pkg/fanal/analyzer/config/json/json.go @@ -0,0 +1,36 @@ +package json + +import ( + "os" + "path/filepath" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/iac/detection" +) + +const ( + analyzerType = analyzer.TypeJSON + version = 1 +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newJSONConfigAnalyzer) +} + +// jsonConfigAnalyzer analyzes JSON files +type jsonConfigAnalyzer struct { + *config.Analyzer +} + +func newJSONConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeJSON, opts) + if err != nil { + return nil, err + } + return &jsonConfigAnalyzer{Analyzer: a}, nil +} + +func (*jsonConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.Ext(filePath) == ".json" +} diff --git a/pkg/fanal/analyzer/config/k8s/k8s.go b/pkg/fanal/analyzer/config/k8s/k8s.go index 6f3a58af16b5..dabdf41a990b 100644 --- a/pkg/fanal/analyzer/config/k8s/k8s.go +++ b/pkg/fanal/analyzer/config/k8s/k8s.go @@ -3,7 +3,7 @@ package k8s import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -22,7 +22,7 @@ type kubernetesConfigAnalyzer struct { } func newKubernetesConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewKubernetesScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeKubernetes, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/terraform/terraform.go b/pkg/fanal/analyzer/config/terraform/terraform.go index 363d35de87fe..14b683742413 100644 --- a/pkg/fanal/analyzer/config/terraform/terraform.go +++ b/pkg/fanal/analyzer/config/terraform/terraform.go @@ -6,7 +6,6 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" "github.com/aquasecurity/trivy/pkg/iac/detection" - "github.com/aquasecurity/trivy/pkg/misconf" ) const ( @@ -25,7 +24,7 @@ type terraformConfigAnalyzer struct { } func newTerraformConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewTerraformScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeTerraform, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/terraformplan/json/json.go b/pkg/fanal/analyzer/config/terraformplan/json/json.go index 5272f0f990f9..f0cb2c518549 100644 --- a/pkg/fanal/analyzer/config/terraformplan/json/json.go +++ b/pkg/fanal/analyzer/config/terraformplan/json/json.go @@ -8,7 +8,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -31,7 +31,7 @@ type terraformPlanConfigAnalyzer struct { } func newTerraformPlanJSONConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewTerraformPlanJSONScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeTerraformPlanJSON, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go index 0597c137d96c..13316914874d 100644 --- a/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go +++ b/pkg/fanal/analyzer/config/terraformplan/snapshot/snapshot.go @@ -6,7 +6,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" - "github.com/aquasecurity/trivy/pkg/misconf" + "github.com/aquasecurity/trivy/pkg/iac/detection" ) const ( @@ -25,7 +25,7 @@ type terraformPlanConfigAnalyzer struct { } func newTerraformPlanSnapshotConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - a, err := config.NewAnalyzer(analyzerType, version, misconf.NewTerraformPlanSnapshotScanner, opts) + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeTerraformPlanSnapshot, opts) if err != nil { return nil, err } diff --git a/pkg/fanal/analyzer/config/yaml/yaml.go b/pkg/fanal/analyzer/config/yaml/yaml.go new file mode 100644 index 000000000000..f8b5569f5bed --- /dev/null +++ b/pkg/fanal/analyzer/config/yaml/yaml.go @@ -0,0 +1,36 @@ +package yaml + +import ( + "os" + "path/filepath" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config" + "github.com/aquasecurity/trivy/pkg/iac/detection" +) + +const ( + analyzerType = analyzer.TypeYAML + version = 1 +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzerType, newYAMLConfigAnalyzer) +} + +// yamlConfigAnalyzer analyzes YAML files +type yamlConfigAnalyzer struct { + *config.Analyzer +} + +func newYAMLConfigAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + a, err := config.NewAnalyzer(analyzerType, version, detection.FileTypeYAML, opts) + if err != nil { + return nil, err + } + return &yamlConfigAnalyzer{Analyzer: a}, nil +} + +func (*yamlConfigAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.Ext(filePath) == ".yaml" || filepath.Ext(filePath) == ".yml" +} diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 681f8b9987cc..ea2108e89281 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -124,6 +124,8 @@ const ( TypeTerraform Type = Type(detection.FileTypeTerraform) TypeTerraformPlanJSON Type = Type(detection.FileTypeTerraformPlanJSON) TypeTerraformPlanSnapshot Type = Type(detection.FileTypeTerraformPlanSnapshot) + TypeYAML Type = Type(detection.FileTypeYAML) + TypeJSON Type = Type(detection.FileTypeJSON) // ======== // License @@ -245,5 +247,7 @@ var ( TypeTerraform, TypeTerraformPlanJSON, TypeTerraformPlanSnapshot, + TypeYAML, + TypeJSON, } ) diff --git a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go index e0c70cc34cd0..5a974a8dd1d1 100644 --- a/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go +++ b/pkg/fanal/analyzer/imgconf/dockerfile/dockerfile.go @@ -11,6 +11,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/image" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/mapfs" "github.com/aquasecurity/trivy/pkg/misconf" ) @@ -26,7 +27,7 @@ type historyAnalyzer struct { } func newHistoryAnalyzer(opts analyzer.ConfigAnalyzerOptions) (analyzer.ConfigAnalyzer, error) { - s, err := misconf.NewDockerfileScanner(opts.FilePatterns, opts.MisconfScannerOption) + s, err := misconf.NewScanner(detection.FileTypeDockerfile, opts.MisconfScannerOption) if err != nil { return nil, xerrors.Errorf("misconfiguration scanner error: %w", err) } diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go index cd7fea2df1e2..a69ab03708e2 100644 --- a/pkg/fanal/artifact/image/image_test.go +++ b/pkg/fanal/artifact/image/image_test.go @@ -352,17 +352,17 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + BlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingArtifact: true, - MissingBlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + MissingBlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638", + BlobID: "sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -429,7 +429,7 @@ func TestArtifact_Inspect(t *testing.T) { Name: "../../test/testdata/alpine-311.tar.gz", Type: artifact.TypeContainerImage, ID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + BlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", DiffIDs: []string{ @@ -488,25 +488,25 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", - "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", - "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", - "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", + "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", + "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", + "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", - "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", - "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", - "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", + "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", + "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", + "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", }, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", + BlobID: "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -594,7 +594,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", + BlobID: "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -690,7 +690,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", + BlobID: "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -898,7 +898,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + BlobID: "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1761,10 +1761,10 @@ func TestArtifact_Inspect(t *testing.T) { Type: artifact.TypeContainerImage, ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", - "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", - "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", - "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", + "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", + "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", + "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", }, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", @@ -1858,25 +1858,25 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:f46989447d5a1357f6b2427b86ca2af827dd380dbd7fbf392d2abf9a5d457323", - "sha256:487a6fb0914825c8fb9f3a0662a608039bd5a8b6488d76b9de2eb1a684e908e1", - "sha256:a23b05a9c95939a0d30d6b4f6c25393473252bde47b2daa03258c27461367509", - "sha256:47226d3c41a3ffd99dacdbcd2b197a7394ee8948270710ee035181427f88dfab", + "sha256:139bc12e936e0c46090b9380c4a29456d3ad8d8abd50c7bdc6160018cd887462", + "sha256:c491838e70ff0fcfdd0605af1ba84e86d6958c0846b16c52a84e06bb344e8e8d", + "sha256:25e775ef81049a93eafd865447b0b79da9e9956ab74bc02b5916eaea21c87c7c", + "sha256:a8a4798a22b65739cda9ca99ddb2cd86125c1dd86df6fc3971f937a0ff5b9ec3", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:f46989447d5a1357f6b2427b86ca2af827dd380dbd7fbf392d2abf9a5d457323", - "sha256:487a6fb0914825c8fb9f3a0662a608039bd5a8b6488d76b9de2eb1a684e908e1", - "sha256:a23b05a9c95939a0d30d6b4f6c25393473252bde47b2daa03258c27461367509", - "sha256:47226d3c41a3ffd99dacdbcd2b197a7394ee8948270710ee035181427f88dfab", + "sha256:139bc12e936e0c46090b9380c4a29456d3ad8d8abd50c7bdc6160018cd887462", + "sha256:c491838e70ff0fcfdd0605af1ba84e86d6958c0846b16c52a84e06bb344e8e8d", + "sha256:25e775ef81049a93eafd865447b0b79da9e9956ab74bc02b5916eaea21c87c7c", + "sha256:a8a4798a22b65739cda9ca99ddb2cd86125c1dd86df6fc3971f937a0ff5b9ec3", }, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:f46989447d5a1357f6b2427b86ca2af827dd380dbd7fbf392d2abf9a5d457323", + BlobID: "sha256:139bc12e936e0c46090b9380c4a29456d3ad8d8abd50c7bdc6160018cd887462", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1887,7 +1887,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:487a6fb0914825c8fb9f3a0662a608039bd5a8b6488d76b9de2eb1a684e908e1", + BlobID: "sha256:c491838e70ff0fcfdd0605af1ba84e86d6958c0846b16c52a84e06bb344e8e8d", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1898,7 +1898,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:a23b05a9c95939a0d30d6b4f6c25393473252bde47b2daa03258c27461367509", + BlobID: "sha256:25e775ef81049a93eafd865447b0b79da9e9956ab74bc02b5916eaea21c87c7c", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1910,7 +1910,7 @@ func TestArtifact_Inspect(t *testing.T) { }, { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:47226d3c41a3ffd99dacdbcd2b197a7394ee8948270710ee035181427f88dfab", + BlobID: "sha256:a8a4798a22b65739cda9ca99ddb2cd86125c1dd86df6fc3971f937a0ff5b9ec3", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -1926,10 +1926,10 @@ func TestArtifact_Inspect(t *testing.T) { Type: artifact.TypeContainerImage, ID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:f46989447d5a1357f6b2427b86ca2af827dd380dbd7fbf392d2abf9a5d457323", - "sha256:487a6fb0914825c8fb9f3a0662a608039bd5a8b6488d76b9de2eb1a684e908e1", - "sha256:a23b05a9c95939a0d30d6b4f6c25393473252bde47b2daa03258c27461367509", - "sha256:47226d3c41a3ffd99dacdbcd2b197a7394ee8948270710ee035181427f88dfab", + "sha256:139bc12e936e0c46090b9380c4a29456d3ad8d8abd50c7bdc6160018cd887462", + "sha256:c491838e70ff0fcfdd0605af1ba84e86d6958c0846b16c52a84e06bb344e8e8d", + "sha256:25e775ef81049a93eafd865447b0b79da9e9956ab74bc02b5916eaea21c87c7c", + "sha256:a8a4798a22b65739cda9ca99ddb2cd86125c1dd86df6fc3971f937a0ff5b9ec3", }, ImageMetadata: artifact.ImageMetadata{ ID: "sha256:58701fd185bda36cab0557bb6438661831267aa4a9e0b54211c4d5317a48aff4", @@ -2012,7 +2012,7 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + BlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ Err: xerrors.New("MissingBlobs failed"), @@ -2026,16 +2026,16 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + BlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ - MissingBlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + MissingBlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638", + BlobID: "sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", @@ -2095,18 +2095,18 @@ func TestArtifact_Inspect(t *testing.T) { Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:33f9415ed2cd5a9cef5d5144333619745b9ec0f851f0684dd45fa79c6b26a650", BlobIDs: []string{ - "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", - "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", - "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", - "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", + "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", + "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", + "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", }, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingBlobIDs: []string{ - "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", - "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", - "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", - "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", + "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", + "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", + "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", }, }, }, @@ -2114,7 +2114,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:a3eb0f92862bc742ea1e7ee875dd5623568ee17213ae7d29f05960eb1135fa6d", + BlobID: "sha256:4a26915356c961f038d5a7b7f73f24cd1eec53dcf6fdeecd39b310ddc066faec", BlobInfoAnything: true, }, @@ -2125,7 +2125,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:05b96a707dab6e1fcd9543f0df6a0e4cdf5c7e26272d7f6bc7ed2e1cf23afa9f", + BlobID: "sha256:e23e1d428a2a4ca9607cde5c556f744c7e9f3a1d3bfe835707c0fea107caf453", BlobInfoAnything: true, }, @@ -2136,7 +2136,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:677cd3a664e4923227de2c2571c40b9956d99e4775b2e11ce8aa207842123119", + BlobID: "sha256:b8ae022ed4f8b8bf827c04a825c2e6998217581d44c0b28b59a4e66ca65bbaa5", BlobInfoAnything: true, }, @@ -2147,7 +2147,7 @@ func TestArtifact_Inspect(t *testing.T) { { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:e870ba0421bc71c046819f809c8369e98f59a2cde34961fdd429a2102da33c0c", + BlobID: "sha256:8c51dcc708602d983f3f0507f0d26de609819c4391db92497639417e54378d11", BlobInfoAnything: true, }, @@ -2164,17 +2164,17 @@ func TestArtifact_Inspect(t *testing.T) { missingBlobsExpectation: cache.ArtifactCacheMissingBlobsExpectation{ Args: cache.ArtifactCacheMissingBlobsArgs{ ArtifactID: "sha256:c232b7d8ac8aa08aa767313d0b53084c4380d1c01a213a5971bdb039e6538313", - BlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + BlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, Returns: cache.ArtifactCacheMissingBlobsReturns{ MissingArtifact: true, - MissingBlobIDs: []string{"sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638"}, + MissingBlobIDs: []string{"sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4"}, }, }, putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:d4e6142cda465c55c8adf5b6c3148f3417a2c5582a76f933836738206e01b638", + BlobID: "sha256:24a7af33784fabfedf01999d9e0dc456e8e1c1943f7d4421f7c05164026788a4", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Digest: "", diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index d27b5ffb4366..ba6d2879bda2 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -47,7 +47,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:e480047f53bccb8a9107a424fab43452dc59df641022a300d34326639254a0cf", + BlobID: "sha256:5ba63074e071e3f0247d03dd7e544b6a75f7224ee238618482c490b36f4792dc", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -82,9 +82,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "host", Type: artifact.TypeFilesystem, - ID: "sha256:e480047f53bccb8a9107a424fab43452dc59df641022a300d34326639254a0cf", + ID: "sha256:5ba63074e071e3f0247d03dd7e544b6a75f7224ee238618482c490b36f4792dc", BlobIDs: []string{ - "sha256:e480047f53bccb8a9107a424fab43452dc59df641022a300d34326639254a0cf", + "sha256:5ba63074e071e3f0247d03dd7e544b6a75f7224ee238618482c490b36f4792dc", }, }, }, @@ -102,7 +102,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", + BlobID: "sha256:649ddb291d142363aafcf9e9cf8a6e32dc0a6ae5a95ab43d09b8201d86ed8f7a", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, }, @@ -112,9 +112,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "host", Type: artifact.TypeFilesystem, - ID: "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", + ID: "sha256:649ddb291d142363aafcf9e9cf8a6e32dc0a6ae5a95ab43d09b8201d86ed8f7a", BlobIDs: []string{ - "sha256:7db98974b2231d3e25f4890008c4d42f6f26a7da5a8aba99e954dec97f050bd6", + "sha256:649ddb291d142363aafcf9e9cf8a6e32dc0a6ae5a95ab43d09b8201d86ed8f7a", }, }, }, @@ -125,7 +125,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:e480047f53bccb8a9107a424fab43452dc59df641022a300d34326639254a0cf", + BlobID: "sha256:5ba63074e071e3f0247d03dd7e544b6a75f7224ee238618482c490b36f4792dc", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -175,7 +175,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", + BlobID: "sha256:00e49bf14e0a8c15b2d611d8e5c231276f1e10f22b3307177e513605fd18d807", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -203,9 +203,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "testdata/requirements.txt", Type: artifact.TypeFilesystem, - ID: "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", + ID: "sha256:00e49bf14e0a8c15b2d611d8e5c231276f1e10f22b3307177e513605fd18d807", BlobIDs: []string{ - "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", + "sha256:00e49bf14e0a8c15b2d611d8e5c231276f1e10f22b3307177e513605fd18d807", }, }, }, @@ -216,7 +216,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", + BlobID: "sha256:00e49bf14e0a8c15b2d611d8e5c231276f1e10f22b3307177e513605fd18d807", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -244,9 +244,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: "testdata/requirements.txt", Type: artifact.TypeFilesystem, - ID: "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", + ID: "sha256:00e49bf14e0a8c15b2d611d8e5c231276f1e10f22b3307177e513605fd18d807", BlobIDs: []string{ - "sha256:c91a594202ba114ce4d067d9cac40b545c7ba2a96520c9ca8050b437020c3ab9", + "sha256:00e49bf14e0a8c15b2d611d8e5c231276f1e10f22b3307177e513605fd18d807", }, }, }, @@ -341,9 +341,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/single-failure", Type: artifact.TypeFilesystem, - ID: "sha256:5e4ca8ffaa89f49c69acfbac6d7cebb0a372b07d4c4c9876f9a58043c8ee56e9", + ID: "sha256:4f2a334086f1d175c0ee57cd4220f20b187b456dc36bbe39a63c42b5637b2179", BlobIDs: []string{ - "sha256:5e4ca8ffaa89f49c69acfbac6d7cebb0a372b07d4c4c9876f9a58043c8ee56e9", + "sha256:4f2a334086f1d175c0ee57cd4220f20b187b456dc36bbe39a63c42b5637b2179", }, }, }, @@ -426,9 +426,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/multiple-failures", Type: artifact.TypeFilesystem, - ID: "sha256:1832cedd0d8baeb172c7297acb56417c0dec454c280a79ee2a8f413e1ffca192", + ID: "sha256:ff7a84de97729e169c94107a89bc9da88f5ecf94873cdbd9bf0844e1af5f5b30", BlobIDs: []string{ - "sha256:1832cedd0d8baeb172c7297acb56417c0dec454c280a79ee2a8f413e1ffca192", + "sha256:ff7a84de97729e169c94107a89bc9da88f5ecf94873cdbd9bf0844e1af5f5b30", }, }, }, @@ -456,9 +456,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/no-results", Type: artifact.TypeFilesystem, - ID: "sha256:00e01cf7bf052dbf8f02739d281675eff16f5ddf004256472cdddb55cf974fd6", + ID: "sha256:06406e9bb7ba09d8d24c73c0995ac3b94fc1d6ce059e5a45418d7c0ab2b6dca4", BlobIDs: []string{ - "sha256:00e01cf7bf052dbf8f02739d281675eff16f5ddf004256472cdddb55cf974fd6", + "sha256:06406e9bb7ba09d8d24c73c0995ac3b94fc1d6ce059e5a45418d7c0ab2b6dca4", }, }, }, @@ -505,9 +505,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/passed", Type: artifact.TypeFilesystem, - ID: "sha256:9ee9fe14c3fcff202bc8c72f9e20a3035442ae394ec5cd201e1cb29d2111b4e1", + ID: "sha256:107251e6ee7312c8c27ff04e71dd943b92021777c575971809f57b60bf41bba4", BlobIDs: []string{ - "sha256:9ee9fe14c3fcff202bc8c72f9e20a3035442ae394ec5cd201e1cb29d2111b4e1", + "sha256:107251e6ee7312c8c27ff04e71dd943b92021777c575971809f57b60bf41bba4", }, }, }, @@ -571,9 +571,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/busted-relative-paths/child/main.tf", Type: artifact.TypeFilesystem, - ID: "sha256:c3e6c9e68cd7a9900cd8aa690e1d5af174ddcc00ddb9438a858c3e329b9ea8f4", + ID: "sha256:f2f07f41dbd6816d41ce6f28b3922fcedab611b8602d95e328571afd5c53b31d", BlobIDs: []string{ - "sha256:c3e6c9e68cd7a9900cd8aa690e1d5af174ddcc00ddb9438a858c3e329b9ea8f4", + "sha256:f2f07f41dbd6816d41ce6f28b3922fcedab611b8602d95e328571afd5c53b31d", }, }, }, @@ -621,9 +621,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/tfvar-outside/tf", Type: artifact.TypeFilesystem, - ID: "sha256:9ee9fe14c3fcff202bc8c72f9e20a3035442ae394ec5cd201e1cb29d2111b4e1", + ID: "sha256:107251e6ee7312c8c27ff04e71dd943b92021777c575971809f57b60bf41bba4", BlobIDs: []string{ - "sha256:9ee9fe14c3fcff202bc8c72f9e20a3035442ae394ec5cd201e1cb29d2111b4e1", + "sha256:107251e6ee7312c8c27ff04e71dd943b92021777c575971809f57b60bf41bba4", }, }, }, @@ -711,9 +711,9 @@ func TestTerraformMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraform/relative-paths/child", Type: artifact.TypeFilesystem, - ID: "sha256:44d28d3fc115f1f8dd3f8978c7e9432ba255dd82661c3810178276897e2d8fb7", + ID: "sha256:f04c37d8e5300ce9344c795c2d4e0bb1dbef251b15538a6e0c11d6d9a86664d1", BlobIDs: []string{ - "sha256:44d28d3fc115f1f8dd3f8978c7e9432ba255dd82661c3810178276897e2d8fb7", + "sha256:f04c37d8e5300ce9344c795c2d4e0bb1dbef251b15538a6e0c11d6d9a86664d1", }, }, }, @@ -830,9 +830,9 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraformplan/snapshots/single-failure", Type: artifact.TypeFilesystem, - ID: "sha256:39babdcb854331c58dc1e20c20dc67c2b4bda23895bf2861cc72d187de2bc716", + ID: "sha256:c21e15d7d0cfe7c1ef1e1933b443f781d1411b864500431302a1e45fe0950529", BlobIDs: []string{ - "sha256:39babdcb854331c58dc1e20c20dc67c2b4bda23895bf2861cc72d187de2bc716", + "sha256:c21e15d7d0cfe7c1ef1e1933b443f781d1411b864500431302a1e45fe0950529", }, }, }, @@ -906,9 +906,9 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraformplan/snapshots/multiple-failures", Type: artifact.TypeFilesystem, - ID: "sha256:32debc3c2857404ceeec978a938609a4b20f1c53c304603815955885377f286c", + ID: "sha256:800c9ce07be36c7f4d1a4876ecfaaa77c1d90b15f43c58eaf52ea27670afcc42", BlobIDs: []string{ - "sha256:32debc3c2857404ceeec978a938609a4b20f1c53c304603815955885377f286c", + "sha256:800c9ce07be36c7f4d1a4876ecfaaa77c1d90b15f43c58eaf52ea27670afcc42", }, }, }, @@ -946,9 +946,9 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/terraformplan/snapshots/passed", Type: artifact.TypeFilesystem, - ID: "sha256:2432b64e4583676ec1e94903d31c4faf79a1e0f4ed49bf24aef2bf9b44517ca2", + ID: "sha256:3d90bb96d2dc0af277ab0ce28972670eb81968d00775d1e92edce54ae2d165c0", BlobIDs: []string{ - "sha256:2432b64e4583676ec1e94903d31c4faf79a1e0f4ed49bf24aef2bf9b44517ca2", + "sha256:3d90bb96d2dc0af277ab0ce28972670eb81968d00775d1e92edce54ae2d165c0", }, }, }, @@ -1045,7 +1045,7 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { }, CauseMetadata: types.CauseMetadata{ Resource: "main.yaml:3-6", - Provider: "Generic", + Provider: "Cloud", Service: "general", StartLine: 3, EndLine: 6, @@ -1061,9 +1061,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/single-failure/src", Type: artifact.TypeFilesystem, - ID: "sha256:43bb7ce5253686cf96a68e6d4bd30c33e163019ae2893968602da9e5e86b1aab", + ID: "sha256:bd481a673eb07ed7b51e1ff2a6e7aca08b433d11288eb9f5e9aa2d2f482a0c16", BlobIDs: []string{ - "sha256:43bb7ce5253686cf96a68e6d4bd30c33e163019ae2893968602da9e5e86b1aab", + "sha256:bd481a673eb07ed7b51e1ff2a6e7aca08b433d11288eb9f5e9aa2d2f482a0c16", }, }, }, @@ -1107,7 +1107,7 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { }, CauseMetadata: types.CauseMetadata{ Resource: "main.yaml:2-5", - Provider: "Generic", + Provider: "Cloud", Service: "general", StartLine: 2, EndLine: 5, @@ -1129,7 +1129,7 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { }, CauseMetadata: types.CauseMetadata{ Resource: "main.yaml:6-9", - Provider: "Generic", + Provider: "Cloud", Service: "general", StartLine: 6, EndLine: 9, @@ -1145,9 +1145,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/multiple-failures/src", Type: artifact.TypeFilesystem, - ID: "sha256:4609d64f57187099483e76a979d2f74862a092a5f42a9945e6f1242c372a811c", + ID: "sha256:c25676d23114b9c912067d45285cd9e662cefae5e3cc82c40f67df5fee39f92a", BlobIDs: []string{ - "sha256:4609d64f57187099483e76a979d2f74862a092a5f42a9945e6f1242c372a811c", + "sha256:c25676d23114b9c912067d45285cd9e662cefae5e3cc82c40f67df5fee39f92a", }, }, }, @@ -1177,9 +1177,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/no-results/src", Type: artifact.TypeFilesystem, - ID: "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", + ID: "sha256:522b19ad182f50b7b04217831c914df52c2d2eb1bdddb02eb9cd2b4e14c9a32b", BlobIDs: []string{ - "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", + "sha256:522b19ad182f50b7b04217831c914df52c2d2eb1bdddb02eb9cd2b4e14c9a32b", }, }, }, @@ -1235,9 +1235,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/params/code/src", Type: artifact.TypeFilesystem, - ID: "sha256:abaeb2f443a59940afd63e015d0bb737127ebde06306840f529dd40d65390703", + ID: "sha256:40d6550292de7518fd7229f7b14803c67cbffbad3376e773ad7e6dc003846e87", BlobIDs: []string{ - "sha256:abaeb2f443a59940afd63e015d0bb737127ebde06306840f529dd40d65390703", + "sha256:40d6550292de7518fd7229f7b14803c67cbffbad3376e773ad7e6dc003846e87", }, }, }, @@ -1279,7 +1279,7 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { References: []string{"https://trivy.dev/"}, }, CauseMetadata: types.CauseMetadata{ - Provider: "Generic", + Provider: "Cloud", Service: "general", }, }, @@ -1293,9 +1293,9 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/cloudformation/passed/src", Type: artifact.TypeFilesystem, - ID: "sha256:73bb13d7c722bfcdf6c00f51f368befda453e1acba31ba62aad31b5b04b235e8", + ID: "sha256:e2269b8ea44e29aedeaeea83368f879b3fb0cb97bfe46bcca4383a637280cace", BlobIDs: []string{ - "sha256:73bb13d7c722bfcdf6c00f51f368befda453e1acba31ba62aad31b5b04b235e8", + "sha256:e2269b8ea44e29aedeaeea83368f879b3fb0cb97bfe46bcca4383a637280cace", }, }, }, @@ -1381,9 +1381,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/single-failure/src", Type: artifact.TypeFilesystem, - ID: "sha256:059f081288e7ea418286bddcee78167e8ebf33b47e365c20754b6cfa180d997d", + ID: "sha256:3551bddb0f53fb9e0c32390e3ac33f841e3cc15a52ddbcbd9ea07f7e6d1d4437", BlobIDs: []string{ - "sha256:059f081288e7ea418286bddcee78167e8ebf33b47e365c20754b6cfa180d997d", + "sha256:3551bddb0f53fb9e0c32390e3ac33f841e3cc15a52ddbcbd9ea07f7e6d1d4437", }, }, }, @@ -1439,9 +1439,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/multiple-failures/src", Type: artifact.TypeFilesystem, - ID: "sha256:059f081288e7ea418286bddcee78167e8ebf33b47e365c20754b6cfa180d997d", + ID: "sha256:3551bddb0f53fb9e0c32390e3ac33f841e3cc15a52ddbcbd9ea07f7e6d1d4437", BlobIDs: []string{ - "sha256:059f081288e7ea418286bddcee78167e8ebf33b47e365c20754b6cfa180d997d", + "sha256:3551bddb0f53fb9e0c32390e3ac33f841e3cc15a52ddbcbd9ea07f7e6d1d4437", }, }, }, @@ -1469,9 +1469,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/no-results/src", Type: artifact.TypeFilesystem, - ID: "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", + ID: "sha256:e57ad1b0be7370a131e1265a25ac8790bbfec2bb5867315916cf92799e5855d3", BlobIDs: []string{ - "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", + "sha256:e57ad1b0be7370a131e1265a25ac8790bbfec2bb5867315916cf92799e5855d3", }, }, }, @@ -1529,9 +1529,9 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/dockerfile/passed/src", Type: artifact.TypeFilesystem, - ID: "sha256:5f6504b01b68ef1418d59210c0c7b3604fe63f8575a72721d1172d9d2fdd2e23", + ID: "sha256:ff4a3a7aed57bd8190277cf2cc16213eef43b7a37f26f8458525f2efd9793e8f", BlobIDs: []string{ - "sha256:5f6504b01b68ef1418d59210c0c7b3604fe63f8575a72721d1172d9d2fdd2e23", + "sha256:ff4a3a7aed57bd8190277cf2cc16213eef43b7a37f26f8458525f2efd9793e8f", }, }, }, @@ -1605,7 +1605,7 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { }, }, CauseMetadata: types.CauseMetadata{ - Provider: "Generic", + Provider: "Kubernetes", Service: "general", StartLine: 7, EndLine: 9, @@ -1621,9 +1621,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/single-failure/src", Type: artifact.TypeFilesystem, - ID: "sha256:14768f3c82388c43e0af6579632535b5ecd6bd4ac802d063d74b636725191fe9", + ID: "sha256:63ceedb6582e29ee4184b8b776ee27efe226d07a932461639c05bfbe47bf7efa", BlobIDs: []string{ - "sha256:14768f3c82388c43e0af6579632535b5ecd6bd4ac802d063d74b636725191fe9", + "sha256:63ceedb6582e29ee4184b8b776ee27efe226d07a932461639c05bfbe47bf7efa", }, }, }, @@ -1668,7 +1668,7 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { }, }, CauseMetadata: types.CauseMetadata{ - Provider: "Generic", + Provider: "Kubernetes", Service: "general", StartLine: 7, EndLine: 9, @@ -1691,7 +1691,7 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { }, }, CauseMetadata: types.CauseMetadata{ - Provider: "Generic", + Provider: "Kubernetes", Service: "general", StartLine: 10, EndLine: 12, @@ -1707,9 +1707,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/multiple-failures/src", Type: artifact.TypeFilesystem, - ID: "sha256:d7b5fe7fb0e8d51cb6a06b5f2027b1e976e25a2462ff40241f86e13e69ff210c", + ID: "sha256:47fcc85b182385fc6cd7ca08270efff33281ba7717c7a97c7b28a47bef24fae3", BlobIDs: []string{ - "sha256:d7b5fe7fb0e8d51cb6a06b5f2027b1e976e25a2462ff40241f86e13e69ff210c", + "sha256:47fcc85b182385fc6cd7ca08270efff33281ba7717c7a97c7b28a47bef24fae3", }, }, }, @@ -1737,9 +1737,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/no-results/src", Type: artifact.TypeFilesystem, - ID: "sha256:ddd0e363b0ebab71250f9d36de65d485ca2faa9510ab6ddea940de7af8267b67", + ID: "sha256:4aad6cb079f406935fa383e126616cee6c82e326a92c163042d6043596f18e04", BlobIDs: []string{ - "sha256:ddd0e363b0ebab71250f9d36de65d485ca2faa9510ab6ddea940de7af8267b67", + "sha256:4aad6cb079f406935fa383e126616cee6c82e326a92c163042d6043596f18e04", }, }, }, @@ -1783,7 +1783,7 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { }, }, CauseMetadata: types.CauseMetadata{ - Provider: "Generic", + Provider: "Kubernetes", Service: "general", }, }, @@ -1797,9 +1797,9 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/kubernetes/passed/src", Type: artifact.TypeFilesystem, - ID: "sha256:af77d733739a4366b6e03e95605f5282262d45905efed501d83ec7ecc97a7574", + ID: "sha256:b781859c685b32a25e96e54b331957d696cedfc98162146819ac64d3f157660e", BlobIDs: []string{ - "sha256:af77d733739a4366b6e03e95605f5282262d45905efed501d83ec7ecc97a7574", + "sha256:b781859c685b32a25e96e54b331957d696cedfc98162146819ac64d3f157660e", }, }, }, @@ -1870,7 +1870,7 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { }, CauseMetadata: types.CauseMetadata{ Resource: "resources[0]", - Provider: "Generic", + Provider: "Cloud", Service: "general", StartLine: 30, EndLine: 40, @@ -1886,9 +1886,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/azurearm/single-failure/src", Type: artifact.TypeFilesystem, - ID: "sha256:ff25ae7d346f724faf4d2653868bc249bf221460bcf6d364cf50372c303342d2", + ID: "sha256:62a167d993f603f5552042e4b3c7ac3a65dbbe62bad28e72631c69c9a8f5e2b5", BlobIDs: []string{ - "sha256:ff25ae7d346f724faf4d2653868bc249bf221460bcf6d364cf50372c303342d2", + "sha256:62a167d993f603f5552042e4b3c7ac3a65dbbe62bad28e72631c69c9a8f5e2b5", }, }, }, @@ -1930,7 +1930,7 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { }, CauseMetadata: types.CauseMetadata{ Resource: "resources[0]", - Provider: "Generic", + Provider: "Cloud", Service: "general", StartLine: 30, EndLine: 40, @@ -1952,7 +1952,7 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { }, CauseMetadata: types.CauseMetadata{ Resource: "resources[1]", - Provider: "Generic", + Provider: "Cloud", Service: "general", StartLine: 41, EndLine: 51, @@ -1968,9 +1968,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/azurearm/multiple-failures/src", Type: artifact.TypeFilesystem, - ID: "sha256:e5c5ad752c6ff8d26f1b6764a9fb6dec345157df7c816b86d491b0af9e9f4ae6", + ID: "sha256:3cc8c966f10a75dc902589329cf202168176243ef8fdec7219452bb54d02af8e", BlobIDs: []string{ - "sha256:e5c5ad752c6ff8d26f1b6764a9fb6dec345157df7c816b86d491b0af9e9f4ae6", + "sha256:3cc8c966f10a75dc902589329cf202168176243ef8fdec7219452bb54d02af8e", }, }, }, @@ -1998,9 +1998,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/azurearm/no-results/src", Type: artifact.TypeFilesystem, - ID: "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", + ID: "sha256:522b19ad182f50b7b04217831c914df52c2d2eb1bdddb02eb9cd2b4e14c9a32b", BlobIDs: []string{ - "sha256:a3d40c3290bf45542f9c0aa364f2c16aca06cdccb4868a2e442b7e6a56c6157a", + "sha256:522b19ad182f50b7b04217831c914df52c2d2eb1bdddb02eb9cd2b4e14c9a32b", }, }, }, @@ -2040,7 +2040,7 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { References: []string{"https://trivy.dev/"}, }, CauseMetadata: types.CauseMetadata{ - Provider: "Generic", + Provider: "Cloud", Service: "general", }, }, @@ -2054,9 +2054,9 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { want: artifact.Reference{ Name: "testdata/misconfig/azurearm/passed/src", Type: artifact.TypeFilesystem, - ID: "sha256:0702eea6f699984f98c788c737f3adf74ae00db2b24b7d44577c7eedc31b1eb1", + ID: "sha256:d6a4722cb6865cac6f55c1789d64c57479539e9198722519918764a230586b4b", BlobIDs: []string{ - "sha256:0702eea6f699984f98c788c737f3adf74ae00db2b24b7d44577c7eedc31b1eb1", + "sha256:d6a4722cb6865cac6f55c1789d64c57479539e9198722519918764a230586b4b", }, }, }, @@ -2110,8 +2110,8 @@ func TestMixedConfigurationScan(t *testing.T) { SchemaVersion: 2, Misconfigurations: []types.Misconfiguration{ { - FileType: "terraform", - FilePath: "main.tf", + FileType: "cloudformation", + FilePath: "main.yaml", Failures: types.MisconfResults{ { Namespace: "user.something", @@ -2120,7 +2120,7 @@ func TestMixedConfigurationScan(t *testing.T) { PolicyMetadata: types.PolicyMetadata{ ID: "TEST001", AVDID: "AVD-TEST-0001", - Type: "Terraform Security Check", + Type: "CloudFormation Security Check", Title: "Test policy", Description: "This is a test policy.", Severity: "LOW", @@ -2128,18 +2128,18 @@ func TestMixedConfigurationScan(t *testing.T) { References: []string{"https://trivy.dev/"}, }, CauseMetadata: types.CauseMetadata{ - Resource: "aws_s3_bucket.asd", - Provider: "Generic", + Resource: "main.yaml:3-6", + Provider: "Cloud", Service: "general", - StartLine: 1, - EndLine: 3, + StartLine: 3, + EndLine: 6, }, }, }, }, { - FileType: "cloudformation", - FilePath: "main.yaml", + FileType: "terraform", + FilePath: "main.tf", Failures: types.MisconfResults{ { Namespace: "user.something", @@ -2148,7 +2148,7 @@ func TestMixedConfigurationScan(t *testing.T) { PolicyMetadata: types.PolicyMetadata{ ID: "TEST001", AVDID: "AVD-TEST-0001", - Type: "CloudFormation Security Check", + Type: "Terraform Security Check", Title: "Test policy", Description: "This is a test policy.", Severity: "LOW", @@ -2156,11 +2156,11 @@ func TestMixedConfigurationScan(t *testing.T) { References: []string{"https://trivy.dev/"}, }, CauseMetadata: types.CauseMetadata{ - Resource: "main.yaml:3-6", - Provider: "Generic", + Resource: "aws_s3_bucket.asd", + Provider: "Cloud", Service: "general", - StartLine: 3, - EndLine: 6, + StartLine: 1, + EndLine: 3, }, }, }, @@ -2195,5 +2195,332 @@ func TestMixedConfigurationScan(t *testing.T) { assert.Equal(t, tt.want.Type, got.Type) }) } +} + +func TestJSONConfigScan(t *testing.T) { + type fields struct { + dir string + schemas []string + } + + tests := []struct { + name string + fields fields + artifactOpt artifact.Option + putBlobExpectation cache.ArtifactCachePutBlobExpectation + want artifact.Reference + }{ + { + name: "happy path without custom schema", + fields: fields{ + dir: "./testdata/misconfig/json/passed/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/json/passed/checks"}, + DisableEmbeddedPolicies: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.JSON, + FilePath: "test1.json", + Failures: types.MisconfResults{ + { + Namespace: "user.test_json_check", + Query: "data.user.test_json_check.deny", + Message: `Service "foo" should not be used`, + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "TEST001", + Type: "JSON Security Check", + Title: "Test check", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + { + FileType: types.JSON, + FilePath: "test2.json", + Failures: types.MisconfResults{ + { + Namespace: "user.test_json_check", + Query: "data.user.test_json_check.deny", + Message: `Provider "bar" should not be used`, + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "TEST001", + Type: "JSON Security Check", + Title: "Test check", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: artifact.Reference{ + Name: "testdata/misconfig/json/passed/src", + Type: artifact.TypeFilesystem, + }, + }, + { + name: "happy path with custom schema", + fields: fields{ + dir: "./testdata/misconfig/json/with-schema/src", + schemas: []string{"./testdata/misconfig/json/with-schema/schemas"}, + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/json/with-schema/checks"}, + DisableEmbeddedPolicies: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.JSON, + FilePath: "test1.json", + Failures: types.MisconfResults{ + { + Namespace: "user.test_json_check", + Query: "data.user.test_json_check.deny", + Message: `Service "foo" should not be used`, + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "TEST001", + Type: "JSON Security Check", + Title: "Test check", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: artifact.Reference{ + Name: "testdata/misconfig/json/with-schema/src", + Type: artifact.TypeFilesystem, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + + if len(tt.fields.schemas) > 0 { + schemas, err := misconf.LoadConfigSchemas(tt.fields.schemas) + require.NoError(t, err) + tt.artifactOpt.MisconfScannerOption.ConfigFileSchemas = schemas + } + + a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + require.NoError(t, err) + require.NotNil(t, got) + + assert.Equal(t, tt.want.Name, got.Name) + assert.Equal(t, tt.want.Type, got.Type) + }) + } +} + +func TestYAMLConfigScan(t *testing.T) { + type fields struct { + dir string + schemas []string + } + + tests := []struct { + name string + fields fields + artifactOpt artifact.Option + putBlobExpectation cache.ArtifactCachePutBlobExpectation + want artifact.Reference + }{ + { + name: "happy path without custom schema", + fields: fields{ + dir: "./testdata/misconfig/yaml/passed/src", + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/yaml/passed/checks"}, + DisableEmbeddedPolicies: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.YAML, + FilePath: "test1.yaml", + Failures: types.MisconfResults{ + { + Namespace: "user.test_yaml_check", + Query: "data.user.test_yaml_check.deny", + Message: `Service "foo" should not be used`, + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "TEST001", + Type: "YAML Security Check", + Title: "Test check", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + { + FileType: types.YAML, + FilePath: "test2.yml", + Failures: types.MisconfResults{ + { + Namespace: "user.test_yaml_check", + Query: "data.user.test_yaml_check.deny", + Message: `Provider "bar" should not be used`, + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "TEST001", + Type: "YAML Security Check", + Title: "Test check", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: artifact.Reference{ + Name: "testdata/misconfig/yaml/passed/src", + Type: artifact.TypeFilesystem, + }, + }, + { + name: "happy path with custom schema", + fields: fields{ + dir: "./testdata/misconfig/yaml/with-schema/src", + schemas: []string{"./testdata/misconfig/yaml/with-schema/schemas"}, + }, + artifactOpt: artifact.Option{ + MisconfScannerOption: misconf.ScannerOption{ + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/yaml/with-schema/checks"}, + DisableEmbeddedPolicies: true, + }, + }, + putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ + Args: cache.ArtifactCachePutBlobArgs{ + BlobIDAnything: true, + BlobInfo: types.BlobInfo{ + SchemaVersion: types.BlobJSONSchemaVersion, + Misconfigurations: []types.Misconfiguration{ + { + FileType: types.YAML, + FilePath: "test1.yaml", + Failures: types.MisconfResults{ + { + Namespace: "user.test_yaml_check", + Query: "data.user.test_yaml_check.deny", + Message: `Service "foo" should not be used`, + PolicyMetadata: types.PolicyMetadata{ + ID: "TEST001", + AVDID: "TEST001", + Type: "YAML Security Check", + Title: "Test check", + Severity: "LOW", + }, + CauseMetadata: types.CauseMetadata{ + Provider: "Generic", + Service: "general", + }, + }, + }, + }, + }, + }, + }, + Returns: cache.ArtifactCachePutBlobReturns{}, + }, + want: artifact.Reference{ + Name: "testdata/misconfig/yaml/with-schema/src", + Type: artifact.TypeFilesystem, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := new(cache.MockArtifactCache) + c.ApplyPutBlobExpectation(tt.putBlobExpectation) + + if len(tt.fields.schemas) > 0 { + schemas, err := misconf.LoadConfigSchemas(tt.fields.schemas) + require.NoError(t, err) + tt.artifactOpt.MisconfScannerOption.ConfigFileSchemas = schemas + } + + a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) + require.NoError(t, err) + + got, err := a.Inspect(context.Background()) + require.NoError(t, err) + require.NotNil(t, got) + + assert.Equal(t, tt.want.Name, got.Name) + assert.Equal(t, tt.want.Type, got.Type) + }) + } } diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/rego/policy.rego index fca807d18e9d..4b5158649568 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/multiple-failures/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/rego/policy.rego index ecf4506727a3..22c65aaf287c 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/no-results/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/rego/policy.rego index efae52a8f62c..243eb8e0d9ec 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/passed/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/rego/policy.rego index fca807d18e9d..4b5158649568 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/azurearm/single-failure/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/rego/policy.rego index ecf4506727a3..22c65aaf287c 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/multiple-failures/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/rego/policy.rego index ecf4506727a3..22c65aaf287c 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/no-results/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/rego/policy.rego index 1a94609aaa8b..1d8cd4a1463b 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/params/code/rego/policy.rego @@ -12,7 +12,9 @@ # severity: HIGH # short_code: foo-bar-baz # recommended_action: "Remove bad stuff" - +# input: +# selector: +# - type: cloud package user.something deny[res] { diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/rego/policy.rego index d844d6cd2e75..ce267bc752a6 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/passed/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/rego/policy.rego index ecf4506727a3..22c65aaf287c 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/cloudformation/single-failure/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/passed/checks/test.rego b/pkg/fanal/artifact/local/testdata/misconfig/json/passed/checks/test.rego new file mode 100644 index 000000000000..b8e61860e873 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/passed/checks/test.rego @@ -0,0 +1,17 @@ +# METADATA +# title: Test check +# custom: +# id: TEST001 +# avd_id: TEST001 +# severity: LOW +package user.test_json_check + +deny[res] { + input.service == "foo" + res := result.new(`Service "foo" should not be used`, input.service) +} + +deny[res] { + input.provider == "bar" + res := result.new(`Provider "bar" should not be used`, input.provider) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test1.json b/pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test1.json new file mode 100644 index 000000000000..95b9d38deb71 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test1.json @@ -0,0 +1,3 @@ +{ + "service": "foo" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test2.json b/pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test2.json new file mode 100644 index 000000000000..22e90bf288b3 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/passed/src/test2.json @@ -0,0 +1,3 @@ +{ + "provider": "bar" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/checks/test.rego b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/checks/test.rego new file mode 100644 index 000000000000..b8e61860e873 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/checks/test.rego @@ -0,0 +1,17 @@ +# METADATA +# title: Test check +# custom: +# id: TEST001 +# avd_id: TEST001 +# severity: LOW +package user.test_json_check + +deny[res] { + input.service == "foo" + res := result.new(`Service "foo" should not be used`, input.service) +} + +deny[res] { + input.provider == "bar" + res := result.new(`Provider "bar" should not be used`, input.provider) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/schemas/test.json b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/schemas/test.json new file mode 100644 index 000000000000..f72cdb0d2016 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/schemas/test.json @@ -0,0 +1,9 @@ +{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "service": { "type": "string" } + }, + "required": ["service"] +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test1.json b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test1.json new file mode 100644 index 000000000000..95b9d38deb71 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test1.json @@ -0,0 +1,3 @@ +{ + "service": "foo" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test2.json b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test2.json new file mode 100644 index 000000000000..22e90bf288b3 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/json/with-schema/src/test2.json @@ -0,0 +1,3 @@ +{ + "provider": "bar" +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/rego/policy.rego index 6acb90f8b852..2e914843a266 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/multiple-failures/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: kubernetes package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/rego/policy.rego index 87c2e8f830b8..e46dea74b0a9 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/no-results/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: kubernetes package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/rego/policy.rego index c46411777481..fd312f3d26d5 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/passed/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: kubernetes package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/rego/policy.rego index de8ae68d4d5d..d01bb39ce435 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/kubernetes/single-failure/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - https://trivy.dev/ +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-evil +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: kubernetes package user.something -__rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-evil", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", -} - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/mixed/rego/policy.rego b/pkg/fanal/artifact/local/testdata/misconfig/mixed/rego/policy.rego index a9399362d285..410a3c8ada8d 100644 --- a/pkg/fanal/artifact/local/testdata/misconfig/mixed/rego/policy.rego +++ b/pkg/fanal/artifact/local/testdata/misconfig/mixed/rego/policy.rego @@ -1,16 +1,19 @@ +# METADATA +# title: Test policy +# description: This is a test policy. +# related_resources: +# - "https://trivy.dev/" +# custom: +# id: TEST001 +# avd_id: AVD-TEST-0001 +# severity: LOW +# short_code: no-buckets +# recommended_actions: Have a cup of tea. +# input: +# selector: +# - type: cloud package user.something - __rego_metadata__ := { - "id": "TEST001", - "avd_id": "AVD-TEST-0001", - "title": "Test policy", - "short_code": "no-buckets", - "severity": "LOW", - "description": "This is a test policy.", - "recommended_actions": "Have a cup of tea.", - "url": "https://trivy.dev/", - } - # taken from defsec rego lib to mimic behaviour result(msg, cause) = result { metadata := object.get(cause, "__defsec_metadata", cause) diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/checks/test.rego b/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/checks/test.rego new file mode 100644 index 000000000000..ef60d7c9f702 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/checks/test.rego @@ -0,0 +1,17 @@ +# METADATA +# title: Test check +# custom: +# id: TEST001 +# avd_id: TEST001 +# severity: LOW +package user.test_yaml_check + +deny[res] { + input.service == "foo" + res := result.new(`Service "foo" should not be used`, input.service) +} + +deny[res] { + input.provider == "bar" + res := result.new(`Provider "bar" should not be used`, input.provider) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test1.yaml b/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test1.yaml new file mode 100644 index 000000000000..18d05964b79b --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test1.yaml @@ -0,0 +1 @@ +service: foo \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test2.yml b/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test2.yml new file mode 100644 index 000000000000..067ebe333ddf --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/passed/src/test2.yml @@ -0,0 +1 @@ +provider: bar \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/checks/test.rego b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/checks/test.rego new file mode 100644 index 000000000000..ef60d7c9f702 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/checks/test.rego @@ -0,0 +1,17 @@ +# METADATA +# title: Test check +# custom: +# id: TEST001 +# avd_id: TEST001 +# severity: LOW +package user.test_yaml_check + +deny[res] { + input.service == "foo" + res := result.new(`Service "foo" should not be used`, input.service) +} + +deny[res] { + input.provider == "bar" + res := result.new(`Provider "bar" should not be used`, input.provider) +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/schemas/test.json b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/schemas/test.json new file mode 100644 index 000000000000..f72cdb0d2016 --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/schemas/test.json @@ -0,0 +1,9 @@ +{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "service": { "type": "string" } + }, + "required": ["service"] +} \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test1.yaml b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test1.yaml new file mode 100644 index 000000000000..18d05964b79b --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test1.yaml @@ -0,0 +1 @@ +service: foo \ No newline at end of file diff --git a/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test2.yml b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test2.yml new file mode 100644 index 000000000000..067ebe333ddf --- /dev/null +++ b/pkg/fanal/artifact/local/testdata/misconfig/yaml/with-schema/src/test2.yml @@ -0,0 +1 @@ +provider: bar \ No newline at end of file diff --git a/pkg/fanal/artifact/repo/git_test.go b/pkg/fanal/artifact/repo/git_test.go index 8de1f3d8864b..fbfbe39ff85f 100644 --- a/pkg/fanal/artifact/repo/git_test.go +++ b/pkg/fanal/artifact/repo/git_test.go @@ -197,9 +197,9 @@ func TestArtifact_Inspect(t *testing.T) { want: artifact.Reference{ Name: ts.URL + "/test-repo.git", Type: artifact.TypeRepository, - ID: "sha256:6a89d4fcd50f840a79da64523c255da80171acd3d286df2acc60056c778d9304", + ID: "sha256:88233504639eb201433a0505956309ba0c48156f45beb786f95ccd3e8a343e9d", BlobIDs: []string{ - "sha256:6a89d4fcd50f840a79da64523c255da80171acd3d286df2acc60056c778d9304", + "sha256:88233504639eb201433a0505956309ba0c48156f45beb786f95ccd3e8a343e9d", }, }, }, diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index c257154e24ea..ffe1e0718764 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -97,6 +97,7 @@ var AggregatingTypes = []LangType{ // Config files const ( JSON ConfigType = "json" + YAML ConfigType = "yaml" Dockerfile ConfigType = "dockerfile" Terraform ConfigType = "terraform" TerraformPlanJSON ConfigType = "terraformplan" diff --git a/pkg/flag/misconf_flags.go b/pkg/flag/misconf_flags.go index fc7505fec393..128ecfcac42e 100644 --- a/pkg/flag/misconf_flags.go +++ b/pkg/flag/misconf_flags.go @@ -3,6 +3,8 @@ package flag import ( "fmt" + "github.com/samber/lo" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/policy" xstrings "github.com/aquasecurity/trivy/pkg/x/strings" @@ -96,8 +98,15 @@ var ( MisconfigScannersFlag = Flag[[]string]{ Name: "misconfig-scanners", ConfigName: "misconfiguration.scanners", - Default: xstrings.ToStringSlice(analyzer.TypeConfigFiles), - Usage: "comma-separated list of misconfig scanners to use for misconfiguration scanning", + Default: xstrings.ToStringSlice( + lo.Without(analyzer.TypeConfigFiles, analyzer.TypeYAML, analyzer.TypeJSON), + ), + Usage: "comma-separated list of misconfig scanners to use for misconfiguration scanning", + } + ConfigFileSchemasFlag = Flag[[]string]{ + Name: "config-file-schemas", + ConfigName: "misconfiguration.config-file-schemas", + Usage: "specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking", } ) @@ -118,6 +127,7 @@ type MisconfFlagGroup struct { CloudformationParamVars *Flag[[]string] TerraformExcludeDownloaded *Flag[bool] MisconfigScanners *Flag[[]string] + ConfigFileSchemas *Flag[[]string] } type MisconfOptions struct { @@ -136,6 +146,7 @@ type MisconfOptions struct { CloudFormationParamVars []string TfExcludeDownloaded bool MisconfigScanners []analyzer.Type + ConfigFileSchemas []string } func NewMisconfFlagGroup() *MisconfFlagGroup { @@ -154,6 +165,7 @@ func NewMisconfFlagGroup() *MisconfFlagGroup { CloudformationParamVars: CfParamsFlag.Clone(), TerraformExcludeDownloaded: TerraformExcludeDownloaded.Clone(), MisconfigScanners: MisconfigScannersFlag.Clone(), + ConfigFileSchemas: ConfigFileSchemasFlag.Clone(), } } @@ -176,6 +188,7 @@ func (f *MisconfFlagGroup) Flags() []Flagger { f.TerraformExcludeDownloaded, f.CloudformationParamVars, f.MisconfigScanners, + f.ConfigFileSchemas, } } @@ -198,5 +211,6 @@ func (f *MisconfFlagGroup) ToOptions() (MisconfOptions, error) { CloudFormationParamVars: f.CloudformationParamVars.Value(), TfExcludeDownloaded: f.TerraformExcludeDownloaded.Value(), MisconfigScanners: xstrings.ToTSlice[analyzer.Type](f.MisconfigScanners.Value()), + ConfigFileSchemas: f.ConfigFileSchemas.Value(), }, nil } diff --git a/pkg/iac/detection/detect.go b/pkg/iac/detection/detect.go index cec90a79cdca..fcfab15f83b2 100644 --- a/pkg/iac/detection/detect.go +++ b/pkg/iac/detection/detect.go @@ -7,11 +7,13 @@ import ( "path/filepath" "strings" + "github.com/xeipuuv/gojsonschema" "gopkg.in/yaml.v3" "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser/armjson" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/snapshot" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) type FileType string @@ -33,12 +35,12 @@ const ( var matchers = make(map[FileType]func(name string, r io.ReadSeeker) bool) +// TODO(nikita): refactor. If the file matches the schema, it no longer needs to be checked for other scanners. // nolint func init() { matchers[FileTypeJSON] = func(name string, r io.ReadSeeker) bool { - ext := filepath.Ext(filepath.Base(name)) - if !strings.EqualFold(ext, ".json") { + if !isJSON(name) { return false } if resetReader(r) == nil { @@ -50,8 +52,7 @@ func init() { } matchers[FileTypeYAML] = func(name string, r io.ReadSeeker) bool { - ext := filepath.Ext(filepath.Base(name)) - if !strings.EqualFold(ext, ".yaml") && !strings.EqualFold(ext, ".yml") { + if !isYAML(name) { return false } if resetReader(r) == nil { @@ -309,3 +310,42 @@ func resetReader(r io.Reader) io.ReadSeeker { } return ensureSeeker(r) } + +func isJSON(name string) bool { + ext := filepath.Ext(name) + return strings.EqualFold(ext, ".json") +} +func isYAML(name string) bool { + ext := filepath.Ext(name) + return strings.EqualFold(ext, ".yaml") || strings.EqualFold(ext, ".yml") +} + +func IsFileMatchesSchemas(schemas map[string]*gojsonschema.Schema, typ FileType, name string, r io.ReadSeeker) bool { + defer resetReader(r) + + var l gojsonschema.JSONLoader + switch { + case typ == FileTypeJSON && isJSON(name): + b, err := io.ReadAll(r) + if err != nil { + return false + } + l = gojsonschema.NewBytesLoader(b) + case typ == FileTypeYAML && isYAML(name): + var content any + if err := yaml.NewDecoder(r).Decode(&content); err != nil { + return false + } + l = gojsonschema.NewGoLoader(content) + default: + return false + } + + for schemaPath, schema := range schemas { + if res, err := schema.Validate(l); err == nil && res.Valid() { + log.Debug("File matched schema", log.FilePath(name), log.String("schema_path", schemaPath)) + return true + } + } + return false +} diff --git a/pkg/iac/detection/detect_test.go b/pkg/iac/detection/detect_test.go index 20998427d99d..f082220f2f00 100644 --- a/pkg/iac/detection/detect_test.go +++ b/pkg/iac/detection/detect_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/xeipuuv/gojsonschema" ) func Test_Detection(t *testing.T) { @@ -473,3 +474,140 @@ func BenchmarkIsType_BigFile(b *testing.B) { _ = IsType(fmt.Sprintf("./testdata/%s", "big.file"), bytes.NewReader(data), FileTypeAzureARM) } } + +func Test_IsFileMatchesSchemas(t *testing.T) { + + schema := `{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "service": { "type": "string" } + }, + "required": ["service"] +}` + + schema2 := `{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "provider": { "type": "string" } + }, + "required": ["provider"] + }` + + type args struct { + schemas []string + fileType FileType + fileName string + fileContent string + } + tests := []struct { + name string + args args + matches bool + }{ + { + name: "json file matches", + args: args{ + schemas: []string{schema}, + fileType: FileTypeJSON, + fileName: "test.json", + fileContent: `{ + "service": "test" +}`, + }, + matches: true, + }, + { + name: "json file dost not matches", + args: args{ + schemas: []string{schema}, + fileType: FileTypeJSON, + fileName: "test.json", + fileContent: `{ + "somefield": "test", +}`, + }, + matches: false, + }, + { + name: "json file matches, but file type is yaml", + args: args{ + schemas: []string{schema}, + fileType: FileTypeYAML, + fileName: "test.json", + fileContent: `{ + "service": "test" +}`, + }, + matches: false, + }, + { + name: "broken json file", + args: args{ + schemas: []string{schema}, + fileType: FileTypeJSON, + fileName: "test.json", + fileContent: `{ + "service": "test",, +}`, + }, + matches: false, + }, + { + name: "yaml file matches", + args: args{ + schemas: []string{schema}, + fileType: FileTypeYAML, + fileName: "test.yml", + fileContent: `service: test`, + }, + matches: true, + }, + { + name: "yaml file does not matches", + args: args{ + schemas: []string{schema}, + fileType: FileTypeYAML, + fileName: "test.yaml", + fileContent: `somefield: test`, + }, + matches: false, + }, + { + name: "broken yaml file", + args: args{ + schemas: []string{schema}, + fileType: FileTypeYAML, + fileName: "test.yaml", + fileContent: `text foobar +number: 2`, + }, + matches: false, + }, + { + name: "multiple schemas", + args: args{ + schemas: []string{schema, schema2}, + fileType: FileTypeYAML, + fileName: "test.yaml", + fileContent: `provider: test`, + }, + matches: true, + }, + } + for _, tt := range tests { + schemas := make(map[string]*gojsonschema.Schema) + for i, content := range tt.args.schemas { + l := gojsonschema.NewStringLoader(content) + s, err := gojsonschema.NewSchema(l) + require.NoError(t, err) + schemas[fmt.Sprintf("schema-%d.json", i)] = s + } + rs := strings.NewReader(tt.args.fileContent) + got := IsFileMatchesSchemas(schemas, tt.args.fileType, tt.args.fileName, rs) + assert.Equal(t, tt.matches, got) + } +} diff --git a/pkg/iac/rego/build.go b/pkg/iac/rego/build.go index a56ee042fb52..6c84c52617fb 100644 --- a/pkg/iac/rego/build.go +++ b/pkg/iac/rego/build.go @@ -13,7 +13,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/types" ) -func BuildSchemaSetFromPolicies(policies map[string]*ast.Module, paths []string, fsys fs.FS) (*ast.SchemaSet, bool, error) { +func BuildSchemaSetFromPolicies(policies map[string]*ast.Module, paths []string, fsys fs.FS, customSchemas map[string][]byte) (*ast.SchemaSet, bool, error) { schemaSet := ast.NewSchemaSet() schemaSet.Put(ast.MustParseRef("schema.input"), make(map[string]any)) // for backwards compat only var customFound bool @@ -26,9 +26,15 @@ func BuildSchemaSetFromPolicies(policies map[string]*ast.Module, paths []string, continue } + if schemaSet.Get(ss.Schema) != nil { + continue + } + var schema []byte if s, ok := schemas.SchemaMap[types.Source(schemaName)]; ok { schema = []byte(s) + } else if s, ok := customSchemas[schemaName]; ok { + schema = s } else { b, err := findSchemaInFS(paths, fsys, schemaName) if err != nil { @@ -47,7 +53,7 @@ func BuildSchemaSetFromPolicies(policies map[string]*ast.Module, paths []string, return schemaSet, false, fmt.Errorf("could not parse schema %q: %w", schemaName, err) } customFound = true - schemaSet.Put(ast.MustParseRef(ss.Schema.String()), rawSchema) + schemaSet.Put(ss.Schema, rawSchema) } } } diff --git a/pkg/iac/rego/embed.go b/pkg/iac/rego/embed.go index 9d1ac6458c52..6f542d9a0b2b 100644 --- a/pkg/iac/rego/embed.go +++ b/pkg/iac/rego/embed.go @@ -33,7 +33,7 @@ func init() { func RegisterRegoRules(modules map[string]*ast.Module) { ctx := context.TODO() - schemaSet, _, _ := BuildSchemaSetFromPolicies(modules, nil, nil) + schemaSet, _, _ := BuildSchemaSetFromPolicies(modules, nil, nil, make(map[string][]byte)) compiler := ast.NewCompiler(). WithSchemas(schemaSet). diff --git a/pkg/iac/rego/load.go b/pkg/iac/rego/load.go index f2e4c0645c4f..bd474a23be4d 100644 --- a/pkg/iac/rego/load.go +++ b/pkg/iac/rego/load.go @@ -230,7 +230,7 @@ func (s *Scanner) prunePoliciesWithError(compiler *ast.Compiler) error { func (s *Scanner) compilePolicies(srcFS fs.FS, paths []string) error { - schemaSet, custom, err := BuildSchemaSetFromPolicies(s.policies, paths, srcFS) + schemaSet, custom, err := BuildSchemaSetFromPolicies(s.policies, paths, srcFS, s.customSchemas) if err != nil { return err } diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go index f7293c46a0c5..fe0553d6bc00 100644 --- a/pkg/iac/rego/scanner.go +++ b/pkg/iac/rego/scanner.go @@ -65,6 +65,7 @@ type Scanner struct { embeddedLibs map[string]*ast.Module embeddedChecks map[string]*ast.Module + customSchemas map[string][]byte } func (s *Scanner) SetIncludeDeprecatedChecks(b bool) { @@ -142,6 +143,10 @@ func (s *Scanner) SetRegoErrorLimit(limit int) { s.regoErrorLimit = limit } +func (s *Scanner) SetCustomSchemas(v map[string][]byte) { + s.customSchemas = v +} + type DynamicMetadata struct { Warning bool Filepath string @@ -161,6 +166,7 @@ func NewScanner(source types.Source, opts ...options.ScannerOption) *Scanner { sourceType: source, ruleNamespaces: make(map[string]struct{}), runtimeValues: addRuntimeValues(), + customSchemas: make(map[string][]byte), } maps.Copy(s.ruleNamespaces, builtinNamespaces) diff --git a/pkg/iac/rego/scanner_test.go b/pkg/iac/rego/scanner_test.go index 67a80f51c2fd..de2b7427ae8a 100644 --- a/pkg/iac/rego/scanner_test.go +++ b/pkg/iac/rego/scanner_test.go @@ -3,6 +3,7 @@ package rego import ( "bytes" "context" + "io" "io/fs" "os" "path/filepath" @@ -1086,3 +1087,72 @@ deny { }) } } + +func Test_RegoScanner_WithCustomSchemas(t *testing.T) { + + schema := `{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "service": { "type": "string" } + }, + "required": ["service"] +}` + + tests := []struct { + name string + check string + expectedResults int + }{ + { + name: "happy path", + check: `# METADATA +# title: test check +# schemas: +# - input: schema["test"] +package user.test + +deny { + input.service == "test" +} +`, + expectedResults: 1, + }, + { + name: "sad path", + check: `# METADATA +# title: test check +# schemas: +# - input: schema["test"] +package user.test + +deny { + input.other == "test" +} +`, + expectedResults: 0, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + scanner := NewScanner( + types.SourceYAML, + options.ScannerWithCustomSchemas(map[string][]byte{ + "test": []byte(schema), + }), + options.ScannerWithPolicyNamespaces("user"), + ) + err := scanner.LoadPolicies(false, false, nil, nil, []io.Reader{strings.NewReader(tc.check)}) + require.NoError(t, err) + + results, err := scanner.ScanInput(context.TODO(), Input{ + Path: "test.yaml", + Contents: map[string]any{"service": "test"}, + }) + require.NoError(t, err) + require.Len(t, results, tc.expectedResults, tc.name) + }) + } +} diff --git a/pkg/iac/scanners/azure/arm/scanner.go b/pkg/iac/scanners/azure/arm/scanner.go index 871b58df3f3d..fd585a5955c5 100644 --- a/pkg/iac/scanners/azure/arm/scanner.go +++ b/pkg/iac/scanners/azure/arm/scanner.go @@ -39,7 +39,8 @@ type Scanner struct { spec string } -func (s *Scanner) SetIncludeDeprecatedChecks(b bool) {} +func (s *Scanner) SetIncludeDeprecatedChecks(b bool) {} +func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) SetSpec(spec string) { s.spec = spec diff --git a/pkg/iac/scanners/cloudformation/scanner.go b/pkg/iac/scanners/cloudformation/scanner.go index 20b96ce947fd..cedd18ea6297 100644 --- a/pkg/iac/scanners/cloudformation/scanner.go +++ b/pkg/iac/scanners/cloudformation/scanner.go @@ -63,7 +63,8 @@ type Scanner struct { spec string } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) addParserOptions(opt options.ParserOption) { s.parserOptions = append(s.parserOptions, opt) diff --git a/pkg/iac/scanners/dockerfile/scanner.go b/pkg/iac/scanners/dockerfile/scanner.go index 561872c70636..6358aada12c2 100644 --- a/pkg/iac/scanners/dockerfile/scanner.go +++ b/pkg/iac/scanners/dockerfile/scanner.go @@ -34,7 +34,8 @@ type Scanner struct { loadEmbeddedPolicies bool } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) SetSpec(spec string) { s.spec = spec diff --git a/pkg/iac/scanners/helm/scanner.go b/pkg/iac/scanners/helm/scanner.go index fe74911c51bc..944187df4dee 100644 --- a/pkg/iac/scanners/helm/scanner.go +++ b/pkg/iac/scanners/helm/scanner.go @@ -42,7 +42,8 @@ type Scanner struct { mu sync.Mutex } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) SetSpec(spec string) { s.spec = spec diff --git a/pkg/iac/scanners/json/scanner.go b/pkg/iac/scanners/json/scanner.go index 3aa0dffdb485..7a18df363823 100644 --- a/pkg/iac/scanners/json/scanner.go +++ b/pkg/iac/scanners/json/scanner.go @@ -34,7 +34,8 @@ type Scanner struct { loadEmbeddedLibraries bool } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) SetRegoOnly(bool) { } diff --git a/pkg/iac/scanners/kubernetes/scanner.go b/pkg/iac/scanners/kubernetes/scanner.go index c5437f292d85..9612fe03ebe4 100644 --- a/pkg/iac/scanners/kubernetes/scanner.go +++ b/pkg/iac/scanners/kubernetes/scanner.go @@ -38,7 +38,8 @@ type Scanner struct { loadEmbeddedLibraries bool } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) SetSpec(spec string) { s.spec = spec diff --git a/pkg/iac/scanners/options/scanner.go b/pkg/iac/scanners/options/scanner.go index 291400887037..f5b3b982ee67 100644 --- a/pkg/iac/scanners/options/scanner.go +++ b/pkg/iac/scanners/options/scanner.go @@ -24,6 +24,7 @@ type ConfigurableScanner interface { SetRegoErrorLimit(limit int) SetUseEmbeddedLibraries(bool) SetIncludeDeprecatedChecks(bool) + SetCustomSchemas(map[string][]byte) } type ScannerOption func(s ConfigurableScanner) @@ -126,3 +127,9 @@ func ScannerWithRegoErrorLimits(limit int) ScannerOption { s.SetRegoErrorLimit(limit) } } + +func ScannerWithCustomSchemas(schemas map[string][]byte) ScannerOption { + return func(s ConfigurableScanner) { + s.SetCustomSchemas(schemas) + } +} diff --git a/pkg/iac/scanners/terraform/scanner.go b/pkg/iac/scanners/terraform/scanner.go index a64201e1fcc5..5e7cea83abfd 100644 --- a/pkg/iac/scanners/terraform/scanner.go +++ b/pkg/iac/scanners/terraform/scanner.go @@ -45,7 +45,8 @@ type Scanner struct { loadEmbeddedPolicies bool } -func (s *Scanner) SetIncludeDeprecatedChecks(b bool) {} +func (s *Scanner) SetIncludeDeprecatedChecks(b bool) {} +func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) SetSpec(spec string) { s.spec = spec diff --git a/pkg/iac/scanners/terraformplan/tfjson/scanner.go b/pkg/iac/scanners/terraformplan/tfjson/scanner.go index b25eed6ae42b..b390d4d10213 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/scanner.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner.go @@ -38,7 +38,8 @@ type Scanner struct { policyReaders []io.Reader } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) SetUseEmbeddedLibraries(b bool) { s.loadEmbeddedLibraries = b diff --git a/pkg/iac/scanners/toml/scanner.go b/pkg/iac/scanners/toml/scanner.go index 37e1807ac254..b7dc3510da8f 100644 --- a/pkg/iac/scanners/toml/scanner.go +++ b/pkg/iac/scanners/toml/scanner.go @@ -32,7 +32,8 @@ type Scanner struct { loadEmbeddedLibraries bool } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) SetRegoOnly(bool) {} diff --git a/pkg/iac/scanners/yaml/scanner.go b/pkg/iac/scanners/yaml/scanner.go index 534ccbd8cb7b..3ec508fdd6f5 100644 --- a/pkg/iac/scanners/yaml/scanner.go +++ b/pkg/iac/scanners/yaml/scanner.go @@ -32,7 +32,8 @@ type Scanner struct { loadEmbeddedPolicies bool } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} +func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) SetRegoOnly(bool) {} diff --git a/pkg/misconf/config_schema.go b/pkg/misconf/config_schema.go new file mode 100644 index 000000000000..a728bfa01e3b --- /dev/null +++ b/pkg/misconf/config_schema.go @@ -0,0 +1,74 @@ +package misconf + +import ( + "bytes" + "io/fs" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/xeipuuv/gojsonschema" + "golang.org/x/xerrors" +) + +type ConfigFileSchema struct { + path string + name string + source []byte + schema *gojsonschema.Schema +} + +func LoadConfigSchemas(paths []string) ([]*ConfigFileSchema, error) { + var configSchemas []*ConfigFileSchema + for _, path := range paths { + walkFn := func(path string, info fs.DirEntry, err error) error { + if err != nil { + return err + } + if info.IsDir() || !strings.HasSuffix(info.Name(), ".json") { + return nil + } + + schema, err := newConfigFileSchema(path) + if err != nil { + return xerrors.Errorf("load config file schema: %w", err) + } + + configSchemas = append(configSchemas, schema) + return nil + } + if err := filepath.WalkDir(path, walkFn); err != nil { + return nil, xerrors.Errorf("walk error: %w", err) + } + } + + return configSchemas, nil +} + +func newConfigFileSchema(path string) (*ConfigFileSchema, error) { + b, err := os.ReadFile(path) + if err != nil { + return nil, xerrors.Errorf("read config schema error: %w", err) + } + + // Go's regular expression engine does not support \Z + b = bytes.ReplaceAll(b, []byte(`\\Z`), []byte(`$`)) + + // Go's regular expression engine does not support negative lookahead + b = regexp.MustCompile(`\(\?\!.*\)`).ReplaceAll(b, []byte{}) + schema, err := gojsonschema.NewSchema(gojsonschema.NewBytesLoader(b)) + if err != nil { + return nil, xerrors.Errorf("compile config schema %s error: %w", path, err) + } + + fileName := filepath.Base(path) + schemaName := strings.TrimSuffix(fileName, filepath.Ext(fileName)) + + return &ConfigFileSchema{ + path: path, + name: schemaName, + schema: schema, + source: b, + }, nil +} diff --git a/pkg/misconf/config_schema_test.go b/pkg/misconf/config_schema_test.go new file mode 100644 index 000000000000..8290dbdfaf7c --- /dev/null +++ b/pkg/misconf/config_schema_test.go @@ -0,0 +1,41 @@ +package misconf_test + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/misconf" +) + +func Test_LoadConfigSchemas(t *testing.T) { + tests := []struct { + name string + paths []string + want int + }{ + { + name: "load one schema", + paths: []string{ + filepath.Join("testdata", "schemas", "schema1.json"), + }, + want: 1, + }, + { + name: "load dir with schemas", + paths: []string{ + filepath.Join("testdata", "schemas"), + }, + want: 3, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := misconf.LoadConfigSchemas(tt.paths) + require.NoError(t, err) + assert.Len(t, got, tt.want) + }) + } +} diff --git a/pkg/misconf/scanner.go b/pkg/misconf/scanner.go index 8730b03d2faf..5737b3b8d3bc 100644 --- a/pkg/misconf/scanner.go +++ b/pkg/misconf/scanner.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/samber/lo" + "github.com/xeipuuv/gojsonschema" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -22,12 +23,14 @@ import ( cfscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation" cfparser "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" dfscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile" - helm2 "github.com/aquasecurity/trivy/pkg/iac/scanners/helm" + "github.com/aquasecurity/trivy/pkg/iac/scanners/helm" + jsonscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/json" k8sscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" tfprawscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/snapshot" tfpjsonscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson" + yamlscanner "github.com/aquasecurity/trivy/pkg/iac/scanners/yaml" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/mapfs" @@ -43,6 +46,8 @@ var enablediacTypes = map[detection.FileType]types.ConfigType{ detection.FileTypeHelm: types.Helm, detection.FileTypeTerraformPlanJSON: types.TerraformPlanJSON, detection.FileTypeTerraformPlanSnapshot: types.TerraformPlanSnapshot, + detection.FileTypeJSON: types.JSON, + detection.FileTypeYAML: types.YAML, } type ScannerOption struct { @@ -66,6 +71,9 @@ type ScannerOption struct { CloudFormationParamVars []string TfExcludeDownloaded bool K8sVersion string + + FilePatterns []string + ConfigFileSchemas []*ConfigFileSchema } func (o *ScannerOption) Sort() { @@ -75,44 +83,13 @@ func (o *ScannerOption) Sort() { } type Scanner struct { - fileType detection.FileType - scanner scanners.FSScanner - hasFilePattern bool -} - -func NewAzureARMScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeAzureARM, filePatterns, opt) -} - -func NewCloudFormationScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeCloudFormation, filePatterns, opt) -} - -func NewDockerfileScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeDockerfile, filePatterns, opt) -} - -func NewHelmScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeHelm, filePatterns, opt) -} - -func NewKubernetesScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeKubernetes, filePatterns, opt) -} - -func NewTerraformScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeTerraform, filePatterns, opt) + fileType detection.FileType + scanner scanners.FSScanner + hasFilePattern bool + configFileSchemas []*ConfigFileSchema } -func NewTerraformPlanJSONScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeTerraformPlanJSON, filePatterns, opt) -} - -func NewTerraformPlanSnapshotScanner(filePatterns []string, opt ScannerOption) (*Scanner, error) { - return newScanner(detection.FileTypeTerraformPlanSnapshot, filePatterns, opt) -} - -func newScanner(t detection.FileType, filePatterns []string, opt ScannerOption) (*Scanner, error) { +func NewScanner(t detection.FileType, opt ScannerOption) (*Scanner, error) { opts, err := scannerOptions(t, opt) if err != nil { return nil, err @@ -127,7 +104,7 @@ func newScanner(t detection.FileType, filePatterns []string, opt ScannerOption) case detection.FileTypeDockerfile: scanner = dfscanner.NewScanner(opts...) case detection.FileTypeHelm: - scanner = helm2.New(opts...) + scanner = helm.New(opts...) case detection.FileTypeKubernetes: scanner = k8sscanner.NewScanner(opts...) case detection.FileTypeTerraform: @@ -136,12 +113,19 @@ func newScanner(t detection.FileType, filePatterns []string, opt ScannerOption) scanner = tfpjsonscanner.New(opts...) case detection.FileTypeTerraformPlanSnapshot: scanner = tfprawscanner.New(opts...) + case detection.FileTypeYAML: + scanner = yamlscanner.NewScanner(opts...) + case detection.FileTypeJSON: + scanner = jsonscanner.NewScanner(opts...) + default: + return nil, xerrors.Errorf("unknown file type: %s", t) } return &Scanner{ - fileType: t, - scanner: scanner, - hasFilePattern: hasFilePattern(t, filePatterns), + fileType: t, + scanner: scanner, + hasFilePattern: hasFilePattern(t, opt.FilePatterns), + configFileSchemas: opt.ConfigFileSchemas, }, nil } @@ -185,21 +169,31 @@ func (s *Scanner) filterFS(fsys fs.FS) (fs.FS, error) { return fsys, nil } + schemas := lo.SliceToMap(s.configFileSchemas, func(schema *ConfigFileSchema) (string, *gojsonschema.Schema) { + return schema.path, schema.schema + }) + var foundRelevantFile bool filter := func(path string, d fs.DirEntry) (bool, error) { file, err := fsys.Open(path) if err != nil { return false, err } + defer file.Close() + rs, ok := file.(io.ReadSeeker) if !ok { return false, xerrors.Errorf("type assertion error: %w", err) } - defer file.Close() - if !s.hasFilePattern && !detection.IsType(path, rs, s.fileType) { + if len(schemas) > 0 && + (s.fileType == detection.FileTypeYAML || s.fileType == detection.FileTypeJSON) && + !detection.IsFileMatchesSchemas(schemas, s.fileType, path, rs) { + return true, nil + } else if !s.hasFilePattern && !detection.IsType(path, rs, s.fileType) { return true, nil } + foundRelevantFile = true return false, nil } @@ -232,9 +226,15 @@ func scannerOptions(t detection.FileType, opt ScannerOption) ([]options.ScannerO if err != nil { return nil, err } + + schemas := lo.SliceToMap(opt.ConfigFileSchemas, func(schema *ConfigFileSchema) (string, []byte) { + return schema.name, schema.source + }) + opts = append(opts, options.ScannerWithDataDirs(dataPaths...), options.ScannerWithDataFilesystem(dataFS), + options.ScannerWithCustomSchemas(schemas), ) if opt.Debug { @@ -320,27 +320,27 @@ func addCFOpts(opts []options.ScannerOption, scannerOption ScannerOption) ([]opt func addHelmOpts(opts []options.ScannerOption, scannerOption ScannerOption) []options.ScannerOption { if len(scannerOption.HelmValueFiles) > 0 { - opts = append(opts, helm2.ScannerWithValuesFile(scannerOption.HelmValueFiles...)) + opts = append(opts, helm.ScannerWithValuesFile(scannerOption.HelmValueFiles...)) } if len(scannerOption.HelmValues) > 0 { - opts = append(opts, helm2.ScannerWithValues(scannerOption.HelmValues...)) + opts = append(opts, helm.ScannerWithValues(scannerOption.HelmValues...)) } if len(scannerOption.HelmFileValues) > 0 { - opts = append(opts, helm2.ScannerWithFileValues(scannerOption.HelmFileValues...)) + opts = append(opts, helm.ScannerWithFileValues(scannerOption.HelmFileValues...)) } if len(scannerOption.HelmStringValues) > 0 { - opts = append(opts, helm2.ScannerWithStringValues(scannerOption.HelmStringValues...)) + opts = append(opts, helm.ScannerWithStringValues(scannerOption.HelmStringValues...)) } if len(scannerOption.HelmAPIVersions) > 0 { - opts = append(opts, helm2.ScannerWithAPIVersions(scannerOption.HelmAPIVersions...)) + opts = append(opts, helm.ScannerWithAPIVersions(scannerOption.HelmAPIVersions...)) } if scannerOption.HelmKubeVersion != "" { - opts = append(opts, helm2.ScannerWithKubeVersion(scannerOption.HelmKubeVersion)) + opts = append(opts, helm.ScannerWithKubeVersion(scannerOption.HelmKubeVersion)) } return opts diff --git a/pkg/misconf/scanner_test.go b/pkg/misconf/scanner_test.go index 6270b78b65e2..e85caf651a53 100644 --- a/pkg/misconf/scanner_test.go +++ b/pkg/misconf/scanner_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/mapfs" ) @@ -82,8 +83,7 @@ func TestScannerOption_Sort(t *testing.T) { func TestScanner_Scan(t *testing.T) { type fields struct { - filePatterns []string - opt ScannerOption + opt ScannerOption } type file struct { path string @@ -91,7 +91,7 @@ func TestScanner_Scan(t *testing.T) { } tests := []struct { name string - scannerFunc func(filePatterns []string, opt ScannerOption) (*Scanner, error) + fileType detection.FileType fields fields files []file wantFilePath string @@ -99,8 +99,8 @@ func TestScanner_Scan(t *testing.T) { misconfsExpected int }{ { - name: "happy path. Dockerfile", - scannerFunc: NewDockerfileScanner, + name: "happy path. Dockerfile", + fileType: detection.FileTypeDockerfile, fields: fields{ opt: ScannerOption{}, }, @@ -115,11 +115,12 @@ func TestScanner_Scan(t *testing.T) { misconfsExpected: 1, }, { - name: "happy path. Dockerfile with custom file name", - scannerFunc: NewDockerfileScanner, + name: "happy path. Dockerfile with custom file name", + fileType: detection.FileTypeDockerfile, fields: fields{ - filePatterns: []string{"dockerfile:dockerf"}, - opt: ScannerOption{}, + opt: ScannerOption{ + FilePatterns: []string{"dockerfile:dockerf"}, + }, }, files: []file{ { @@ -132,8 +133,8 @@ func TestScanner_Scan(t *testing.T) { misconfsExpected: 1, }, { - name: "happy path. terraform plan file", - scannerFunc: NewTerraformPlanJSONScanner, + name: "happy path. terraform plan file", + fileType: detection.FileTypeTerraformPlanJSON, files: []file{ { path: "main.tfplan.json", @@ -154,7 +155,8 @@ func TestScanner_Scan(t *testing.T) { require.NoError(t, err) } - s, err := tt.scannerFunc(tt.fields.filePatterns, tt.fields.opt) + // s, err := tt.scannerFunc(tt.fields.filePatterns, tt.fields.opt) + s, err := NewScanner(tt.fileType, tt.fields.opt) require.NoError(t, err) misconfs, err := s.Scan(context.Background(), fsys) diff --git a/pkg/misconf/testdata/schemas/no-schema.file b/pkg/misconf/testdata/schemas/no-schema.file new file mode 100644 index 000000000000..7b4d68d70fca --- /dev/null +++ b/pkg/misconf/testdata/schemas/no-schema.file @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/pkg/misconf/testdata/schemas/schema-with-bad-regex.json b/pkg/misconf/testdata/schemas/schema-with-bad-regex.json new file mode 100644 index 000000000000..522a1ccb46e3 --- /dev/null +++ b/pkg/misconf/testdata/schemas/schema-with-bad-regex.json @@ -0,0 +1,9 @@ +{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "patternProperties": { + "^(?![\\.0-9]).": { "type": "string" }, + "test\\Z": { "type": "string" } + } +} diff --git a/pkg/misconf/testdata/schemas/schema1.json b/pkg/misconf/testdata/schemas/schema1.json new file mode 100644 index 000000000000..f72cdb0d2016 --- /dev/null +++ b/pkg/misconf/testdata/schemas/schema1.json @@ -0,0 +1,9 @@ +{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "service": { "type": "string" } + }, + "required": ["service"] +} \ No newline at end of file diff --git a/pkg/misconf/testdata/schemas/schema2.json b/pkg/misconf/testdata/schemas/schema2.json new file mode 100644 index 000000000000..f72cdb0d2016 --- /dev/null +++ b/pkg/misconf/testdata/schemas/schema2.json @@ -0,0 +1,9 @@ +{ + "$id": "https://example.com/test.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "service": { "type": "string" } + }, + "required": ["service"] +} \ No newline at end of file From db2c95598da098ca610825089eb4ab63b789b215 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 21 Aug 2024 07:01:30 +0600 Subject: [PATCH 294/352] feat(misconf): variable support for Terraform Plan (#7228) Signed-off-by: nikpivkin --- go.mod | 2 + go.sum | 4 + magefiles/magefile.go | 23 +- pkg/iac/scanners/terraform/parser/option.go | 11 + pkg/iac/scanners/terraform/parser/parser.go | 15 +- .../scanners/terraform/parser/parser_test.go | 27 +++ .../scanners/terraformplan/snapshot/plan.go | 64 +++++ .../snapshot/planproto/planfile.pb.go | 222 ++++++++++++++++++ .../snapshot/planproto/planfile.proto | 12 + .../terraformplan/snapshot/scanner.go | 5 + .../terraformplan/snapshot/scanner_test.go | 5 +- .../terraformplan/snapshot/snapshot.go | 24 +- .../terraformplan/snapshot/snapshot_test.go | 16 ++ .../with-var/checks/s3-bucket-name.rego | 21 ++ .../snapshot/testdata/with-var/main.tf | 5 + .../snapshot/testdata/with-var/tfplan | Bin 0 -> 2364 bytes 16 files changed, 446 insertions(+), 10 deletions(-) create mode 100644 pkg/iac/scanners/terraformplan/snapshot/plan.go create mode 100644 pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.pb.go create mode 100644 pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto create mode 100644 pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/checks/s3-bucket-name.rego create mode 100644 pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/main.tf create mode 100644 pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/tfplan diff --git a/go.mod b/go.mod index 31315bc0f550..192669315152 100644 --- a/go.mod +++ b/go.mod @@ -358,6 +358,8 @@ require ( github.com/transparency-dev/merkle v0.0.2 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/vbatts/tar-split v0.11.5 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect diff --git a/go.sum b/go.sum index c34a319faf06..035cf8935436 100644 --- a/go.sum +++ b/go.sum @@ -1368,7 +1368,11 @@ github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/X github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4= github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= diff --git a/magefiles/magefile.go b/magefiles/magefile.go index 7ce148d885a0..b1dbcd3439ad 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -30,6 +30,10 @@ var ( } ) +var protoFiles = []string{ + "pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto", +} + func init() { slog.SetDefault(log.New(log.NewHandler(os.Stderr, nil))) // stdout is suppressed in mage } @@ -154,11 +158,11 @@ func Mock(dir string) error { func Protoc() error { // It is called in the protoc container if _, ok := os.LookupEnv("TRIVY_PROTOC_CONTAINER"); ok { - protoFiles, err := findProtoFiles() + rpcProtoFiles, err := findRPCProtoFiles() if err != nil { return err } - for _, file := range protoFiles { + for _, file := range rpcProtoFiles { // Check if the generated Go file is up-to-date dst := strings.TrimSuffix(file, ".proto") + ".pb.go" if updated, err := target.Path(dst, file); err != nil { @@ -173,6 +177,13 @@ func Protoc() error { return err } } + + for _, file := range protoFiles { + if err := sh.RunV("protoc", ".", "paths=source_relative", "--go_out", ".", "--go_opt", + "paths=source_relative", file); err != nil { + return err + } + } return nil } @@ -331,11 +342,13 @@ func Fmt() error { } // Format proto files - protoFiles, err := findProtoFiles() + rpcProtoFiles, err := findRPCProtoFiles() if err != nil { return err } - for _, file := range protoFiles { + + allProtoFiles := append(protoFiles, rpcProtoFiles...) + for _, file := range allProtoFiles { if err = sh.Run("clang-format", "-i", file); err != nil { return err } @@ -422,7 +435,7 @@ func (Docs) Generate() error { return sh.RunWith(ENV, "go", "run", "-tags=mage_docs", "./magefiles") } -func findProtoFiles() ([]string, error) { +func findRPCProtoFiles() ([]string, error) { var files []string err := filepath.WalkDir("rpc", func(path string, d fs.DirEntry, err error) error { switch { diff --git a/pkg/iac/scanners/terraform/parser/option.go b/pkg/iac/scanners/terraform/parser/option.go index 887899496f1b..76146a4ea6bf 100644 --- a/pkg/iac/scanners/terraform/parser/option.go +++ b/pkg/iac/scanners/terraform/parser/option.go @@ -3,12 +3,15 @@ package parser import ( "io/fs" + "github.com/zclconf/go-cty/cty" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) type ConfigurableTerraformParser interface { options.ConfigurableParser SetTFVarsPaths(...string) + SetTFVars(vars map[string]cty.Value) SetStopOnHCLError(bool) SetWorkspaceName(string) SetAllowDownloads(bool) @@ -26,6 +29,14 @@ func OptionWithTFVarsPaths(paths ...string) options.ParserOption { } } +func OptionsWithTfVars(vars map[string]cty.Value) options.ParserOption { + return func(p options.ConfigurableParser) { + if tf, ok := p.(ConfigurableTerraformParser); ok { + tf.SetTFVars(vars) + } + } +} + func OptionStopOnHCLError(stop bool) options.ParserOption { return func(p options.ConfigurableParser) { if tf, ok := p.(ConfigurableTerraformParser); ok { diff --git a/pkg/iac/scanners/terraform/parser/parser.go b/pkg/iac/scanners/terraform/parser/parser.go index 4f79c1fdf6f2..a5b2b909a462 100644 --- a/pkg/iac/scanners/terraform/parser/parser.go +++ b/pkg/iac/scanners/terraform/parser/parser.go @@ -39,6 +39,7 @@ type Parser struct { moduleBlock *terraform.Block files []sourceFile tfvarsPaths []string + tfvars map[string]cty.Value stopOnHCLError bool workspaceName string underlying *hclparse.Parser @@ -59,6 +60,10 @@ func (p *Parser) SetTFVarsPaths(s ...string) { p.tfvarsPaths = s } +func (p *Parser) SetTFVars(vars map[string]cty.Value) { + p.tfvars = vars +} + func (p *Parser) SetStopOnHCLError(b bool) { p.stopOnHCLError = b } @@ -90,6 +95,7 @@ func New(moduleFS fs.FS, moduleSource string, opts ...options.ParserOption) *Par moduleFS: moduleFS, moduleSource: moduleSource, configsFS: moduleFS, + tfvars: make(map[string]cty.Value), } for _, option := range opts { @@ -215,10 +221,15 @@ func (p *Parser) Load(ctx context.Context) (*evaluator, error) { p.debug.Log("Read %d block(s) and %d ignore(s) for module '%s' (%d file[s])...", len(blocks), len(ignores), p.moduleName, len(p.files)) var inputVars map[string]cty.Value - if p.moduleBlock != nil { + + switch { + case p.moduleBlock != nil: inputVars = p.moduleBlock.Values().AsValueMap() p.debug.Log("Added %d input variables from module definition.", len(inputVars)) - } else { + case len(p.tfvars) > 0: + inputVars = p.tfvars + p.debug.Log("Added %d input variables from tfvars.", len(inputVars)) + default: inputVars, err = loadTFVars(p.configsFS, p.tfvarsPaths) if err != nil { return nil, err diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index e7a8041aa7ff..df06dd2aa393 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -1746,6 +1746,33 @@ func TestTFVarsFileDoesNotExist(t *testing.T) { assert.ErrorContains(t, err, "file does not exist") } +func Test_OptionsWithTfVars(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "main.tf": `resource "test" "this" { + foo = var.foo +} +variable "foo" {} +`}) + + parser := New(fs, "", OptionsWithTfVars( + map[string]cty.Value{ + "foo": cty.StringVal("bar"), + }, + )) + + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + modules, _, err := parser.EvaluateAll(context.TODO()) + require.NoError(t, err) + assert.Len(t, modules, 1) + + rootModule := modules[0] + + blocks := rootModule.GetResourcesByType("test") + assert.Len(t, blocks, 1) + assert.Equal(t, "bar", blocks[0].GetAttribute("foo").Value().AsString()) +} + func TestDynamicWithIterator(t *testing.T) { fsys := fstest.MapFS{ "main.tf": &fstest.MapFile{ diff --git a/pkg/iac/scanners/terraformplan/snapshot/plan.go b/pkg/iac/scanners/terraformplan/snapshot/plan.go new file mode 100644 index 000000000000..3f0c3adcd791 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/plan.go @@ -0,0 +1,64 @@ +package snapshot + +import ( + "fmt" + "io" + + "github.com/zclconf/go-cty/cty" + ctymsgpack "github.com/zclconf/go-cty/cty/msgpack" + "google.golang.org/protobuf/proto" + + "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/snapshot/planproto" +) + +type DynamicValue []byte + +func (v DynamicValue) Decode(ty cty.Type) (cty.Value, error) { + if v == nil { + return cty.NilVal, nil + } + + return ctymsgpack.Unmarshal([]byte(v), ty) +} + +type Plan struct { + variableValues map[string]DynamicValue +} + +func (p Plan) inputVariables() (map[string]cty.Value, error) { + vars := make(map[string]cty.Value) + for k, v := range p.variableValues { + val, err := v.Decode(cty.DynamicPseudoType) + if err != nil { + return nil, err + } + vars[k] = val + } + return vars, nil +} + +func readTfPlan(r io.Reader) (*Plan, error) { + b, err := io.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("failed to read plan: %w", err) + } + + var rawPlan planproto.Plan + if err := proto.Unmarshal(b, &rawPlan); err != nil { + return nil, fmt.Errorf("failed to unmarshal plan: %w", err) + } + + plan := Plan{ + variableValues: make(map[string]DynamicValue), + } + + for k, v := range rawPlan.Variables { + if len(v.Msgpack) == 0 { // len(0) because that's the default value for a "bytes" in protobuf + return nil, fmt.Errorf("dynamic value does not have msgpack serialization") + } + + plan.variableValues[k] = DynamicValue(v.Msgpack) + } + + return &plan, nil +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.pb.go b/pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.pb.go new file mode 100644 index 000000000000..1aa3ae5a7ceb --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.pb.go @@ -0,0 +1,222 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc v5.27.1 +// source: pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto + +package planproto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type DynamicValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Msgpack []byte `protobuf:"bytes,1,opt,name=msgpack,proto3" json:"msgpack,omitempty"` +} + +func (x *DynamicValue) Reset() { + *x = DynamicValue{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DynamicValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DynamicValue) ProtoMessage() {} + +func (x *DynamicValue) ProtoReflect() protoreflect.Message { + mi := &file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DynamicValue.ProtoReflect.Descriptor instead. +func (*DynamicValue) Descriptor() ([]byte, []int) { + return file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescGZIP(), []int{0} +} + +func (x *DynamicValue) GetMsgpack() []byte { + if x != nil { + return x.Msgpack + } + return nil +} + +type Plan struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Variables map[string]*DynamicValue `protobuf:"bytes,2,rep,name=variables,proto3" json:"variables,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *Plan) Reset() { + *x = Plan{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Plan) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Plan) ProtoMessage() {} + +func (x *Plan) ProtoReflect() protoreflect.Message { + mi := &file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Plan.ProtoReflect.Descriptor instead. +func (*Plan) Descriptor() ([]byte, []int) { + return file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescGZIP(), []int{1} +} + +func (x *Plan) GetVariables() map[string]*DynamicValue { + if x != nil { + return x.Variables + } + return nil +} + +var File_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto protoreflect.FileDescriptor + +var file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDesc = []byte{ + 0x0a, 0x40, 0x70, 0x6b, 0x67, 0x2f, 0x69, 0x61, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, + 0x72, 0x73, 0x2f, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x70, 0x6c, 0x61, 0x6e, + 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x06, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x79, + 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x73, + 0x67, 0x70, 0x61, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x73, 0x67, + 0x70, 0x61, 0x63, 0x6b, 0x22, 0x95, 0x01, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x39, 0x0a, + 0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x56, + 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x76, + 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x1a, 0x52, 0x0a, 0x0e, 0x56, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66, + 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x4d, 0x5a, 0x4b, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, + 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x69, 0x61, + 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x2f, 0x74, 0x65, 0x72, 0x72, 0x61, + 0x66, 0x6f, 0x72, 0x6d, 0x70, 0x6c, 0x61, 0x6e, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescOnce sync.Once + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescData = file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDesc +) + +func file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescGZIP() []byte { + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescOnce.Do(func() { + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescData) + }) + return file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescData +} + +var file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_goTypes = []interface{}{ + (*DynamicValue)(nil), // 0: tfplan.DynamicValue + (*Plan)(nil), // 1: tfplan.Plan + nil, // 2: tfplan.Plan.VariablesEntry +} +var file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_depIdxs = []int32{ + 2, // 0: tfplan.Plan.variables:type_name -> tfplan.Plan.VariablesEntry + 0, // 1: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_init() } +func file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_init() { + if File_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DynamicValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Plan); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_goTypes, + DependencyIndexes: file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_depIdxs, + MessageInfos: file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes, + }.Build() + File_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto = out.File + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDesc = nil + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_goTypes = nil + file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_depIdxs = nil +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto b/pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto new file mode 100644 index 000000000000..c5615b819e66 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package tfplan; + +option go_package = "github.com/aquasecurity/trivy/iac/scanners/terraformplan/snapshot/planproto"; + +message DynamicValue { + bytes msgpack = 1; +} + +message Plan { + map variables = 2; +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/scanner.go b/pkg/iac/scanners/terraformplan/snapshot/scanner.go index e2a8d2807fa1..3c8dcc8fce0b 100644 --- a/pkg/iac/scanners/terraformplan/snapshot/scanner.go +++ b/pkg/iac/scanners/terraformplan/snapshot/scanner.go @@ -10,6 +10,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" terraformScanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" + tfparser "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" ) type Scanner struct { @@ -71,5 +72,9 @@ func (s *Scanner) Scan(ctx context.Context, reader io.Reader) (scan.Results, err if err != nil { return nil, fmt.Errorf("failed to convert snapshot to FS: %w", err) } + + s.inner.AddParserOptions( + tfparser.OptionsWithTfVars(snap.inputVariables), + ) return s.inner.ScanFS(ctx, fsys, ".") } diff --git a/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go b/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go index 9eca249c1f16..6e7299e19dd1 100644 --- a/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go @@ -98,6 +98,10 @@ func Test_ScanFS(t *testing.T) { dir: "with-remote-module", expectedIDs: []string{"ID001"}, }, + { + dir: "with-var", + expectedIDs: []string{"ID001"}, + }, } for _, tc := range tests { @@ -133,5 +137,4 @@ func Test_ScanFS(t *testing.T) { assert.Equal(t, tc.expectedIDs, ids) }) } - } diff --git a/pkg/iac/scanners/terraformplan/snapshot/snapshot.go b/pkg/iac/scanners/terraformplan/snapshot/snapshot.go index e32e47790c32..72556a984479 100644 --- a/pkg/iac/scanners/terraformplan/snapshot/snapshot.go +++ b/pkg/iac/scanners/terraformplan/snapshot/snapshot.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/liamg/memoryfs" + "github.com/zclconf/go-cty/cty" iox "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -74,7 +75,8 @@ func parseSnapshot(r io.Reader) (*snapshot, error) { } snap := &snapshot{ - modules: make(map[string]*snapshotModule), + modules: make(map[string]*snapshotModule), + inputVariables: make(map[string]cty.Value), } var moduleManifest configSnapshotModuleManifest @@ -91,6 +93,23 @@ func parseSnapshot(r io.Reader) (*snapshot, error) { if err := snap.addFile(file); err != nil { return nil, err } + case file.Name == tfplanFilename: + r, err := file.Open() + if err != nil { + return nil, fmt.Errorf("failed to open plan: %w", err) + } + + plan, err := readTfPlan(r) + if err != nil { + _ = r.Close() + return nil, fmt.Errorf("failed to read tfplan: %w", err) + } + _ = r.Close() + + snap.inputVariables, err = plan.inputVariables() + if err != nil { + return nil, err + } } } @@ -140,7 +159,8 @@ type ( } snapshot struct { - modules map[string]*snapshotModule + modules map[string]*snapshotModule + inputVariables map[string]cty.Value } ) diff --git a/pkg/iac/scanners/terraformplan/snapshot/snapshot_test.go b/pkg/iac/scanners/terraformplan/snapshot/snapshot_test.go index 22f26e5e7b6b..c85791b2f283 100644 --- a/pkg/iac/scanners/terraformplan/snapshot/snapshot_test.go +++ b/pkg/iac/scanners/terraformplan/snapshot/snapshot_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" ) func TestReadSnapshot(t *testing.T) { @@ -108,3 +109,18 @@ func TestIsPlanSnapshot(t *testing.T) { assert.False(t, got) }) } + +func TestPlanWithVariables(t *testing.T) { + f, err := os.Open(filepath.Join("testdata", "with-var", "tfplan")) + require.NoError(t, err) + defer f.Close() + + snapshot, err := parseSnapshot(f) + require.NoError(t, err) + require.NotNil(t, snapshot) + + expectedVars := map[string]cty.Value{ + "bucket_name": cty.StringVal("test-bucket"), + } + assert.Equal(t, expectedVars, snapshot.inputVariables) +} diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/checks/s3-bucket-name.rego b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/checks/s3-bucket-name.rego new file mode 100644 index 000000000000..798b1b705a49 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/checks/s3-bucket-name.rego @@ -0,0 +1,21 @@ +# METADATA +# title: Test rego +# description: A bucket named "test-bucket" is not allowed +# schemas: +# - input: schema["cloud"] +# custom: +# avd_id: ID001 +# severity: LOW +# input: +# selector: +# - type: cloud +# subtypes: +# - service: s3 +# provider: aws +package user.aws.ID001 + +deny[res] { + bucket := input.aws.s3.buckets[_] + bucket.name.value == "test-bucket" + res := result.new("Bucket not allowed", bucket.name) +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/main.tf b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/main.tf new file mode 100644 index 000000000000..a6fc475bfb3e --- /dev/null +++ b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/main.tf @@ -0,0 +1,5 @@ +variable "bucket_name" {} + +resource "aws_s3_bucket" "this" { + bucket = var.bucket_name +} \ No newline at end of file diff --git a/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/tfplan b/pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/tfplan new file mode 100644 index 0000000000000000000000000000000000000000..78be033f53d4cefbfb73bc2bf9805b93a2b4011e GIT binary patch literal 2364 zcmWIWW@Zs#-~htQ%|9a;kN_J4CqqeEK~7>`Xb39Djr$A;WaLdHKDRy}Qr6J8BpxRmUW&+oHmv>a@hkgzcj+A1Buj z-4``|9Gu~AdheatQ~BA1yZ74LXHS<1NWa+GrnoK7Wc}Vh+XO89gPtF(?myRFTKDnv zpTDWzX~D}p{Fx5LSouHRwsFPYlU>^u)>Ja&DYPo?=1~^6sr|5eqrvrG7c=Ve|D>7w zJ8*<}JXm`1PUVMIZOLk8C*S%d-p9VZ%1l?UpTp^MR$$4zS2~i`vldP>m>jK|JU`uG z>6wgx=dbQ_I`;)HTy}P*Vxx`g^OHUM_nqb3qAzJJC-L0y>G@tay~~p}UcdZ3;v~<} z2o0Om8{ul_m!9)JW;ZiMw)Od`4qm;Ub189Gyr=cdo#VTx%_Y%wM|$t1q!`z&_qy6I zXlZ6upZhE$Xs3OlGB2?w#OT}SY0kG?9{DXh5y&ld`+wQHsuOpgEU2`kU+z9r27`TW-Uht~RZw*1|Pl`Lz=(WBKFH=Ca2;?sz;+{Xw2WyJLNI zy5@nrNveA-xB?kk=JKs;%f5LlN@D$y)rCq`NvHawQBqJr`aO8?} zj+atC26ah_oVr}I{BCwR|MPVvi{E)kd_DB;uKUHkbuwQc-+OG&$iVRbe}FeT$Evm8 z+BBFL7_68X7y@u6GInqxD=tYaNrk5}mo8f_Cqtfw@AH&WrnPq|e9=J}8G zmJN3I3WMF7oS&DLnXaF!tDl>gnWtBh26wiWx6UaapHrTp0bW|Z=lpa#&wHQo4Gmhc z?&%{fFWqzcx?X-jDINVjeY3vy6#+p3S|?BFgaxg578KCHp?O1VimKR4#hC{}xjA6r zI6wYCw;wPJBk_i#D2luCQ%ZAEi}kXK^AW{N7tjijH9+vmZ*9n?pn#xDjRmnr-3~Ff zs(P%gQR^ZcfL1`#+1#bsvYJ312;+`CP+;hlq!tw=rsWsq>gD7oXX|Ao=fDFatxZpN-)${xB`WQnG`3AXoCLV4STr%VC+>Lw+6HU%9DaA zOU`fl`F#1@+L&W6i|VrPU#tCp8HF`R6Wn$<-PxXkXPn^ zj`wf=+Vu19yl?#H$H#}Ke|@<9JKsLOZrA?*Pfsj=C;$Kd)9e1{pPybX-?x1C-~azk z|EsB)zM1Wt8vNfg*-Ig^{qS#{Q7gCong9a<-R*5>e>$h4yuRkMEgeVVcJ*bJR(S!XYF z1%&cBZCcRiwKs9LM}Xq_zDv3*x-}TZcQ4ht#W34!xk+D;Zqf?r=`tK0Y7_e z$g;^evcYP`9*bYRrcuKBk{1iI!u7AZ^Qyd!Sm|CqiK&MpD95U;RQ6Ui?`DJVMc!Wx zS`$*;bl%P2-M~?P;%JxQZsi>Ye4+2cRy><$Dxy=IxRyO--VeT?L*I5qv z>-P4 Date: Wed, 21 Aug 2024 08:37:42 +0600 Subject: [PATCH 295/352] fix: safely check if the directory exists (#7353) Signed-off-by: nikpivkin --- pkg/utils/fsutils/fs.go | 14 +++----- pkg/utils/fsutils/fs_test.go | 62 ++++++++++++++++++++++++++---------- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/pkg/utils/fsutils/fs.go b/pkg/utils/fsutils/fs.go index e0518236f9a8..b2aea51dda0a 100644 --- a/pkg/utils/fsutils/fs.go +++ b/pkg/utils/fsutils/fs.go @@ -1,7 +1,6 @@ package fsutils import ( - "errors" "fmt" "io" "io/fs" @@ -59,18 +58,13 @@ func CopyFile(src, dst string) (int64, error) { } func DirExists(path string) bool { - if f, err := os.Stat(path); os.IsNotExist(err) || !f.IsDir() { - return false - } - return true + f, err := os.Stat(path) + return err == nil && f.IsDir() } func FileExists(filename string) bool { - _, err := os.Stat(filename) - if errors.Is(err, os.ErrNotExist) { - return false - } - return err == nil + f, err := os.Stat(filename) + return err == nil && !f.IsDir() } type WalkDirRequiredFunc func(path string, d fs.DirEntry) bool diff --git a/pkg/utils/fsutils/fs_test.go b/pkg/utils/fsutils/fs_test.go index fef4b7f5a32c..42a4b113a5ef 100644 --- a/pkg/utils/fsutils/fs_test.go +++ b/pkg/utils/fsutils/fs_test.go @@ -2,29 +2,13 @@ package fsutils import ( "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func touch(t *testing.T, name string) { - f, err := os.Create(name) - if err != nil { - t.Fatal(err) - } - if err := f.Close(); err != nil { - t.Fatal(err) - } -} - -func write(t *testing.T, name, content string) { - err := os.WriteFile(name, []byte(content), 0666) - if err != nil { - t.Fatal(err) - } -} - func TestCopyFile(t *testing.T) { type args struct { src string @@ -72,3 +56,47 @@ func TestCopyFile(t *testing.T) { }) } } + +func TestDirExists(t *testing.T) { + t.Run("invalid path", func(t *testing.T) { + assert.False(t, DirExists("\000invalid:path")) + }) + + t.Run("valid path", func(t *testing.T) { + assert.True(t, DirExists(t.TempDir())) + }) + + t.Run("dir not exist", func(t *testing.T) { + assert.False(t, DirExists(filepath.Join(t.TempDir(), "tmp"))) + }) + + t.Run("file path", func(t *testing.T) { + filePath := filepath.Join(t.TempDir(), "tmp") + f, err := os.Create(filePath) + require.NoError(t, f.Close()) + require.NoError(t, err) + assert.False(t, DirExists(filePath)) + }) +} + +func TestFileExists(t *testing.T) { + t.Run("invalid path", func(t *testing.T) { + assert.False(t, FileExists("\000invalid:path")) + }) + + t.Run("valid path", func(t *testing.T) { + filePath := filepath.Join(t.TempDir(), "tmp") + f, err := os.Create(filePath) + require.NoError(t, f.Close()) + require.NoError(t, err) + assert.True(t, FileExists(filePath)) + }) + + t.Run("file not exist", func(t *testing.T) { + assert.False(t, FileExists(filepath.Join(t.TempDir(), "tmp"))) + }) + + t.Run("dir path", func(t *testing.T) { + assert.False(t, FileExists(t.TempDir())) + }) +} From 3f0e7ebe0dfaca16d073f56c484c00c65c7f025a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 06:38:42 +0400 Subject: [PATCH 296/352] chore(deps): bump the aws group across 1 directory with 7 updates (#7358) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 32 ++++++++++++++--------------- go.sum | 64 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/go.mod b/go.mod index 192669315152..1465ad2b2343 100644 --- a/go.mod +++ b/go.mod @@ -29,14 +29,14 @@ require ( github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b - github.com/aws/aws-sdk-go-v2 v1.30.3 - github.com/aws/aws-sdk-go-v2/config v1.27.27 - github.com/aws/aws-sdk-go-v2/credentials v1.17.27 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 - github.com/aws/aws-sdk-go-v2/service/ecr v1.31.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 - github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect - github.com/aws/smithy-go v1.20.3 + github.com/aws/aws-sdk-go-v2 v1.30.4 + github.com/aws/aws-sdk-go-v2/config v1.27.28 + github.com/aws/aws-sdk-go-v2/credentials v1.17.28 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.175.1 + github.com/aws/aws-sdk-go-v2/service/ecr v1.32.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.59.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 // indirect + github.com/aws/smithy-go v1.20.4 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cenkalti/backoff/v4 v4.3.0 @@ -171,15 +171,15 @@ require ( github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.54.6 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index 035cf8935436..1a1e285e8de6 100644 --- a/go.sum +++ b/go.sum @@ -365,44 +365,44 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g= github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= -github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= -github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= -github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= -github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= -github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8= +github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2/config v1.27.28 h1:OTxWGW/91C61QlneCtnD62NLb4W616/NM1jA8LhJqbg= +github.com/aws/aws-sdk-go-v2/config v1.27.28/go.mod h1:uzVRVtJSU5EFv6Fu82AoVFKozJi2ZCY6WRCXj06rbvs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.28 h1:m8+AHY/ND8CMHJnPoH7PJIRakWGa4gbfbxuY9TGTUXM= +github.com/aws/aws-sdk-go-v2/credentials v1.17.28/go.mod h1:6TF7dSc78ehD1SL6KpRIPKMA1GyyWflIkjqg+qmf4+c= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16/go.mod h1:2DwJF39FlNAUiX5pAc0UNeiz16lK2t7IaFcm0LFHEgc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 h1:jYfy8UPmd+6kJW5YhY0L1/KftReOGxI/4NtVSTh9O/I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16/go.mod h1:7ZfEPZxkW42Afq4uQB8H2E2e6ebh6mXTueEpYzjCzcs= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 h1:ta62lid9JkIpKZtZZXSj6rP2AqY5x1qYGq53ffxqD9Q= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0/go.mod h1:o6QDjdVKpP5EF0dp/VlvqckzuSDATr1rLdHt3A5m0YY= -github.com/aws/aws-sdk-go-v2/service/ecr v1.31.0 h1:vi/MwojjLGATEEUFn2GEdLiom7CFlB+qCIx4tDWqKfQ= -github.com/aws/aws-sdk-go-v2/service/ecr v1.31.0/go.mod h1:RhaP7Wil0+uuuhiE4FzOOEFZwkmFAk1ZflXzK+O3ptU= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.175.1 h1:7B5ppg4i5N2B6t+aH77WLbAu8sD98MLlzruWzq5scyY= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.175.1/go.mod h1:ISODge3zgdwOEa4Ou6WM9PKbxJWJ15DYKnr2bfmCAIA= +github.com/aws/aws-sdk-go-v2/service/ecr v1.32.1 h1:PxM8EHsv1sd9eWGamMQCvqBEjxytK5kAwjrxlfG3tac= +github.com/aws/aws-sdk-go-v2/service/ecr v1.32.1/go.mod h1:kdk+WJbHcGVbIlRQfSrKyuKkbWDdD8I9NScyS5vZ8eQ= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 h1:PpbXaecV3sLAS6rjQiaKw4/jyq3Z8gNzmoJupHAoBp0= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2/go.mod h1:fUHpGXr4DrXkEDpGAjClPsviWf+Bszeb0daKE0blxv8= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 h1:yS0JkEdV6h9JOo8sy2JSpjX+i7vsKifU8SIeHrqiDhU= github.com/aws/aws-sdk-go-v2/service/kms v1.30.0/go.mod h1:+I8VUUSVD4p5ISQtzpgSva4I8cJ4SQ4b1dcBcof7O+g= -github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 h1:hT8ZAZRIfqBqHbzKTII+CIiY8G2oC9OpLedkZ51DWl8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= -github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= -github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/aws-sdk-go-v2/service/s3 v1.59.0 h1:Cso4Ev/XauMVsbwdhYEoxg8rxZWw43CFqqaPB5w3W2c= +github.com/aws/aws-sdk-go-v2/service/s3 v1.59.0/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 h1:iAckBT2OeEK/kBDyN/jDtpEExhjeeA/Im2q4X0rJZT8= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.4/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= +github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= +github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8/go.mod h1:2JF49jcDOrLStIXN/j/K1EKRq8a8R2qRnlZA6/o/c7c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= From 24a45636867b893ff54c5ce07197f3b5c6db1d9b Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 21 Aug 2024 09:26:11 +0400 Subject: [PATCH 297/352] feat(server): add internal `--path-prefix` flag for client/server mode (#7321) Signed-off-by: knqyf263 --- .../references/configuration/config-file.md | 15 ----- integration/client_server_test.go | 59 ++++++++++++------- magefiles/docs.go | 2 +- pkg/cache/remote.go | 9 ++- pkg/commands/artifact/run.go | 6 +- pkg/commands/server/run.go | 2 +- pkg/flag/options.go | 30 ++++++++-- pkg/flag/remote_flags.go | 21 ++++++- pkg/rpc/client/client.go | 19 +++--- pkg/rpc/server/listen.go | 32 ++++++---- pkg/rpc/server/listen_test.go | 12 ++-- 11 files changed, 130 insertions(+), 77 deletions(-) diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index f3edb6a8e2b6..932710f6e2b9 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -33,9 +33,6 @@ cache: # Same as '--cache-backend' backend: "fs" - # Same as '--clear-cache' - clear: false - redis: # Same as '--redis-ca' ca: "" @@ -112,9 +109,6 @@ db: # Same as '--skip-java-db-update' java-skip-update: false - # Same as '--light' - light: false - # Same as '--no-progress' no-progress: false @@ -124,9 +118,6 @@ db: # Same as '--skip-db-update' skip-update: false -# Same as '--reset' -reset: false - ``` ## Image options @@ -411,9 +402,6 @@ misconfiguration: # Same as '--include-non-failures' include-non-failures: false - # Same as '--reset-checks-bundle' - reset-checks-bundle: false - # Same as '--misconfig-scanners' scanners: - azure-arm @@ -580,9 +568,6 @@ scan: # Same as '--skip-files' skip-files: [] - # Same as '--slow' - slow: false - ``` ## Secret options diff --git a/integration/client_server_test.go b/integration/client_server_test.go index e0edce7aa6a8..adeab3f4c7b3 100644 --- a/integration/client_server_test.go +++ b/integration/client_server_test.go @@ -32,6 +32,7 @@ type csArgs struct { Input string ClientToken string ClientTokenHeader string + PathPrefix string ListAllPackages bool Target string secretConfig string @@ -443,7 +444,11 @@ func TestClientServerWithCycloneDX(t *testing.T) { } } -func TestClientServerWithToken(t *testing.T) { +func TestClientServerWithCustomOptions(t *testing.T) { + token := "token" + tokenHeader := "Trivy-Token" + pathPrefix := "prefix" + tests := []struct { name string args csArgs @@ -451,11 +456,12 @@ func TestClientServerWithToken(t *testing.T) { wantErr string }{ { - name: "alpine 3.9 with token", + name: "alpine 3.9 with token and prefix", args: csArgs{ Input: "testdata/fixtures/images/alpine-39.tar.gz", - ClientToken: "token", - ClientTokenHeader: "Trivy-Token", + ClientToken: token, + ClientTokenHeader: tokenHeader, + PathPrefix: pathPrefix, }, golden: "testdata/alpine-39.json.golden", }, @@ -464,7 +470,8 @@ func TestClientServerWithToken(t *testing.T) { args: csArgs{ Input: "testdata/fixtures/images/distroless-base.tar.gz", ClientToken: "invalidtoken", - ClientTokenHeader: "Trivy-Token", + ClientTokenHeader: tokenHeader, + PathPrefix: pathPrefix, }, wantErr: "twirp error unauthenticated: invalid token", }, @@ -472,18 +479,28 @@ func TestClientServerWithToken(t *testing.T) { name: "invalid token header", args: csArgs{ Input: "testdata/fixtures/images/distroless-base.tar.gz", - ClientToken: "token", + ClientToken: token, ClientTokenHeader: "Unknown-Header", + PathPrefix: pathPrefix, }, wantErr: "twirp error unauthenticated: invalid token", }, + { + name: "wrong path prefix", + args: csArgs{ + Input: "testdata/fixtures/images/distroless-base.tar.gz", + ClientToken: token, + ClientTokenHeader: tokenHeader, + PathPrefix: "wrong", + }, + wantErr: "HTTP status code 404", + }, } - serverToken := "token" - serverTokenHeader := "Trivy-Token" addr, cacheDir := setup(t, setupOptions{ - token: serverToken, - tokenHeader: serverTokenHeader, + token: token, + tokenHeader: tokenHeader, + pathPrefix: pathPrefix, }) for _, tt := range tests { @@ -539,6 +556,7 @@ func TestClientServerWithRedis(t *testing.T) { type setupOptions struct { token string tokenHeader string + pathPrefix string cacheBackend string } @@ -556,7 +574,7 @@ func setup(t *testing.T, options setupOptions) (string, string) { addr := fmt.Sprintf("localhost:%d", port) go func() { - osArgs := setupServer(addr, options.token, options.tokenHeader, cacheDir, options.cacheBackend) + osArgs := setupServer(addr, options.token, options.tokenHeader, options.pathPrefix, cacheDir, options.cacheBackend) // Run Trivy server require.NoError(t, execute(osArgs)) @@ -569,22 +587,20 @@ func setup(t *testing.T, options setupOptions) (string, string) { return addr, cacheDir } -func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []string { +func setupServer(addr, token, tokenHeader, pathPrefix, cacheDir, cacheBackend string) []string { osArgs := []string{ "--cache-dir", cacheDir, "server", - "--skip-update", + "--skip-db-update", "--listen", addr, } if token != "" { - osArgs = append(osArgs, []string{ - "--token", - token, - "--token-header", - tokenHeader, - }...) + osArgs = append(osArgs, "--token", token, "--token-header", tokenHeader) + } + if pathPrefix != "" { + osArgs = append(osArgs, "--path-prefix", pathPrefix) } if cacheBackend != "" { osArgs = append(osArgs, "--cache-backend", cacheBackend) @@ -593,13 +609,13 @@ func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []stri } func setupClient(t *testing.T, c csArgs, addr string, cacheDir string) []string { + t.Helper() if c.Command == "" { c.Command = "image" } if c.RemoteAddrOption == "" { c.RemoteAddrOption = "--server" } - t.Helper() osArgs := []string{ "--cache-dir", cacheDir, @@ -639,6 +655,9 @@ func setupClient(t *testing.T, c csArgs, addr string, cacheDir string) []string if c.ClientToken != "" { osArgs = append(osArgs, "--token", c.ClientToken, "--token-header", c.ClientTokenHeader) } + if c.PathPrefix != "" { + osArgs = append(osArgs, "--path-prefix", c.PathPrefix) + } if c.Input != "" { osArgs = append(osArgs, "--input", c.Input) } diff --git a/magefiles/docs.go b/magefiles/docs.go index 7d09d37e1092..bdb85c5357cb 100644 --- a/magefiles/docs.go +++ b/magefiles/docs.go @@ -105,7 +105,7 @@ func writeFlags(group flag.FlagGroup, w *os.File) { var lastParts []string for _, flg := range flags { - if flg.GetConfigName() == "" { + if flg.GetConfigName() == "" || flg.Hidden() { continue } // We need to split the config name on `.` to make the indentations needed in yaml. diff --git a/pkg/cache/remote.go b/pkg/cache/remote.go index 44c9f63c92d8..19d748bf7923 100644 --- a/pkg/cache/remote.go +++ b/pkg/cache/remote.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "net/http" + "github.com/twitchtv/twirp" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -19,6 +20,7 @@ type RemoteOptions struct { ServerAddr string CustomHeaders http.Header Insecure bool + PathPrefix string } // RemoteCache implements remote cache @@ -39,7 +41,12 @@ func NewRemoteCache(opts RemoteOptions) *RemoteCache { }, }, } - c := rpcCache.NewCacheProtobufClient(opts.ServerAddr, httpClient) + + var twirpOpts []twirp.ClientOption + if opts.PathPrefix != "" { + twirpOpts = append(twirpOpts, twirp.WithClientPathPrefix(opts.PathPrefix)) + } + c := rpcCache.NewCacheProtobufClient(opts.ServerAddr, httpClient, twirpOpts...) return &RemoteCache{ ctx: ctx, client: c, diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 3edc54dc723c..59b3313d2f15 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -545,11 +545,7 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan Target: target, CacheOptions: opts.CacheOpts(), RemoteCacheOptions: opts.RemoteCacheOpts(), - ServerOption: client.ScannerOption{ - RemoteURL: opts.ServerAddr, - CustomHeaders: opts.CustomHeaders, - Insecure: opts.Insecure, - }, + ServerOption: opts.ClientScannerOpts(), ArtifactOption: artifact.Option{ DisabledAnalyzers: disabledAnalyzers(opts), DisabledHandlers: disabledHandlers, diff --git a/pkg/commands/server/run.go b/pkg/commands/server/run.go index 19b24f990396..6c5316d7465a 100644 --- a/pkg/commands/server/run.go +++ b/pkg/commands/server/run.go @@ -50,6 +50,6 @@ func Run(ctx context.Context, opts flag.Options) (err error) { m.Register() server := rpcServer.NewServer(opts.AppVersion, opts.Listen, opts.CacheDir, opts.Token, opts.TokenHeader, - opts.DBRepository, opts.RegistryOpts()) + opts.PathPrefix, opts.DBRepository, opts.RegistryOpts()) return server.ListenAndServe(ctx, cacheClient, opts.SkipDBUpdate) } diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 9bbcef79690b..e00aa4cfa922 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -23,6 +23,7 @@ import ( "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/plugin" "github.com/aquasecurity/trivy/pkg/result" + "github.com/aquasecurity/trivy/pkg/rpc/client" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/version/app" ) @@ -56,15 +57,21 @@ type Flag[T FlagType] struct { // Usage explains how to use the flag. Usage string - // Persistent represents if the flag is persistent + // Persistent represents if the flag is persistent. Persistent bool - // Deprecated represents if the flag is deprecated + // Deprecated represents if the flag is deprecated. + // It shows a warning message when the flag is used. Deprecated string - // Removed represents if the flag is removed and no longer works + // Removed represents if the flag is removed and no longer works. + // It shows an error message when the flag is used. Removed string + // Internal represents if the flag is for internal use only. + // It is not shown in the usage message. + Internal bool + // Aliases represents aliases Aliases []Alias @@ -208,6 +215,10 @@ func (f *Flag[T]) GetAliases() []Alias { return f.Aliases } +func (f *Flag[T]) Hidden() bool { + return f.Deprecated != "" || f.Removed != "" || f.Internal +} + func (f *Flag[T]) Value() (t T) { if f == nil { return t @@ -249,7 +260,7 @@ func (f *Flag[T]) Add(cmd *cobra.Command) { flags.Float64P(f.Name, f.Shorthand, v, f.Usage) } - if f.Deprecated != "" || f.Removed != "" { + if f.Hidden() { _ = flags.MarkHidden(f.Name) } } @@ -313,6 +324,7 @@ type Flagger interface { GetConfigName() string GetDefaultValue() any GetAliases() []Alias + Hidden() bool Parse() error Add(cmd *cobra.Command) @@ -480,6 +492,16 @@ func (o *Options) RemoteCacheOpts() cache.RemoteOptions { ServerAddr: o.ServerAddr, CustomHeaders: o.CustomHeaders, Insecure: o.Insecure, + PathPrefix: o.PathPrefix, + } +} + +func (o *Options) ClientScannerOpts() client.ScannerOption { + return client.ScannerOption{ + RemoteURL: o.ServerAddr, + CustomHeaders: o.CustomHeaders, + Insecure: o.Insecure, + PathPrefix: o.PathPrefix, } } diff --git a/pkg/flag/remote_flags.go b/pkg/flag/remote_flags.go index 2348ef649e66..b09e89a48104 100644 --- a/pkg/flag/remote_flags.go +++ b/pkg/flag/remote_flags.go @@ -39,6 +39,12 @@ var ( Default: "localhost:4954", Usage: "listen address in server mode", } + ServerPathPrefixFlag = Flag[string]{ + Name: "path-prefix", + ConfigName: "server.path-prefix", + Usage: "prefix for the server endpoint", + Internal: true, // Internal use + } ) // RemoteFlagGroup composes common printer flag structs @@ -47,6 +53,7 @@ type RemoteFlagGroup struct { // for client/server Token *Flag[string] TokenHeader *Flag[string] + PathPrefix *Flag[string] // for client ServerAddr *Flag[string] @@ -63,12 +70,17 @@ type RemoteOptions struct { ServerAddr string Listen string CustomHeaders http.Header + + // Server endpoint: []/./ (default prefix: /twirp) + // e.g., http://localhost:4954/twirp/trivy.scanner.v1.Scanner/Scan + PathPrefix string } func NewClientFlags() *RemoteFlagGroup { return &RemoteFlagGroup{ Token: ServerTokenFlag.Clone(), TokenHeader: ServerTokenHeaderFlag.Clone(), + PathPrefix: ServerPathPrefixFlag.Clone(), ServerAddr: ServerAddrFlag.Clone(), CustomHeaders: ServerCustomHeadersFlag.Clone(), } @@ -76,9 +88,10 @@ func NewClientFlags() *RemoteFlagGroup { func NewServerFlags() *RemoteFlagGroup { return &RemoteFlagGroup{ - Token: &ServerTokenFlag, - TokenHeader: &ServerTokenHeaderFlag, - Listen: &ServerListenFlag, + Token: ServerTokenFlag.Clone(), + TokenHeader: ServerTokenHeaderFlag.Clone(), + PathPrefix: ServerPathPrefixFlag.Clone(), + Listen: ServerListenFlag.Clone(), } } @@ -90,6 +103,7 @@ func (f *RemoteFlagGroup) Flags() []Flagger { return []Flagger{ f.Token, f.TokenHeader, + f.PathPrefix, f.ServerAddr, f.CustomHeaders, f.Listen, @@ -129,6 +143,7 @@ func (f *RemoteFlagGroup) ToOptions() (RemoteOptions, error) { return RemoteOptions{ Token: token, TokenHeader: tokenHeader, + PathPrefix: f.PathPrefix.Value(), ServerAddr: serverAddr, CustomHeaders: customHeaders, Listen: listen, diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index 9e13db8ded48..ed7d130dcb06 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "net/http" + "github.com/twitchtv/twirp" "golang.org/x/xerrors" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -32,6 +33,7 @@ type ScannerOption struct { RemoteURL string Insecure bool CustomHeaders http.Header + PathPrefix string } // Scanner implements the RPC scanner @@ -42,16 +44,15 @@ type Scanner struct { // NewScanner is the factory method to return RPC Scanner func NewScanner(scannerOptions ScannerOption, opts ...Option) Scanner { - httpClient := &http.Client{ - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: scannerOptions.Insecure, - }, - }, - } + tr := http.DefaultTransport.(*http.Transport).Clone() + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: scannerOptions.Insecure} + httpClient := &http.Client{Transport: tr} - c := rpc.NewScannerProtobufClient(scannerOptions.RemoteURL, httpClient) + var twirpOpts []twirp.ClientOption + if scannerOptions.PathPrefix != "" { + twirpOpts = append(twirpOpts, twirp.WithClientPathPrefix(scannerOptions.PathPrefix)) + } + c := rpc.NewScannerProtobufClient(scannerOptions.RemoteURL, httpClient, twirpOpts...) o := &options{rpcClient: c} for _, opt := range opts { diff --git a/pkg/rpc/server/listen.go b/pkg/rpc/server/listen.go index 0c4484930773..2b6d1288b7ff 100644 --- a/pkg/rpc/server/listen.go +++ b/pkg/rpc/server/listen.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" "os" + "strings" "sync" "time" @@ -30,9 +31,11 @@ const updateInterval = 1 * time.Hour type Server struct { appVersion string addr string + cacheDir string dbDir string token string tokenHeader string + pathPrefix string dbRepository name.Reference // For OCI registries @@ -40,13 +43,15 @@ type Server struct { } // NewServer returns an instance of Server -func NewServer(appVersion, addr, cacheDir, token, tokenHeader string, dbRepository name.Reference, opt types.RegistryOptions) Server { +func NewServer(appVersion, addr, cacheDir, token, tokenHeader, pathPrefix string, dbRepository name.Reference, opt types.RegistryOptions) Server { return Server{ appVersion: appVersion, addr: addr, + cacheDir: cacheDir, dbDir: db.Dir(cacheDir), token: token, tokenHeader: tokenHeader, + pathPrefix: pathPrefix, dbRepository: dbRepository, RegistryOptions: opt, } @@ -67,14 +72,13 @@ func (s Server) ListenAndServe(ctx context.Context, serverCache cache.Cache, ski } }() - mux := newServeMux(ctx, serverCache, dbUpdateWg, requestWg, s.token, s.tokenHeader, s.dbDir) + mux := s.newServeMux(ctx, serverCache, dbUpdateWg, requestWg) log.Infof("Listening %s...", s.addr) return http.ListenAndServe(s.addr, mux) } -func newServeMux(ctx context.Context, serverCache cache.Cache, dbUpdateWg, requestWg *sync.WaitGroup, - token, tokenHeader, cacheDir string) *http.ServeMux { +func (s Server) newServeMux(ctx context.Context, serverCache cache.Cache, dbUpdateWg, requestWg *sync.WaitGroup) *http.ServeMux { withWaitGroup := func(base http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Stop processing requests during DB update @@ -91,13 +95,19 @@ func newServeMux(ctx context.Context, serverCache cache.Cache, dbUpdateWg, reque mux := http.NewServeMux() - scanServer := rpcScanner.NewScannerServer(initializeScanServer(serverCache), nil) - scanHandler := withToken(withWaitGroup(scanServer), token, tokenHeader) - mux.Handle(rpcScanner.ScannerPathPrefix, gziphandler.GzipHandler(scanHandler)) + var twirpOpts []any + if s.pathPrefix != "" { + pathPrefix := "/" + strings.TrimPrefix(s.pathPrefix, "/") // Twirp requires the leading slash + twirpOpts = append(twirpOpts, twirp.WithServerPathPrefix(pathPrefix)) + } + + scanServer := rpcScanner.NewScannerServer(initializeScanServer(serverCache), twirpOpts...) + scanHandler := withToken(withWaitGroup(scanServer), s.token, s.tokenHeader) + mux.Handle(scanServer.PathPrefix(), gziphandler.GzipHandler(scanHandler)) - layerServer := rpcCache.NewCacheServer(NewCacheServer(serverCache), nil) - layerHandler := withToken(withWaitGroup(layerServer), token, tokenHeader) - mux.Handle(rpcCache.CachePathPrefix, gziphandler.GzipHandler(layerHandler)) + cacheServer := rpcCache.NewCacheServer(NewCacheServer(serverCache), twirpOpts...) + layerHandler := withToken(withWaitGroup(cacheServer), s.token, s.tokenHeader) + mux.Handle(cacheServer.PathPrefix(), gziphandler.GzipHandler(layerHandler)) mux.HandleFunc("/healthz", func(rw http.ResponseWriter, r *http.Request) { if _, err := rw.Write([]byte("ok")); err != nil { @@ -108,7 +118,7 @@ func newServeMux(ctx context.Context, serverCache cache.Cache, dbUpdateWg, reque mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(version.NewVersionInfo(cacheDir)); err != nil { + if err := json.NewEncoder(w).Encode(version.NewVersionInfo(s.cacheDir)); err != nil { log.Error("Version error", log.Err(err)) } }) diff --git a/pkg/rpc/server/listen_test.go b/pkg/rpc/server/listen_test.go index 8457cef042d7..fa61e228806c 100644 --- a/pkg/rpc/server/listen_test.go +++ b/pkg/rpc/server/listen_test.go @@ -115,7 +115,7 @@ func Test_dbWorker_update(t *testing.T) { } } -func Test_newServeMux(t *testing.T) { +func TestServer_newServeMux(t *testing.T) { type args struct { token string tokenHeader string @@ -182,9 +182,8 @@ func Test_newServeMux(t *testing.T) { require.NoError(t, err) defer func() { _ = c.Close() }() - ts := httptest.NewServer(newServeMux(context.Background(), c, dbUpdateWg, requestWg, tt.args.token, - tt.args.tokenHeader, ""), - ) + s := NewServer("", "", "", tt.args.token, tt.args.tokenHeader, "", nil, ftypes.RegistryOptions{}) + ts := httptest.NewServer(s.newServeMux(context.Background(), c, dbUpdateWg, requestWg)) defer ts.Close() var resp *http.Response @@ -214,9 +213,8 @@ func Test_VersionEndpoint(t *testing.T) { require.NoError(t, err) defer func() { _ = c.Close() }() - ts := httptest.NewServer(newServeMux(context.Background(), c, dbUpdateWg, requestWg, "", "", - "testdata/testcache"), - ) + s := NewServer("", "", "testdata/testcache", "", "", "", nil, ftypes.RegistryOptions{}) + ts := httptest.NewServer(s.newServeMux(context.Background(), c, dbUpdateWg, requestWg)) defer ts.Close() resp, err := http.Get(ts.URL + "/version") From 6fe672732b46e0379fa14c2bbf4ba01881b8155a Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 22 Aug 2024 06:21:39 +0600 Subject: [PATCH 298/352] chore(deps): bump trivy-checks (#7350) Signed-off-by: nikpivkin --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1465ad2b2343..230470d8f12c 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/aquasecurity/table v1.8.0 github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 github.com/aquasecurity/tml v0.6.1 - github.com/aquasecurity/trivy-checks v0.13.0 + github.com/aquasecurity/trivy-checks v0.13.1-0.20240809030752-558fcff75807 github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b diff --git a/go.sum b/go.sum index 1a1e285e8de6..860b788ae605 100644 --- a/go.sum +++ b/go.sum @@ -348,8 +348,8 @@ github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 h1:b43UVqY github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8/go.mod h1:wXA9k3uuaxY3yu7gxrxZDPo/04FEMJtwyecdAlYrEIo= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= -github.com/aquasecurity/trivy-checks v0.13.0 h1:na6PTdY4U0uK/fjz3HNRYBxvYSJ8vgTb57a5T8Y5t9w= -github.com/aquasecurity/trivy-checks v0.13.0/go.mod h1:Xec/SMVGV66I7RgUqOX9MEr+YxBqHXDVLTYmpspPi3E= +github.com/aquasecurity/trivy-checks v0.13.1-0.20240809030752-558fcff75807 h1:yw2INXrbfekt1yHDQAlNZlHIUZQXMcSS+mWI9XWJUN0= +github.com/aquasecurity/trivy-checks v0.13.1-0.20240809030752-558fcff75807/go.mod h1:Xec/SMVGV66I7RgUqOX9MEr+YxBqHXDVLTYmpspPi3E= github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 h1:6/T8sFdNVG/AwOGoK6X55h7hF7LYqK8bsuPz8iEz8jM= github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04/go.mod h1:0T6oy2t1Iedt+yi3Ml5cpOYp5FZT4MI1/mx+3p+PIs8= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= From bfdf5cfc305c6617e1030ea16b5632c9137c916e Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Fri, 23 Aug 2024 10:27:17 +0600 Subject: [PATCH 299/352] refactor(misconf): use slog (#7295) Signed-off-by: nikpivkin --- pkg/commands/artifact/run.go | 1 - pkg/iac/debug/debug.go | 38 ----- pkg/iac/rego/load.go | 39 +++-- pkg/iac/rego/load_test.go | 8 +- pkg/iac/rego/scanner.go | 34 +++-- pkg/iac/rego/scanner_test.go | 4 - pkg/iac/scanners/azure/arm/parser/parser.go | 46 +++--- .../scanners/azure/arm/parser/parser_test.go | 6 +- pkg/iac/scanners/azure/arm/scanner.go | 15 +- .../cloudformation/parser/file_context.go | 12 +- .../parser/file_context_test.go | 46 +++++- .../scanners/cloudformation/parser/parser.go | 55 ++++--- .../cloudformation/parser/resource.go | 2 +- pkg/iac/scanners/cloudformation/scanner.go | 27 ++-- .../cloudformation/test/cf_scanning_test.go | 17 --- pkg/iac/scanners/dockerfile/parser/parser.go | 21 +-- pkg/iac/scanners/dockerfile/scanner.go | 14 +- pkg/iac/scanners/dockerfile/scanner_test.go | 2 - pkg/iac/scanners/helm/options.go | 29 ++-- pkg/iac/scanners/helm/parser/option.go | 53 +++---- pkg/iac/scanners/helm/parser/parser.go | 37 +---- pkg/iac/scanners/helm/parser/parser_tar.go | 5 +- pkg/iac/scanners/helm/scanner.go | 22 ++- pkg/iac/scanners/helm/test/option_test.go | 9 +- pkg/iac/scanners/json/parser/parser.go | 22 +-- pkg/iac/scanners/json/scanner.go | 16 +-- pkg/iac/scanners/kubernetes/parser/parser.go | 21 +-- pkg/iac/scanners/kubernetes/scanner.go | 16 +-- pkg/iac/scanners/kubernetes/scanner_test.go | 2 - pkg/iac/scanners/options/parser.go | 16 --- pkg/iac/scanners/options/scanner.go | 8 -- .../scanners/terraform/executor/executor.go | 28 ++-- pkg/iac/scanners/terraform/executor/option.go | 9 -- pkg/iac/scanners/terraform/fs_test.go | 1 - pkg/iac/scanners/terraform/module_test.go | 13 +- pkg/iac/scanners/terraform/options.go | 2 +- .../scanners/terraform/parser/evaluator.go | 99 +++++++++---- .../scanners/terraform/parser/load_module.go | 24 +++- .../terraform/parser/module_retrieval.go | 6 +- pkg/iac/scanners/terraform/parser/option.go | 71 +++------- pkg/iac/scanners/terraform/parser/parser.go | 106 +++++++------- .../scanners/terraform/parser/parser_test.go | 41 +++++- .../terraform/parser/resolvers/cache.go | 10 +- .../terraform/parser/resolvers/local.go | 10 +- .../terraform/parser/resolvers/options.go | 8 +- .../terraform/parser/resolvers/registry.go | 18 ++- .../terraform/parser/resolvers/remote.go | 16 ++- pkg/iac/scanners/terraform/scanner.go | 44 +++--- .../terraform/scanner_integration_test.go | 16 --- pkg/iac/scanners/terraform/scanner_test.go | 134 +----------------- .../terraformplan/snapshot/scanner_test.go | 3 - .../terraformplan/tfjson/parser/option.go | 17 --- .../terraformplan/tfjson/parser/parser.go | 21 +-- .../scanners/terraformplan/tfjson/scanner.go | 20 +-- .../terraformplan/tfjson/scanner_test.go | 8 +- pkg/iac/scanners/toml/parser/parser.go | 22 +-- pkg/iac/scanners/toml/scanner.go | 16 +-- pkg/iac/scanners/yaml/parser/parser.go | 24 ++-- pkg/iac/scanners/yaml/scanner.go | 16 +-- pkg/log/logger.go | 16 --- pkg/misconf/scanner.go | 5 - 61 files changed, 566 insertions(+), 901 deletions(-) delete mode 100644 pkg/iac/debug/debug.go delete mode 100644 pkg/iac/scanners/options/parser.go delete mode 100644 pkg/iac/scanners/terraformplan/tfjson/parser/option.go diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 59b3313d2f15..fa454c0cd276 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -640,7 +640,6 @@ func initMisconfScannerOption(opts flag.Options) (misconf.ScannerOption, error) } return misconf.ScannerOption{ - Debug: opts.Debug, Trace: opts.Trace, Namespaces: append(opts.CheckNamespaces, rego.BuiltinNamespaces()...), PolicyPaths: append(opts.CheckPaths, downloadedPolicyPaths...), diff --git a/pkg/iac/debug/debug.go b/pkg/iac/debug/debug.go deleted file mode 100644 index 489c777dd205..000000000000 --- a/pkg/iac/debug/debug.go +++ /dev/null @@ -1,38 +0,0 @@ -package debug - -import ( - "fmt" - "io" - "strings" - "time" -) - -const timeFormat = "04:05.000000000" - -type Logger struct { - writer io.Writer - prefix string -} - -func New(w io.Writer, parts ...string) Logger { - return Logger{ - writer: w, - prefix: strings.Join(parts, "."), - } -} - -func (l *Logger) Extend(parts ...string) Logger { - return Logger{ - writer: l.writer, - prefix: strings.Join(append([]string{l.prefix}, parts...), "."), - } -} - -func (l *Logger) Log(format string, args ...any) { - if l.writer == nil { - return - } - message := fmt.Sprintf(format, args...) - line := fmt.Sprintf("%s %-32s %s\n", time.Now().Format(timeFormat), l.prefix, message) - _, _ = l.writer.Write([]byte(line)) -} diff --git a/pkg/iac/rego/load.go b/pkg/iac/rego/load.go index bd474a23be4d..249e55992c2c 100644 --- a/pkg/iac/rego/load.go +++ b/pkg/iac/rego/load.go @@ -10,6 +10,8 @@ import ( "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/bundle" "github.com/samber/lo" + + "github.com/aquasecurity/trivy/pkg/log" ) var builtinNamespaces = map[string]struct{}{ @@ -61,14 +63,14 @@ func (s *Scanner) loadEmbedded() error { return fmt.Errorf("failed to load embedded rego libraries: %w", err) } s.embeddedLibs = loaded - s.debug.Log("Loaded %d embedded libraries.", len(loaded)) + s.logger.Debug("Embedded libraries are loaded", log.Int("count", len(loaded))) loaded, err = LoadEmbeddedPolicies() if err != nil { - return fmt.Errorf("failed to load embedded rego policies: %w", err) + return fmt.Errorf("failed to load embedded rego checks: %w", err) } s.embeddedChecks = loaded - s.debug.Log("Loaded %d embedded policies.", len(loaded)) + s.logger.Debug("Embedded checks are loaded", log.Int("count", len(loaded))) return nil } @@ -80,7 +82,7 @@ func (s *Scanner) LoadPolicies(enableEmbeddedLibraries, enableEmbeddedPolicies b } if s.policyFS != nil { - s.debug.Log("Overriding filesystem for checks!") + s.logger.Debug("Overriding filesystem for checks") srcFS = s.policyFS } @@ -105,7 +107,7 @@ func (s *Scanner) LoadPolicies(enableEmbeddedLibraries, enableEmbeddedPolicies b for name, policy := range loaded { s.policies[name] = policy } - s.debug.Log("Loaded %d checks from disk.", len(loaded)) + s.logger.Debug("Checks from disk are loaded", log.Int("count", len(loaded))) } if len(readers) > 0 { @@ -116,7 +118,7 @@ func (s *Scanner) LoadPolicies(enableEmbeddedLibraries, enableEmbeddedPolicies b for name, policy := range loaded { s.policies[name] = policy } - s.debug.Log("Loaded %d checks from reader(s).", len(loaded)) + s.logger.Debug("Checks from readers are loaded", log.Int("count", len(loaded))) } // gather namespaces @@ -132,7 +134,7 @@ func (s *Scanner) LoadPolicies(enableEmbeddedLibraries, enableEmbeddedPolicies b dataFS := srcFS if s.dataFS != nil { - s.debug.Log("Overriding filesystem for data!") + s.logger.Debug("Overriding filesystem for data") dataFS = s.dataFS } store, err := initStore(dataFS, s.dataDirs, namespaces) @@ -168,15 +170,19 @@ func (s *Scanner) fallbackChecks(compiler *ast.Compiler) { continue } - s.debug.Log("Error occurred while parsing: %s, %s. Trying to fallback to embedded check.", loc, e.Error()) + s.logger.Error( + "Error occurred while parsing. Trying to fallback to embedded check", + log.FilePath(loc), + log.Err(e), + ) embedded := s.findMatchedEmbeddedCheck(badPolicy) if embedded == nil { - s.debug.Log("Failed to find embedded check: %s", loc) + s.logger.Error("Failed to find embedded check, skipping", log.FilePath(loc)) continue } - s.debug.Log("Found embedded check: %s", embedded.Package.Location.File) + s.logger.Debug("Found embedded check", log.FilePath(embedded.Package.Location.File)) delete(s.policies, loc) // remove bad check s.policies[embedded.Package.Location.File] = embedded delete(s.embeddedChecks, embedded.Package.Location.File) // avoid infinite loop if embedded check contains ref error @@ -214,7 +220,7 @@ func (s *Scanner) findMatchedEmbeddedCheck(badPolicy *ast.Module) *ast.Module { func (s *Scanner) prunePoliciesWithError(compiler *ast.Compiler) error { if len(compiler.Errors) > s.regoErrorLimit { - s.debug.Log("Error(s) occurred while loading checks") + s.logger.Error("Error(s) occurred while loading checks") return compiler.Errors } @@ -222,7 +228,10 @@ func (s *Scanner) prunePoliciesWithError(compiler *ast.Compiler) error { if e.Location == nil { continue } - s.debug.Log("Error occurred while parsing: %s, %s", e.Location.File, e.Error()) + s.logger.Error( + "Error occurred while parsing", + log.FilePath(e.Location.File), log.Err(e), + ) delete(s.policies, e.Location.File) } return nil @@ -282,7 +291,11 @@ func (s *Scanner) filterModules(retriever *MetadataRetriever) error { return err } if len(meta.InputOptions.Selectors) == 0 { - s.debug.Log("WARNING: Module %s has no input selectors - it will be loaded for all inputs!", name) + s.logger.Warn( + "Module has no input selectors - it will be loaded for all inputs!", + log.FilePath(module.Package.Location.File), + log.String("module", name), + ) filtered[name] = module continue } diff --git a/pkg/iac/rego/load_test.go b/pkg/iac/rego/load_test.go index ede06e4610ed..e0c136562d45 100644 --- a/pkg/iac/rego/load_test.go +++ b/pkg/iac/rego/load_test.go @@ -5,6 +5,7 @@ import ( "embed" "fmt" "io" + "log/slog" "strings" "testing" "testing/fstest" @@ -17,6 +18,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) //go:embed all:testdata/policies @@ -28,10 +30,10 @@ var embeddedChecksFS embed.FS func Test_RegoScanning_WithSomeInvalidPolicies(t *testing.T) { t.Run("allow no errors", func(t *testing.T) { var debugBuf bytes.Buffer + slog.SetDefault(log.New(log.NewHandler(&debugBuf, nil))) scanner := rego.NewScanner( types.SourceDockerfile, options.ScannerWithRegoErrorLimits(0), - options.ScannerWithDebug(&debugBuf), ) err := scanner.LoadPolicies(false, false, testEmbedFS, []string{"."}, nil) @@ -41,16 +43,16 @@ func Test_RegoScanning_WithSomeInvalidPolicies(t *testing.T) { t.Run("allow up to max 1 error", func(t *testing.T) { var debugBuf bytes.Buffer + slog.SetDefault(log.New(log.NewHandler(&debugBuf, nil))) scanner := rego.NewScanner( types.SourceDockerfile, options.ScannerWithRegoErrorLimits(1), - options.ScannerWithDebug(&debugBuf), ) err := scanner.LoadPolicies(false, false, testEmbedFS, []string{"."}, nil) require.NoError(t, err) - assert.Contains(t, debugBuf.String(), "Error occurred while parsing: testdata/policies/invalid.rego, testdata/policies/invalid.rego:7") + assert.Contains(t, debugBuf.String(), "Error occurred while parsing\tfile_path=\"testdata/policies/invalid.rego\" err=\"testdata/policies/invalid.rego:7") }) t.Run("schema does not exist", func(t *testing.T) { diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go index fe0553d6bc00..23a1e04bc2b8 100644 --- a/pkg/iac/rego/scanner.go +++ b/pkg/iac/rego/scanner.go @@ -15,13 +15,13 @@ import ( "github.com/open-policy-agent/opa/storage" "github.com/open-policy-agent/opa/util" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rego/schemas" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var checkTypesWithSubtype = map[types.Source]struct{}{ @@ -51,7 +51,7 @@ type Scanner struct { runtimeValues *ast.Term compiler *ast.Compiler regoErrorLimit int - debug debug.Logger + logger *log.Logger traceWriter io.Writer tracePerResult bool retriever *MetadataRetriever @@ -113,10 +113,6 @@ func (s *Scanner) SetPolicyReaders(_ []io.Reader) { // NOTE: Policy readers option not applicable for rego, policies are loaded on-demand by other scanners. } -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "rego", "scanner") -} - func (s *Scanner) SetTraceWriter(writer io.Writer) { s.traceWriter = writer } @@ -166,6 +162,7 @@ func NewScanner(source types.Source, opts ...options.ScannerOption) *Scanner { sourceType: source, ruleNamespaces: make(map[string]struct{}), runtimeValues: addRuntimeValues(), + logger: log.WithPrefix("rego"), customSchemas: make(map[string][]byte), } @@ -183,10 +180,6 @@ func NewScanner(source types.Source, opts ...options.ScannerOption) *Scanner { return s } -func (s *Scanner) SetParentDebugLogger(l debug.Logger) { - s.debug = l.Extend("rego") -} - func (s *Scanner) runQuery(ctx context.Context, query string, input ast.Value, disableTracing bool) (rego.ResultSet, []string, error) { trace := (s.traceWriter != nil || s.tracePerResult) && !disableTracing @@ -247,7 +240,7 @@ func GetInputsContents(inputs []Input) []any { func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, error) { - s.debug.Log("Scanning %d inputs...", len(inputs)) + s.logger.Debug("Scannning inputs", "count", len(inputs)) var results scan.Results @@ -267,9 +260,11 @@ func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, staticMeta, err := s.retriever.RetrieveMetadata(ctx, module, GetInputsContents(inputs)...) if err != nil { - s.debug.Log( - "Error occurred while retrieving metadata from check %q: %s", - module.Package.Location.File, err) + s.logger.Error( + "Error occurred while retrieving metadata from check", + log.FilePath(module.Package.Location.File), + log.Err(err), + ) continue } @@ -300,9 +295,12 @@ func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, if isEnforcedRule(ruleName) { ruleResults, err := s.applyRule(ctx, namespace, ruleName, inputs, staticMeta.InputOptions.Combined) if err != nil { - s.debug.Log( - "Error occurred while applying rule %q from check %q: %s", - ruleName, module.Package.Location.File, err) + s.logger.Error( + "Error occurred while applying rule from check", + log.String("rule", ruleName), + log.FilePath(module.Package.Location.File), + log.Err(err), + ) continue } results = append(results, s.embellishResultsWithRuleMetadata(ruleResults, *staticMeta)...) @@ -390,7 +388,7 @@ func (s *Scanner) applyRule(ctx context.Context, namespace, rule string, inputs s.trace("INPUT", input) parsedInput, err := parseRawInput(input.Contents) if err != nil { - s.debug.Log("Error occurred while parsing input: %s", err) + s.logger.Error("Error occurred while parsing input", log.Err(err)) continue } if ignored, err := s.isIgnored(ctx, namespace, rule, parsedInput); err != nil { diff --git a/pkg/iac/rego/scanner_test.go b/pkg/iac/rego/scanner_test.go index de2b7427ae8a..c453cc984b1c 100644 --- a/pkg/iac/rego/scanner_test.go +++ b/pkg/iac/rego/scanner_test.go @@ -998,10 +998,8 @@ deny { }, } - var buf bytes.Buffer scanner := NewScanner( types.SourceYAML, - options.ScannerWithDebug(&buf), ) require.NoError( t, @@ -1009,8 +1007,6 @@ deny { ) _, err := scanner.ScanInput(context.TODO(), Input{}) require.NoError(t, err) - assert.Contains(t, buf.String(), - `Error occurred while applying rule "deny" from check "checks/bad.rego"`) } func Test_RegoScanning_WithDeprecatedCheck(t *testing.T) { diff --git a/pkg/iac/scanners/azure/arm/parser/parser.go b/pkg/iac/scanners/azure/arm/parser/parser.go index 9fbf7b7019a8..53f9eb21431b 100644 --- a/pkg/iac/scanners/azure/arm/parser/parser.go +++ b/pkg/iac/scanners/azure/arm/parser/parser.go @@ -6,36 +6,28 @@ import ( "io" "io/fs" - "github.com/aquasecurity/trivy/pkg/iac/debug" - azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/arm/parser/armjson" "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/resolver" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) type Parser struct { targetFS fs.FS - debug debug.Logger + logger *log.Logger } -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "azure", "arm") -} - -func New(targetFS fs.FS, opts ...options.ParserOption) *Parser { - p := &Parser{ +func New(targetFS fs.FS) *Parser { + return &Parser{ targetFS: targetFS, + logger: log.WithPrefix("arm parser"), } - for _, opt := range opts { - opt(p) - } - return p } -func (p *Parser) ParseFS(ctx context.Context, dir string) ([]azure2.Deployment, error) { +func (p *Parser) ParseFS(ctx context.Context, dir string) ([]azure.Deployment, error) { - var deployments []azure2.Deployment + var deployments []azure.Deployment if err := fs.WalkDir(p.targetFS, dir, func(path string, entry fs.DirEntry, err error) error { if err != nil { @@ -69,7 +61,7 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) ([]azure2.Deployment, return deployments, nil } -func (p *Parser) parseFile(r io.Reader, filename string) (*azure2.Deployment, error) { +func (p *Parser) parseFile(r io.Reader, filename string) (*azure.Deployment, error) { var template Template data, err := io.ReadAll(r) if err != nil { @@ -86,11 +78,11 @@ func (p *Parser) parseFile(r io.Reader, filename string) (*azure2.Deployment, er return p.convertTemplate(template), nil } -func (p *Parser) convertTemplate(template Template) *azure2.Deployment { +func (p *Parser) convertTemplate(template Template) *azure.Deployment { - deployment := azure2.Deployment{ + deployment := azure.Deployment{ Metadata: template.Metadata, - TargetScope: azure2.ScopeResourceGroup, // TODO: override from --resource-group? + TargetScope: azure.ScopeResourceGroup, // TODO: override from --resource-group? Parameters: nil, Variables: nil, Resources: nil, @@ -103,8 +95,8 @@ func (p *Parser) convertTemplate(template Template) *azure2.Deployment { // TODO: the references passed here should probably not be the name - maybe params.NAME.DefaultValue? for name, param := range template.Parameters { - deployment.Parameters = append(deployment.Parameters, azure2.Parameter{ - Variable: azure2.Variable{ + deployment.Parameters = append(deployment.Parameters, azure.Parameter{ + Variable: azure.Variable{ Name: name, Value: param.DefaultValue, }, @@ -114,14 +106,14 @@ func (p *Parser) convertTemplate(template Template) *azure2.Deployment { } for name, variable := range template.Variables { - deployment.Variables = append(deployment.Variables, azure2.Variable{ + deployment.Variables = append(deployment.Variables, azure.Variable{ Name: name, Value: variable, }) } for name, output := range template.Outputs { - deployment.Outputs = append(deployment.Outputs, azure2.Output{ + deployment.Outputs = append(deployment.Outputs, azure.Output{ Name: name, Value: output, }) @@ -134,15 +126,15 @@ func (p *Parser) convertTemplate(template Template) *azure2.Deployment { return &deployment } -func (p *Parser) convertResource(input Resource) azure2.Resource { +func (p *Parser) convertResource(input Resource) azure.Resource { - var children []azure2.Resource + var children []azure.Resource for _, child := range input.Resources { children = append(children, p.convertResource(child)) } - resource := azure2.Resource{ + resource := azure.Resource{ Metadata: input.Metadata, APIVersion: input.APIVersion, Type: input.Type, diff --git a/pkg/iac/scanners/azure/arm/parser/parser_test.go b/pkg/iac/scanners/azure/arm/parser/parser_test.go index b16213d9bebd..ff1444a60655 100644 --- a/pkg/iac/scanners/azure/arm/parser/parser_test.go +++ b/pkg/iac/scanners/azure/arm/parser/parser_test.go @@ -3,7 +3,6 @@ package parser import ( "context" "io/fs" - "os" "testing" "github.com/liamg/memoryfs" @@ -12,7 +11,6 @@ import ( azure2 "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" "github.com/aquasecurity/trivy/pkg/iac/scanners/azure/resolver" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" ) @@ -205,7 +203,7 @@ func TestParser_Parse(t *testing.T) { require.NoError(t, targetFS.WriteFile(filename, []byte(tt.input), 0644)) - p := New(targetFS, options.ParserWithDebug(os.Stderr)) + p := New(targetFS) got, err := p.ParseFS(context.Background(), ".") require.NoError(t, err) @@ -293,7 +291,7 @@ func Test_NestedResourceParsing(t *testing.T) { require.NoError(t, targetFS.WriteFile("nested.json", []byte(input), 0644)) - p := New(targetFS, options.ParserWithDebug(os.Stderr)) + p := New(targetFS) got, err := p.ParseFS(context.Background(), ".") require.NoError(t, err) require.Len(t, got, 1) diff --git a/pkg/iac/scanners/azure/arm/scanner.go b/pkg/iac/scanners/azure/arm/scanner.go index fd585a5955c5..66bab2b4f806 100644 --- a/pkg/iac/scanners/azure/arm/scanner.go +++ b/pkg/iac/scanners/azure/arm/scanner.go @@ -8,7 +8,6 @@ import ( "sync" "github.com/aquasecurity/trivy/pkg/iac/adapters/arm" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/rules" @@ -19,6 +18,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/state" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ scanners.FSScanner = (*Scanner)(nil) @@ -27,8 +27,7 @@ var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { mu sync.Mutex scannerOptions []options.ScannerOption - parserOptions []options.ParserOption - debug debug.Logger + logger *log.Logger frameworks []framework.Framework regoOnly bool loadEmbeddedPolicies bool @@ -53,6 +52,7 @@ func (s *Scanner) SetRegoOnly(regoOnly bool) { func New(opts ...options.ScannerOption) *Scanner { scanner := &Scanner{ scannerOptions: opts, + logger: log.WithPrefix("azure-arm"), } for _, opt := range opts { opt(scanner) @@ -64,11 +64,6 @@ func (s *Scanner) Name() string { return "Azure ARM" } -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "azure", "arm") - s.parserOptions = append(s.parserOptions, options.ParserWithDebug(writer)) -} - func (s *Scanner) SetPolicyDirs(dirs ...string) { s.policyDirs = dirs } @@ -109,7 +104,6 @@ func (s *Scanner) initRegoScanner(srcFS fs.FS) error { return nil } regoScanner := rego.NewScanner(types.SourceCloud, s.scannerOptions...) - regoScanner.SetParentDebugLogger(s.debug) if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { return err } @@ -118,7 +112,7 @@ func (s *Scanner) initRegoScanner(srcFS fs.FS) error { } func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) { - p := parser.New(fsys, s.parserOptions...) + p := parser.New(fsys) deployments, err := p.ParseFS(ctx, dir) if err != nil { return nil, err @@ -160,7 +154,6 @@ func (s *Scanner) scanDeployment(ctx context.Context, deployment azure.Deploymen continue } ruleResults := rule.Evaluate(deploymentState) - s.debug.Log("Found %d results for %s", len(ruleResults), rule.GetRule().AVDID) if len(ruleResults) > 0 { results = append(results, ruleResults...) } diff --git a/pkg/iac/scanners/cloudformation/parser/file_context.go b/pkg/iac/scanners/cloudformation/parser/file_context.go index 78a267cabb04..949add1ca7c4 100644 --- a/pkg/iac/scanners/cloudformation/parser/file_context.go +++ b/pkg/iac/scanners/cloudformation/parser/file_context.go @@ -54,10 +54,20 @@ func (t *FileContext) Metadata() iacTypes.Metadata { return iacTypes.NewMetadata(rng, NewCFReference("Template", rng).String()) } -func (t *FileContext) OverrideParameters(params map[string]any) { +func (t *FileContext) overrideParameters(params map[string]any) { for key := range t.Parameters { if val, ok := params[key]; ok { t.Parameters[key].UpdateDefault(val) } } } + +func (t *FileContext) missingParameterValues() []string { + var missing []string + for key := range t.Parameters { + if t.Parameters[key].inner.Default == nil { + missing = append(missing, key) + } + } + return missing +} diff --git a/pkg/iac/scanners/cloudformation/parser/file_context_test.go b/pkg/iac/scanners/cloudformation/parser/file_context_test.go index bbf5db4ddc39..80bdfa72eb05 100644 --- a/pkg/iac/scanners/cloudformation/parser/file_context_test.go +++ b/pkg/iac/scanners/cloudformation/parser/file_context_test.go @@ -1,6 +1,7 @@ package parser import ( + "slices" "testing" "github.com/stretchr/testify/assert" @@ -54,8 +55,51 @@ func TestFileContext_OverrideParameters(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.ctx.OverrideParameters(tt.arg) + tt.ctx.overrideParameters(tt.arg) assert.Equal(t, tt.expected, tt.ctx.Parameters) }) } } + +func TestFileContext_MissingParameterValues(t *testing.T) { + + tests := []struct { + name string + ctx FileContext + expected []string + }{ + { + name: "happy", + ctx: FileContext{ + Parameters: map[string]*Parameter{ + "BucketName": { + inner: parameterInner{ + Type: "String", + Default: "test", + }, + }, + "QueueName": { + inner: parameterInner{ + Type: "String", + }, + }, + "KMSKey": { + inner: parameterInner{ + Type: "String", + Default: nil, + }, + }, + }, + }, + expected: []string{"KMSKey", "QueueName"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.ctx.missingParameterValues() + slices.Sort(got) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/pkg/iac/scanners/cloudformation/parser/parser.go b/pkg/iac/scanners/cloudformation/parser/parser.go index d281b585035e..40d0035f6cd2 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser.go +++ b/pkg/iac/scanners/cloudformation/parser/parser.go @@ -13,51 +13,42 @@ import ( "github.com/liamg/jfather" "gopkg.in/yaml.v3" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/ignore" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ options.ConfigurableParser = (*Parser)(nil) - type Parser struct { - debug debug.Logger + logger *log.Logger parameterFiles []string parameters map[string]any overridedParameters Parameters configsFS fs.FS } -func WithParameters(params map[string]any) options.ParserOption { - return func(cp options.ConfigurableParser) { - if p, ok := cp.(*Parser); ok { - p.parameters = params - } - } -} +type Option func(*Parser) -func WithParameterFiles(files ...string) options.ParserOption { - return func(cp options.ConfigurableParser) { - if p, ok := cp.(*Parser); ok { - p.parameterFiles = files - } +func WithParameters(params map[string]any) Option { + return func(p *Parser) { + p.parameters = params } } -func WithConfigsFS(fsys fs.FS) options.ParserOption { - return func(cp options.ConfigurableParser) { - if p, ok := cp.(*Parser); ok { - p.configsFS = fsys - } +func WithParameterFiles(files ...string) Option { + return func(p *Parser) { + p.parameterFiles = files } } -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "cloudformation", "parser") +func WithConfigsFS(fsys fs.FS) Option { + return func(p *Parser) { + p.configsFS = fsys + } } -func New(opts ...options.ParserOption) *Parser { - p := &Parser{} +func New(opts ...Option) *Parser { + p := &Parser{ + logger: log.WithPrefix("cloudformation parser"), + } for _, option := range opts { option(p) } @@ -81,7 +72,7 @@ func (p *Parser) ParseFS(ctx context.Context, fsys fs.FS, dir string) (FileConte c, err := p.ParseFile(ctx, fsys, path) if err != nil { - p.debug.Log("Error parsing file '%s': %s", path, err) + p.logger.Error("Error parsing file", log.FilePath(path), log.Err(err)) return nil } contexts = append(contexts, c) @@ -149,13 +140,17 @@ func (p *Parser) ParseFile(ctx context.Context, fsys fs.FS, path string) (fctx * } } - fctx.OverrideParameters(p.overridedParameters) + fctx.overrideParameters(p.overridedParameters) + + if params := fctx.missingParameterValues(); len(params) > 0 { + p.logger.Warn("Missing parameter values", log.FilePath(path), log.String("parameters", strings.Join(params, ", "))) + } fctx.lines = lines fctx.SourceFormat = sourceFmt fctx.filepath = path - p.debug.Log("Context loaded from source %s", path) + p.logger.Debug("Context loaded from source", log.FilePath(path)) // the context must be set to conditions before resources for _, c := range fctx.Conditions { @@ -163,7 +158,7 @@ func (p *Parser) ParseFile(ctx context.Context, fsys fs.FS, path string) (fctx * } for name, r := range fctx.Resources { - r.ConfigureResource(name, fsys, path, fctx) + r.configureResource(name, fsys, path, fctx) } return fctx, nil diff --git a/pkg/iac/scanners/cloudformation/parser/resource.go b/pkg/iac/scanners/cloudformation/parser/resource.go index bd1351f234df..ed4b2834d573 100644 --- a/pkg/iac/scanners/cloudformation/parser/resource.go +++ b/pkg/iac/scanners/cloudformation/parser/resource.go @@ -23,7 +23,7 @@ type ResourceInner struct { Properties map[string]*Property `json:"Properties" yaml:"Properties"` } -func (r *Resource) ConfigureResource(id string, target fs.FS, filepath string, ctx *FileContext) { +func (r *Resource) configureResource(id string, target fs.FS, filepath string, ctx *FileContext) { r.setId(id) r.setFile(target, filepath) r.setContext(ctx) diff --git a/pkg/iac/scanners/cloudformation/scanner.go b/pkg/iac/scanners/cloudformation/scanner.go index cedd18ea6297..6db08c0d15bb 100644 --- a/pkg/iac/scanners/cloudformation/scanner.go +++ b/pkg/iac/scanners/cloudformation/scanner.go @@ -9,7 +9,6 @@ import ( "sync" adapter "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/rules" @@ -18,12 +17,13 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) func WithParameters(params map[string]any) options.ScannerOption { return func(cs options.ConfigurableScanner) { if s, ok := cs.(*Scanner); ok { - s.addParserOptions(parser.WithParameters(params)) + s.addParserOption(parser.WithParameters(params)) } } } @@ -31,7 +31,7 @@ func WithParameters(params map[string]any) options.ScannerOption { func WithParameterFiles(files ...string) options.ScannerOption { return func(cs options.ConfigurableScanner) { if s, ok := cs.(*Scanner); ok { - s.addParserOptions(parser.WithParameterFiles(files...)) + s.addParserOption(parser.WithParameterFiles(files...)) } } } @@ -39,7 +39,7 @@ func WithParameterFiles(files ...string) options.ScannerOption { func WithConfigsFS(fsys fs.FS) options.ScannerOption { return func(cs options.ConfigurableScanner) { if s, ok := cs.(*Scanner); ok { - s.addParserOptions(parser.WithConfigsFS(fsys)) + s.addParserOption(parser.WithConfigsFS(fsys)) } } } @@ -49,7 +49,7 @@ var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { mu sync.Mutex - debug debug.Logger + logger *log.Logger policyDirs []string policyReaders []io.Reader parser *parser.Parser @@ -58,7 +58,7 @@ type Scanner struct { loadEmbeddedPolicies bool loadEmbeddedLibraries bool options []options.ScannerOption - parserOptions []options.ParserOption + parserOptions []parser.Option frameworks []framework.Framework spec string } @@ -66,7 +66,7 @@ type Scanner struct { func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} func (s *Scanner) SetCustomSchemas(map[string][]byte) {} -func (s *Scanner) addParserOptions(opt options.ParserOption) { +func (s *Scanner) addParserOption(opt parser.Option) { s.parserOptions = append(s.parserOptions, opt) } @@ -98,11 +98,6 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "cloudformation", "scanner") - s.parserOptions = append(s.parserOptions, options.ParserWithDebug(writer)) -} - func (s *Scanner) SetPolicyDirs(dirs ...string) { s.policyDirs = dirs } @@ -125,6 +120,7 @@ func (s *Scanner) SetPolicyNamespaces(_ ...string) {} func New(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("cloudformation scanner"), } for _, opt := range opts { opt(s) @@ -140,7 +136,6 @@ func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceCloud, s.options...) - regoScanner.SetParentDebugLogger(s.debug) if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { return nil, err } @@ -221,7 +216,6 @@ func (s *Scanner) scanFileContext(ctx context.Context, regoScanner *rego.Scanner } evalResult := rule.Evaluate(state) if len(evalResult) > 0 { - s.debug.Log("Found %d results for %s", len(evalResult), rule.GetRule().AVDID) for _, scanResult := range evalResult { ref := scanResult.Metadata().Reference() @@ -250,7 +244,10 @@ func (s *Scanner) scanFileContext(ctx context.Context, regoScanner *rego.Scanner results.Ignore(cfCtx.Ignores, nil) for _, ignored := range results.GetIgnored() { - s.debug.Log("Ignored '%s' at '%s'.", ignored.Rule().LongID(), ignored.Range()) + s.logger.Info("Ignore finding", + log.String("rule", ignored.Rule().LongID()), + log.String("range", ignored.Range().String()), + ) } return results, nil diff --git a/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go b/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go index 4ab3009d4dab..396963447ca9 100644 --- a/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go +++ b/pkg/iac/scanners/cloudformation/test/cf_scanning_test.go @@ -1,7 +1,6 @@ package test import ( - "bytes" "context" "os" "testing" @@ -30,19 +29,3 @@ func Test_cloudformation_scanning_has_expected_errors(t *testing.T) { assert.NotEmpty(t, results.GetFailed()) } - -func Test_cloudformation_scanning_with_debug(t *testing.T) { - - debugWriter := bytes.NewBufferString("") - - scannerOptions := []options.ScannerOption{ - options.ScannerWithDebug(debugWriter), - } - cfScanner := cloudformation.New(scannerOptions...) - - _, err := cfScanner.ScanFS(context.TODO(), os.DirFS("./examples/bucket"), ".") - require.NoError(t, err) - - // check debug is as expected - assert.NotEmpty(t, debugWriter.String()) -} diff --git a/pkg/iac/scanners/dockerfile/parser/parser.go b/pkg/iac/scanners/dockerfile/parser/parser.go index 4255b4609b46..e92116ae3079 100644 --- a/pkg/iac/scanners/dockerfile/parser/parser.go +++ b/pkg/iac/scanners/dockerfile/parser/parser.go @@ -11,28 +11,19 @@ import ( "github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/moby/buildkit/frontend/dockerfile/parser" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ options.ConfigurableParser = (*Parser)(nil) - type Parser struct { - debug debug.Logger -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "dockerfile", "parser") + logger *log.Logger } // New creates a new Dockerfile parser -func New(opts ...options.ParserOption) *Parser { - p := &Parser{} - for _, option := range opts { - option(p) +func New() *Parser { + return &Parser{ + logger: log.WithPrefix("dockerfile parser"), } - return p } func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]*dockerfile.Dockerfile, error) { @@ -53,7 +44,7 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st df, err := p.ParseFile(ctx, target, path) if err != nil { - // TODO add debug for parse errors + p.logger.Error("Failed to parse Dockerfile", log.FilePath(path), log.Err(err)) return nil } files[path] = df diff --git a/pkg/iac/scanners/dockerfile/scanner.go b/pkg/iac/scanners/dockerfile/scanner.go index 6358aada12c2..e0d6ebd9ebde 100644 --- a/pkg/iac/scanners/dockerfile/scanner.go +++ b/pkg/iac/scanners/dockerfile/scanner.go @@ -6,7 +6,6 @@ import ( "io/fs" "sync" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" @@ -14,6 +13,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile/parser" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ scanners.FSScanner = (*Scanner)(nil) @@ -21,13 +21,12 @@ var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { mu sync.Mutex - debug debug.Logger + logger *log.Logger policyDirs []string policyReaders []io.Reader parser *parser.Parser regoScanner *rego.Scanner options []options.ScannerOption - parserOpts []options.ParserOption frameworks []framework.Framework spec string loadEmbeddedLibraries bool @@ -64,11 +63,6 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "dockerfile", "scanner") - s.parserOpts = append(s.parserOpts, options.ParserWithDebug(writer)) -} - func (s *Scanner) SetTraceWriter(_ io.Writer) { // handled by rego later - nothing to do for now... } @@ -104,6 +98,7 @@ func (s *Scanner) SetRegoErrorLimit(_ int) { func NewScanner(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("dockerfile scanner"), } for _, opt := range opts { opt(s) @@ -144,7 +139,7 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R if err != nil { return nil, err } - s.debug.Log("Scanning %s...", path) + s.logger.Debug("Scanning", log.FilePath(path)) return s.scanRego(ctx, fsys, rego.Input{ Path: path, Contents: dockerfile.ToRego(), @@ -159,7 +154,6 @@ func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { } regoScanner := rego.NewScanner(types.SourceDockerfile, s.options...) - regoScanner.SetParentDebugLogger(s.debug) if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { return nil, err } diff --git a/pkg/iac/scanners/dockerfile/scanner_test.go b/pkg/iac/scanners/dockerfile/scanner_test.go index 72c48f6a9f5e..437c37ec9660 100644 --- a/pkg/iac/scanners/dockerfile/scanner_test.go +++ b/pkg/iac/scanners/dockerfile/scanner_test.go @@ -566,12 +566,10 @@ COPY --from=dep /binary /` fs := testutil.CreateFS(t, regoMap) var traceBuf bytes.Buffer - var debugBuf bytes.Buffer scanner := NewScanner( options.ScannerWithPolicyDirs("rules"), options.ScannerWithTrace(&traceBuf), - options.ScannerWithDebug(&debugBuf), options.ScannerWithRegoErrorLimits(0), ) diff --git a/pkg/iac/scanners/helm/options.go b/pkg/iac/scanners/helm/options.go index 6ac412bf1e34..c4439d928845 100644 --- a/pkg/iac/scanners/helm/options.go +++ b/pkg/iac/scanners/helm/options.go @@ -5,55 +5,50 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) -type ConfigurableHelmScanner interface { - options.ConfigurableScanner - AddParserOptions(options ...options.ParserOption) -} - func ScannerWithValuesFile(paths ...string) options.ScannerOption { return func(s options.ConfigurableScanner) { - if helmScanner, ok := s.(ConfigurableHelmScanner); ok { - helmScanner.AddParserOptions(parser.OptionWithValuesFile(paths...)) + if helmScanner, ok := s.(*Scanner); ok { + helmScanner.addParserOptions(parser.OptionWithValuesFile(paths...)) } } } func ScannerWithValues(values ...string) options.ScannerOption { return func(s options.ConfigurableScanner) { - if helmScanner, ok := s.(ConfigurableHelmScanner); ok { - helmScanner.AddParserOptions(parser.OptionWithValues(values...)) + if helmScanner, ok := s.(*Scanner); ok { + helmScanner.addParserOptions(parser.OptionWithValues(values...)) } } } func ScannerWithFileValues(values ...string) options.ScannerOption { return func(s options.ConfigurableScanner) { - if helmScanner, ok := s.(ConfigurableHelmScanner); ok { - helmScanner.AddParserOptions(parser.OptionWithFileValues(values...)) + if helmScanner, ok := s.(*Scanner); ok { + helmScanner.addParserOptions(parser.OptionWithFileValues(values...)) } } } func ScannerWithStringValues(values ...string) options.ScannerOption { return func(s options.ConfigurableScanner) { - if helmScanner, ok := s.(ConfigurableHelmScanner); ok { - helmScanner.AddParserOptions(parser.OptionWithStringValues(values...)) + if helmScanner, ok := s.(*Scanner); ok { + helmScanner.addParserOptions(parser.OptionWithStringValues(values...)) } } } func ScannerWithAPIVersions(values ...string) options.ScannerOption { return func(s options.ConfigurableScanner) { - if helmScanner, ok := s.(ConfigurableHelmScanner); ok { - helmScanner.AddParserOptions(parser.OptionWithAPIVersions(values...)) + if helmScanner, ok := s.(*Scanner); ok { + helmScanner.addParserOptions(parser.OptionWithAPIVersions(values...)) } } } func ScannerWithKubeVersion(values string) options.ScannerOption { return func(s options.ConfigurableScanner) { - if helmScanner, ok := s.(ConfigurableHelmScanner); ok { - helmScanner.AddParserOptions(parser.OptionWithKubeVersion(values)) + if helmScanner, ok := s.(*Scanner); ok { + helmScanner.addParserOptions(parser.OptionWithKubeVersion(values)) } } } diff --git a/pkg/iac/scanners/helm/parser/option.go b/pkg/iac/scanners/helm/parser/option.go index 6de98d765182..0f2eae3cc291 100644 --- a/pkg/iac/scanners/helm/parser/option.go +++ b/pkg/iac/scanners/helm/parser/option.go @@ -1,9 +1,6 @@ package parser -import "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - type ConfigurableHelmParser interface { - options.ConfigurableParser SetValuesFile(...string) SetValues(...string) SetFileValues(...string) @@ -12,50 +9,40 @@ type ConfigurableHelmParser interface { SetKubeVersion(string) } -func OptionWithValuesFile(paths ...string) options.ParserOption { - return func(p options.ConfigurableParser) { - if helmParser, ok := p.(ConfigurableHelmParser); ok { - helmParser.SetValuesFile(paths...) - } +type Option func(p *Parser) + +func OptionWithValuesFile(paths ...string) Option { + return func(p *Parser) { + p.valuesFiles = paths } } -func OptionWithValues(values ...string) options.ParserOption { - return func(p options.ConfigurableParser) { - if helmParser, ok := p.(ConfigurableHelmParser); ok { - helmParser.SetValues(values...) - } +func OptionWithValues(values ...string) Option { + return func(p *Parser) { + p.values = values } } -func OptionWithFileValues(values ...string) options.ParserOption { - return func(p options.ConfigurableParser) { - if helmParser, ok := p.(ConfigurableHelmParser); ok { - helmParser.SetValues(values...) - } +func OptionWithFileValues(values ...string) Option { + return func(p *Parser) { + p.fileValues = values } } -func OptionWithStringValues(values ...string) options.ParserOption { - return func(p options.ConfigurableParser) { - if helmParser, ok := p.(ConfigurableHelmParser); ok { - helmParser.SetValues(values...) - } +func OptionWithStringValues(values ...string) Option { + return func(p *Parser) { + p.stringValues = values } } -func OptionWithAPIVersions(values ...string) options.ParserOption { - return func(p options.ConfigurableParser) { - if helmParser, ok := p.(ConfigurableHelmParser); ok { - helmParser.SetAPIVersions(values...) - } +func OptionWithAPIVersions(values ...string) Option { + return func(p *Parser) { + p.apiVersions = values } } -func OptionWithKubeVersion(value string) options.ParserOption { - return func(p options.ConfigurableParser) { - if helmParser, ok := p.(ConfigurableHelmParser); ok { - helmParser.SetKubeVersion(value) - } +func OptionWithKubeVersion(value string) Option { + return func(p *Parser) { + p.kubeVersion = value } } diff --git a/pkg/iac/scanners/helm/parser/parser.go b/pkg/iac/scanners/helm/parser/parser.go index bf4a9fc91721..e38f032b6221 100644 --- a/pkg/iac/scanners/helm/parser/parser.go +++ b/pkg/iac/scanners/helm/parser/parser.go @@ -5,7 +5,6 @@ import ( "context" "errors" "fmt" - "io" "io/fs" "path/filepath" "regexp" @@ -21,19 +20,18 @@ import ( "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/detection" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) var manifestNameRegex = regexp.MustCompile("# Source: [^/]+/(.+)") type Parser struct { + logger *log.Logger helmClient *action.Install rootPath string ChartSource string filepaths []string - debug debug.Logger workingFS fs.FS valuesFiles []string values []string @@ -48,35 +46,7 @@ type ChartFile struct { ManifestContent string } -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "helm", "parser") -} - -func (p *Parser) SetValuesFile(s ...string) { - p.valuesFiles = s -} - -func (p *Parser) SetValues(values ...string) { - p.values = values -} - -func (p *Parser) SetFileValues(values ...string) { - p.fileValues = values -} - -func (p *Parser) SetStringValues(values ...string) { - p.stringValues = values -} - -func (p *Parser) SetAPIVersions(values ...string) { - p.apiVersions = values -} - -func (p *Parser) SetKubeVersion(value string) { - p.kubeVersion = value -} - -func New(path string, opts ...options.ParserOption) (*Parser, error) { +func New(path string, opts ...Option) (*Parser, error) { client := action.NewInstall(&action.Configuration{}) client.DryRun = true // don't do anything @@ -86,6 +56,7 @@ func New(path string, opts ...options.ParserOption) (*Parser, error) { p := &Parser{ helmClient: client, ChartSource: path, + logger: log.WithPrefix("helm parser"), } for _, option := range opts { diff --git a/pkg/iac/scanners/helm/parser/parser_tar.go b/pkg/iac/scanners/helm/parser/parser_tar.go index f8d692eaa977..8e2ba97a81d2 100644 --- a/pkg/iac/scanners/helm/parser/parser_tar.go +++ b/pkg/iac/scanners/helm/parser/parser_tar.go @@ -14,6 +14,7 @@ import ( "github.com/liamg/memoryfs" "github.com/aquasecurity/trivy/pkg/iac/detection" + "github.com/aquasecurity/trivy/pkg/log" ) var errSkipFS = errors.New("skip parse FS") @@ -75,13 +76,13 @@ func (p *Parser) addTarToFS(archivePath string) (fs.FS, error) { return nil, err } case tar.TypeReg: - p.debug.Log("Unpacking tar entry %s", targetPath) + p.logger.Debug("Unpacking tar entry", log.FilePath(targetPath)) if err := copyFile(tarFS, tr, targetPath); err != nil { return nil, err } case tar.TypeSymlink: if path.IsAbs(link) { - p.debug.Log("Symlink %s is absolute, skipping", link) + p.logger.Debug("Symlink is absolute, skipping", log.String("link", link)) continue } diff --git a/pkg/iac/scanners/helm/scanner.go b/pkg/iac/scanners/helm/scanner.go index 944187df4dee..305a116b56b7 100644 --- a/pkg/iac/scanners/helm/scanner.go +++ b/pkg/iac/scanners/helm/scanner.go @@ -11,7 +11,6 @@ import ( "github.com/liamg/memoryfs" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/detection" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" @@ -21,6 +20,7 @@ import ( kparser "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes/parser" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ scanners.FSScanner = (*Scanner)(nil) @@ -29,9 +29,9 @@ var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { policyDirs []string dataDirs []string - debug debug.Logger + logger *log.Logger options []options.ScannerOption - parserOptions []options.ParserOption + parserOptions []parser.Option policyReaders []io.Reader loadEmbeddedLibraries bool loadEmbeddedPolicies bool @@ -60,6 +60,7 @@ func (s *Scanner) SetFrameworks(frameworks []framework.Framework) { func New(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("helm scanner"), } for _, option := range opts { @@ -68,7 +69,7 @@ func New(opts ...options.ScannerOption) *Scanner { return s } -func (s *Scanner) AddParserOptions(opts ...options.ParserOption) { +func (s *Scanner) addParserOptions(opts ...parser.Option) { s.parserOptions = append(s.parserOptions, opts...) } @@ -88,11 +89,6 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "helm", "scanner") - s.parserOptions = append(s.parserOptions, options.ParserWithDebug(writer)) -} - func (s *Scanner) SetTraceWriter(_ io.Writer) { // handled by rego later - nothing to do for now... } @@ -180,13 +176,16 @@ func (s *Scanner) getScanResults(path string, ctx context.Context, target fs.FS) chartFiles, err := helmParser.RenderedChartFiles() if err != nil { // not valid helm, maybe some other yaml etc., abort - s.debug.Log("Failed to render Chart files: %s", err) + s.logger.Error( + "Failed to render Chart files", + log.FilePath(path), log.Err(err), + ) return nil, nil } for _, file := range chartFiles { file := file - s.debug.Log("Processing rendered chart file: %s", file.TemplateFilePath) + s.logger.Debug("Processing rendered chart file", log.FilePath(file.TemplateFilePath)) manifests, err := kparser.New().Parse(strings.NewReader(file.ManifestContent), file.TemplateFilePath) if err != nil { @@ -229,7 +228,6 @@ func (s *Scanner) initRegoScanner(srcFS fs.FS) error { return nil } regoScanner := rego.NewScanner(types.SourceKubernetes, s.options...) - regoScanner.SetParentDebugLogger(s.debug) if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { return err } diff --git a/pkg/iac/scanners/helm/test/option_test.go b/pkg/iac/scanners/helm/test/option_test.go index 8efb03f16116..05bf96ee01d3 100644 --- a/pkg/iac/scanners/helm/test/option_test.go +++ b/pkg/iac/scanners/helm/test/option_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/helm/parser" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) func Test_helm_parser_with_options_with_values_file(t *testing.T) { @@ -34,7 +33,7 @@ func Test_helm_parser_with_options_with_values_file(t *testing.T) { t.Logf("Running test: %s", test.testName) - var opts []options.ParserOption + var opts []parser.Option if test.valuesFile != "" { opts = append(opts, parser.OptionWithValuesFile(test.valuesFile)) @@ -84,7 +83,7 @@ func Test_helm_parser_with_options_with_set_value(t *testing.T) { t.Logf("Running test: %s", test.testName) - var opts []options.ParserOption + var opts []parser.Option if test.valuesFile != "" { opts = append(opts, parser.OptionWithValuesFile(test.valuesFile)) @@ -138,7 +137,7 @@ func Test_helm_parser_with_options_with_api_versions(t *testing.T) { t.Logf("Running test: %s", test.testName) - var opts []options.ParserOption + var opts []parser.Option if len(test.apiVersions) > 0 { opts = append(opts, parser.OptionWithAPIVersions(test.apiVersions...)) @@ -195,7 +194,7 @@ func Test_helm_parser_with_options_with_kube_versions(t *testing.T) { t.Logf("Running test: %s", test.testName) - var opts []options.ParserOption + var opts []parser.Option opts = append(opts, parser.OptionWithKubeVersion(test.kubeVersion)) diff --git a/pkg/iac/scanners/json/parser/parser.go b/pkg/iac/scanners/json/parser/parser.go index 8f35794229ef..c97e54b40bd9 100644 --- a/pkg/iac/scanners/json/parser/parser.go +++ b/pkg/iac/scanners/json/parser/parser.go @@ -3,31 +3,21 @@ package parser import ( "context" "encoding/json" - "io" "io/fs" "path/filepath" - "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ options.ConfigurableParser = (*Parser)(nil) - type Parser struct { - debug debug.Logger -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "json", "parser") + logger *log.Logger } // New creates a new parser -func New(opts ...options.ParserOption) *Parser { - p := &Parser{} - for _, opt := range opts { - opt(p) +func New() *Parser { + return &Parser{ + logger: log.WithPrefix("json parser"), } - return p } func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]any, error) { @@ -48,7 +38,7 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st df, err := p.ParseFile(ctx, target, path) if err != nil { - p.debug.Log("Parse error in '%s': %s", path, err) + p.logger.Error("Parse error", log.FilePath(path), log.Err(err)) return nil } diff --git a/pkg/iac/scanners/json/scanner.go b/pkg/iac/scanners/json/scanner.go index 7a18df363823..8991120be26f 100644 --- a/pkg/iac/scanners/json/scanner.go +++ b/pkg/iac/scanners/json/scanner.go @@ -6,7 +6,6 @@ import ( "io/fs" "sync" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" @@ -14,6 +13,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/json/parser" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ scanners.FSScanner = (*Scanner)(nil) @@ -21,13 +21,12 @@ var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { mu sync.Mutex - debug debug.Logger + logger *log.Logger policyDirs []string policyReaders []io.Reader parser *parser.Parser regoScanner *rego.Scanner options []options.ScannerOption - parserOpts []options.ParserOption frameworks []framework.Framework spec string loadEmbeddedPolicies bool @@ -60,11 +59,6 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "json", "scanner") - s.parserOpts = append(s.parserOpts, options.ParserWithDebug(writer)) -} - func (s *Scanner) SetTraceWriter(_ io.Writer) { } @@ -90,11 +84,12 @@ func (s *Scanner) SetRegoErrorLimit(_ int) {} func NewScanner(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("json scanner"), + parser: parser.New(), } for _, opt := range opts { opt(s) } - s.parser = parser.New(s.parserOpts...) return s } @@ -134,7 +129,7 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R if err != nil { return nil, err } - s.debug.Log("Scanning %s...", path) + s.logger.Debug("Scanning", log.FilePath(path)) return s.scanRego(ctx, fsys, rego.Input{ Path: path, Contents: parsed, @@ -148,7 +143,6 @@ func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceJSON, s.options...) - regoScanner.SetParentDebugLogger(s.debug) if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { return nil, err } diff --git a/pkg/iac/scanners/kubernetes/parser/parser.go b/pkg/iac/scanners/kubernetes/parser/parser.go index e2f225a9fb86..f3ca7d613562 100644 --- a/pkg/iac/scanners/kubernetes/parser/parser.go +++ b/pkg/iac/scanners/kubernetes/parser/parser.go @@ -13,27 +13,18 @@ import ( "gopkg.in/yaml.v3" kyaml "sigs.k8s.io/yaml" - "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ options.ConfigurableParser = (*Parser)(nil) - type Parser struct { - debug debug.Logger -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "kubernetes", "parser") + logger *log.Logger } // New creates a new K8s parser -func New(opts ...options.ParserOption) *Parser { - p := &Parser{} - for _, option := range opts { - option(p) +func New() *Parser { + return &Parser{ + logger: log.WithPrefix("k8s parser"), } - return p } func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string][]any, error) { @@ -53,7 +44,7 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st parsed, err := p.ParseFile(ctx, target, path) if err != nil { - p.debug.Log("Parse error in '%s': %s", path, err) + p.logger.Error("Parse error", log.FilePath(path), log.Err(err)) return nil } diff --git a/pkg/iac/scanners/kubernetes/scanner.go b/pkg/iac/scanners/kubernetes/scanner.go index 9612fe03ebe4..e9e9eacd73e8 100644 --- a/pkg/iac/scanners/kubernetes/scanner.go +++ b/pkg/iac/scanners/kubernetes/scanner.go @@ -10,7 +10,6 @@ import ( "github.com/liamg/memoryfs" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" @@ -18,6 +17,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes/parser" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ scanners.FSScanner = (*Scanner)(nil) @@ -25,9 +25,8 @@ var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { mu sync.Mutex - debug debug.Logger + logger *log.Logger options []options.ScannerOption - parserOpts []options.ParserOption policyDirs []string policyReaders []io.Reader regoScanner *rego.Scanner @@ -63,11 +62,6 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "kubernetes", "scanner") - s.parserOpts = append(s.parserOpts, options.ParserWithDebug(writer)) -} - func (s *Scanner) SetTraceWriter(_ io.Writer) { } @@ -94,11 +88,12 @@ func (s *Scanner) SetRegoErrorLimit(_ int) {} func NewScanner(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("k8s scanner"), + parser: parser.New(), } for _, opt := range opts { opt(s) } - s.parser = parser.New(s.parserOpts...) return s } @@ -113,7 +108,6 @@ func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceKubernetes, s.options...) - regoScanner.SetParentDebugLogger(s.debug) if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { return nil, err } @@ -163,7 +157,7 @@ func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, dir string) (scan.Re return nil, err } - s.debug.Log("Scanning %d files...", len(inputs)) + s.logger.Debug("Scanning files", log.Int("count", len(inputs))) results, err := regoScanner.ScanInput(ctx, inputs...) if err != nil { return nil, err diff --git a/pkg/iac/scanners/kubernetes/scanner_test.go b/pkg/iac/scanners/kubernetes/scanner_test.go index a75173f67c0e..d972e52e8e80 100644 --- a/pkg/iac/scanners/kubernetes/scanner_test.go +++ b/pkg/iac/scanners/kubernetes/scanner_test.go @@ -486,7 +486,6 @@ deny[msg] { func Test_FileScanWithMetadata(t *testing.T) { results, err := NewScanner( - options.ScannerWithDebug(os.Stdout), options.ScannerWithTrace(os.Stdout), options.ScannerWithPolicyReader(strings.NewReader(`package defsec @@ -526,7 +525,6 @@ spec: func Test_FileScanExampleWithResultFunction(t *testing.T) { results, err := NewScanner( - options.ScannerWithDebug(os.Stdout), options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), options.ScannerWithPolicyReader(strings.NewReader(`package defsec diff --git a/pkg/iac/scanners/options/parser.go b/pkg/iac/scanners/options/parser.go deleted file mode 100644 index e411126a6329..000000000000 --- a/pkg/iac/scanners/options/parser.go +++ /dev/null @@ -1,16 +0,0 @@ -package options - -import "io" - -type ConfigurableParser interface { - SetDebugWriter(io.Writer) -} - -type ParserOption func(s ConfigurableParser) - -// ParserWithDebug specifies an io.Writer for debug logs - if not set, they are discarded -func ParserWithDebug(w io.Writer) ParserOption { - return func(s ConfigurableParser) { - s.SetDebugWriter(w) - } -} diff --git a/pkg/iac/scanners/options/scanner.go b/pkg/iac/scanners/options/scanner.go index f5b3b982ee67..4f07c4d14b0a 100644 --- a/pkg/iac/scanners/options/scanner.go +++ b/pkg/iac/scanners/options/scanner.go @@ -8,7 +8,6 @@ import ( ) type ConfigurableScanner interface { - SetDebugWriter(io.Writer) SetTraceWriter(io.Writer) SetPerResultTracingEnabled(bool) SetPolicyDirs(...string) @@ -47,13 +46,6 @@ func ScannerWithPolicyReader(readers ...io.Reader) ScannerOption { } } -// ScannerWithDebug specifies an io.Writer for debug logs - if not set, they are discarded -func ScannerWithDebug(w io.Writer) ScannerOption { - return func(s ConfigurableScanner) { - s.SetDebugWriter(w) - } -} - func ScannerWithEmbeddedPolicies(embedded bool) ScannerOption { return func(s ConfigurableScanner) { s.SetUseEmbeddedPolicies(embedded) diff --git a/pkg/iac/scanners/terraform/executor/executor.go b/pkg/iac/scanners/terraform/executor/executor.go index 88dc1fa9801c..c6337a2bfd1d 100644 --- a/pkg/iac/scanners/terraform/executor/executor.go +++ b/pkg/iac/scanners/terraform/executor/executor.go @@ -8,7 +8,6 @@ import ( "github.com/zclconf/go-cty/cty" adapter "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/ignore" "github.com/aquasecurity/trivy/pkg/iac/rego" @@ -16,12 +15,13 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/terraform" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) // Executor scans HCL blocks by running all registered rules against them type Executor struct { workspaceName string - debug debug.Logger + logger *log.Logger resultsFilters []func(scan.Results) scan.Results regoScanner *rego.Scanner regoOnly bool @@ -32,6 +32,7 @@ type Executor struct { func New(options ...Option) *Executor { s := &Executor{ regoOnly: false, + logger: log.WithPrefix("terraform executor"), } for _, option := range options { option(s) @@ -41,31 +42,30 @@ func New(options ...Option) *Executor { func (e *Executor) Execute(modules terraform.Modules) (scan.Results, error) { - e.debug.Log("Adapting modules...") + e.logger.Debug("Adapting modules...") infra := adapter.Adapt(modules) - e.debug.Log("Adapted %d module(s) into defsec state data.", len(modules)) + e.logger.Debug("Adapted module(s) into state data.", log.Int("count", len(modules))) threads := runtime.NumCPU() if threads > 1 { threads-- } - e.debug.Log("Using max routines of %d", threads) + e.logger.Debug("Using max routines", log.Int("count", threads)) registeredRules := rules.GetRegistered(e.frameworks...) - e.debug.Log("Initialized %d rule(s).", len(registeredRules)) + e.logger.Debug("Initialized rule(s).", log.Int("count", len(registeredRules))) pool := NewPool(threads, registeredRules, modules, infra, e.regoScanner, e.regoOnly) - e.debug.Log("Created pool with %d worker(s) to apply rules.", threads) results, err := pool.Run() if err != nil { return nil, err } - e.debug.Log("Finished applying rules.") + e.logger.Debug("Finished applying rules.") - e.debug.Log("Applying ignores...") + e.logger.Debug("Applying ignores...") var ignores ignore.Rules for _, module := range modules { ignores = append(ignores, module.Ignores()...) @@ -79,7 +79,10 @@ func (e *Executor) Execute(modules terraform.Modules) (scan.Results, error) { results.Ignore(ignores, ignorers) for _, ignored := range results.GetIgnored() { - e.debug.Log("Ignored '%s' at '%s'.", ignored.Rule().LongID(), ignored.Range()) + e.logger.Info("Ignore finding", + log.String("rule", ignored.Rule().LongID()), + log.String("range", ignored.Range().String()), + ) } results = e.filterResults(results) @@ -91,11 +94,12 @@ func (e *Executor) Execute(modules terraform.Modules) (scan.Results, error) { func (e *Executor) filterResults(results scan.Results) scan.Results { if len(e.resultsFilters) > 0 && len(results) > 0 { before := len(results.GetIgnored()) - e.debug.Log("Applying %d results filters to %d results...", len(results), before) + e.logger.Debug("Applying results filters...") for _, filter := range e.resultsFilters { results = filter(results) } - e.debug.Log("Filtered out %d results.", len(results.GetIgnored())-before) + e.logger.Debug("Applied results filters.", + log.Int("count", len(results.GetIgnored())-before)) } return results diff --git a/pkg/iac/scanners/terraform/executor/option.go b/pkg/iac/scanners/terraform/executor/option.go index a58d72867b54..d2414878c98f 100644 --- a/pkg/iac/scanners/terraform/executor/option.go +++ b/pkg/iac/scanners/terraform/executor/option.go @@ -1,9 +1,6 @@ package executor import ( - "io" - - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" @@ -23,12 +20,6 @@ func OptionWithResultsFilter(f func(scan.Results) scan.Results) Option { } } -func OptionWithDebugWriter(w io.Writer) Option { - return func(s *Executor) { - s.debug = debug.New(w, "terraform", "executor") - } -} - func OptionWithWorkspaceName(workspaceName string) Option { return func(s *Executor) { s.workspaceName = workspaceName diff --git a/pkg/iac/scanners/terraform/fs_test.go b/pkg/iac/scanners/terraform/fs_test.go index 79b0844fe983..649062792514 100644 --- a/pkg/iac/scanners/terraform/fs_test.go +++ b/pkg/iac/scanners/terraform/fs_test.go @@ -13,7 +13,6 @@ import ( func Test_OS_FS(t *testing.T) { s := New( - options.ScannerWithDebug(os.Stderr), options.ScannerWithEmbeddedPolicies(true), options.ScannerWithEmbeddedLibraries(true), ) diff --git a/pkg/iac/scanners/terraform/module_test.go b/pkg/iac/scanners/terraform/module_test.go index 5469204e741f..d0d289a9d562 100644 --- a/pkg/iac/scanners/terraform/module_test.go +++ b/pkg/iac/scanners/terraform/module_test.go @@ -1,10 +1,7 @@ package terraform import ( - "bytes" "context" - "fmt" - "os" "testing" "github.com/stretchr/testify/require" @@ -14,7 +11,6 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/providers" "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scan" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" "github.com/aquasecurity/trivy/pkg/iac/severity" @@ -86,9 +82,7 @@ resource "problem" "uhoh" { `, }) - debug := bytes.NewBuffer([]byte{}) - - p := parser.New(fs, "", parser.OptionStopOnHCLError(true), options.ParserWithDebug(debug)) + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) err := p.ParseFS(context.TODO(), "project") require.NoError(t, err) modules, _, err := p.EvaluateAll(context.TODO()) @@ -98,9 +92,6 @@ resource "problem" "uhoh" { require.NoError(t, err) testutil.AssertRuleFound(t, badRule.LongID(), results, "") - if t.Failed() { - fmt.Println(debug.String()) - } } func Test_ProblemInModuleInSiblingDir(t *testing.T) { @@ -293,7 +284,7 @@ resource "problem" "uhoh" { `, }) - p := parser.New(fs, "", parser.OptionStopOnHCLError(true), options.ParserWithDebug(os.Stderr)) + p := parser.New(fs, "", parser.OptionStopOnHCLError(true)) err := p.ParseFS(context.TODO(), "project") require.NoError(t, err) modules, _, err := p.EvaluateAll(context.TODO()) diff --git a/pkg/iac/scanners/terraform/options.go b/pkg/iac/scanners/terraform/options.go index f5a0d2223534..d256f0a34daa 100644 --- a/pkg/iac/scanners/terraform/options.go +++ b/pkg/iac/scanners/terraform/options.go @@ -14,7 +14,7 @@ type ConfigurableTerraformScanner interface { options.ConfigurableScanner SetForceAllDirs(bool) AddExecutorOptions(options ...executor.Option) - AddParserOptions(options ...options.ParserOption) + AddParserOptions(options ...parser.Option) } func ScannerWithTFVarsPaths(paths ...string) options.ScannerOption { diff --git a/pkg/iac/scanners/terraform/parser/evaluator.go b/pkg/iac/scanners/terraform/parser/evaluator.go index e0231d2311a5..811f618787b5 100644 --- a/pkg/iac/scanners/terraform/parser/evaluator.go +++ b/pkg/iac/scanners/terraform/parser/evaluator.go @@ -13,11 +13,11 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/ignore" "github.com/aquasecurity/trivy/pkg/iac/terraform" tfcontext "github.com/aquasecurity/trivy/pkg/iac/terraform/context" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) const ( @@ -25,6 +25,7 @@ const ( ) type evaluator struct { + logger *log.Logger filesystem fs.FS ctx *tfcontext.Context blocks terraform.Blocks @@ -35,7 +36,6 @@ type evaluator struct { moduleName string ignores ignore.Rules parentParser *Parser - debug debug.Logger allowDownloads bool skipCachedModules bool } @@ -52,7 +52,7 @@ func newEvaluator( moduleMetadata *modulesMetadata, workspace string, ignores ignore.Rules, - logger debug.Logger, + logger *log.Logger, allowDownloads bool, skipCachedModules bool, ) *evaluator { @@ -84,7 +84,7 @@ func newEvaluator( inputVars: inputVars, moduleMetadata: moduleMetadata, ignores: ignores, - debug: logger, + logger: logger, allowDownloads: allowDownloads, skipCachedModules: skipCachedModules, } @@ -114,20 +114,24 @@ func (e *evaluator) exportOutputs() cty.Value { continue } data[block.Label()] = attr.Value() - e.debug.Log("Added module output %s=%s.", block.Label(), attr.Value().GoString()) + e.logger.Debug( + "Added module output", + log.String("block", block.Label()), + log.String("value", attr.Value().GoString()), + ) } return cty.ObjectVal(data) } func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[string]fs.FS) { - fsKey := types.CreateFSKey(e.filesystem) - e.debug.Log("Filesystem key is '%s'", fsKey) + e.logger.Debug("Starting module evaluation...", log.String("path", e.modulePath)) - fsMap := make(map[string]fs.FS) - fsMap[fsKey] = e.filesystem + fsKey := types.CreateFSKey(e.filesystem) + fsMap := map[string]fs.FS{ + fsKey: e.filesystem, + } - e.debug.Log("Starting module evaluation...") e.evaluateSteps() // expand out resources and modules via count, for-each and dynamic @@ -135,21 +139,37 @@ func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[str e.blocks = e.expandBlocks(e.blocks) e.blocks = e.expandBlocks(e.blocks) - e.debug.Log("Starting submodule evaluation...") + submodules := e.evaluateSubmodules(ctx, fsMap) + + e.logger.Debug("Starting post-submodules evaluation...") + e.evaluateSteps() + + e.logger.Debug("Module evaluation complete.") + rootModule := terraform.NewModule(e.projectRootPath, e.modulePath, e.blocks, e.ignores) + return append(terraform.Modules{rootModule}, submodules...), fsMap +} + +func (e *evaluator) evaluateSubmodules(ctx context.Context, fsMap map[string]fs.FS) terraform.Modules { submodules := e.loadSubmodules(ctx) + if len(submodules) == 0 { + return nil + } + + e.logger.Debug("Starting submodules evaluation...") + for i := 0; i < maxContextIterations; i++ { changed := false for _, sm := range submodules { changed = changed || e.evaluateSubmodule(ctx, sm) } if !changed { - e.debug.Log("All submodules are evaluated at i=%d", i) + e.logger.Debug("All submodules are evaluated", log.Int("loop", i)) break } } - e.debug.Log("Starting post-submodule evaluation...") + e.logger.Debug("Starting post-submodule evaluation...") e.evaluateSteps() var modules terraform.Modules @@ -158,11 +178,8 @@ func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[str fsMap = lo.Assign(fsMap, sm.fsMap) } - e.debug.Log("Finished processing %d submodule(s).", len(modules)) - - e.debug.Log("Module evaluation complete.") - rootModule := terraform.NewModule(e.projectRootPath, e.modulePath, e.blocks, e.ignores) - return append(terraform.Modules{rootModule}, modules...), fsMap + e.logger.Debug("Finished processing submodule(s).", log.Int("count", len(modules))) + return modules } type submodule struct { @@ -181,7 +198,7 @@ func (e *evaluator) loadSubmodules(ctx context.Context) []*submodule { if errors.Is(err, ErrNoFiles) { continue } else if err != nil { - e.debug.Log("Failed to load submodule '%s': %s.", definition.Name, err) + e.logger.Error("Failed to load submodule", log.String("name", definition.Name), log.Err(err)) continue } @@ -199,12 +216,12 @@ func (e *evaluator) evaluateSubmodule(ctx context.Context, sm *submodule) bool { inputVars := sm.definition.inputVars() if len(sm.modules) > 0 { if reflect.DeepEqual(inputVars, sm.lastState) { - e.debug.Log("Submodule %s inputs unchanged", sm.definition.Name) + e.logger.Debug("Submodule inputs unchanged", log.String("name", sm.definition.Name)) return false } } - e.debug.Log("Evaluating submodule %s", sm.definition.Name) + e.logger.Debug("Evaluating submodule", log.String("name", sm.definition.Name)) sm.eval.inputVars = inputVars sm.modules, sm.fsMap = sm.eval.EvaluateAll(ctx) outputs := sm.eval.exportOutputs() @@ -223,12 +240,12 @@ func (e *evaluator) evaluateSteps() { var lastContext hcl.EvalContext for i := 0; i < maxContextIterations; i++ { - e.debug.Log("Starting iteration %d", i) + e.logger.Debug("Starting iteration", log.Int("iteration", i)) e.evaluateStep() // if ctx matches the last evaluation, we can bail, nothing left to resolve if i > 0 && reflect.DeepEqual(lastContext.Variables, e.ctx.Inner().Variables) { - e.debug.Log("Context unchanged at i=%d", i) + e.logger.Debug("Context unchanged", log.Int("iteration", i)) break } if len(e.ctx.Inner().Variables) != len(lastContext.Variables) { @@ -283,6 +300,7 @@ func isBlockSupportsForEachMetaArgument(block *terraform.Block) bool { } func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool) terraform.Blocks { + var forEachFiltered terraform.Blocks for _, block := range blocks { @@ -297,6 +315,10 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool forEachVal := forEachAttr.Value() if forEachVal.IsNull() || !forEachVal.IsKnown() || !forEachAttr.IsIterable() { + e.logger.Error(`Failed to expand block. Invalid "for-each" argument. Must be known and iterable.`, + log.String("block", block.FullName()), + log.String("value", forEachVal.GoString()), + ) continue } @@ -310,9 +332,12 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool // instances are identified by a map key (or set member) from the value provided to for_each idx, err := convert.Convert(key, cty.String) if err != nil { - e.debug.Log( - `Invalid "for-each" argument: map key (or set value) is not a string, but %s`, - key.Type().FriendlyName(), + e.logger.Error( + `Failed to expand block. Invalid "for-each" argument: map key (or set value) is not a string`, + log.String("block", block.FullName()), + log.String("key", key.GoString()), + log.String("value", val.GoString()), + log.Err(err), ) return } @@ -324,7 +349,13 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool !forEachVal.Type().IsMapType() && !isDynamic { stringVal, err := convert.Convert(val, cty.String) if err != nil { - e.debug.Log("Failed to convert for-each arg %v to string", val) + e.logger.Error( + "Failed to expand block. Invalid 'for-each' argument: value is not a string", + log.String("block", block.FullName()), + log.String("key", idx.AsString()), + log.String("value", val.GoString()), + log.Err(err), + ) return } idx = stringVal @@ -349,7 +380,8 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool ctx.Set(idx, refs[0].TypeLabel(), "key") ctx.Set(val, refs[0].TypeLabel(), "value") } else { - e.debug.Log("Ignoring iterator attribute in dynamic block, expected one reference but got %d", len(refs)) + e.logger.Debug("Ignoring iterator attribute in dynamic block, expected one reference", + log.Int("refs", len(refs))) } } } @@ -367,7 +399,10 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool // So we must replace the old resource with a map with the attributes of the resource. e.ctx.Replace(cty.ObjectVal(clones), metadata.Reference()) } - e.debug.Log("Expanded block '%s' into %d clones via 'for_each' attribute.", block.LocalName(), len(clones)) + e.logger.Debug("Expanded block into clones via 'for_each' attribute.", + log.String("block", block.FullName()), + log.Int("clones", len(clones)), + ) } return forEachFiltered @@ -409,7 +444,11 @@ func (e *evaluator) expandBlockCounts(blocks terraform.Blocks) terraform.Blocks } else { e.ctx.SetByDot(cty.TupleVal(clones), metadata.Reference()) } - e.debug.Log("Expanded block '%s' into %d clones via 'count' attribute.", block.LocalName(), len(clones)) + e.logger.Debug( + "Expanded block into clones via 'count' attribute.", + log.String("block", block.FullName()), + log.Int("clones", len(clones)), + ) } return countFiltered diff --git a/pkg/iac/scanners/terraform/parser/load_module.go b/pkg/iac/scanners/terraform/parser/load_module.go index 0bd6a6395936..78ebe3430b4e 100644 --- a/pkg/iac/scanners/terraform/parser/load_module.go +++ b/pkg/iac/scanners/terraform/parser/load_module.go @@ -11,6 +11,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers" "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/log" ) type ModuleDefinition struct { @@ -42,11 +43,11 @@ func (e *evaluator) loadModules(ctx context.Context) []*ModuleDefinition { } moduleDefinition, err := e.loadModule(ctx, moduleBlock) if err != nil { - e.debug.Log("Failed to load module %q. Maybe try 'terraform init'?", err) + e.logger.Error("Failed to load module. Maybe try 'terraform init'?", log.Err(err)) continue } - e.debug.Log("Loaded module %q from %q.", moduleDefinition.Name, moduleDefinition.Path) + e.logger.Debug("Loaded module", log.String("name", moduleDefinition.Name), log.FilePath(moduleDefinition.Path)) moduleDefinitions = append(moduleDefinitions, moduleDefinition) } @@ -77,7 +78,7 @@ func (e *evaluator) loadModule(ctx context.Context, b *terraform.Block) (*Module } if def, err := e.loadModuleFromTerraformCache(ctx, b, source); err == nil { - e.debug.Log("found module '%s' in .terraform/modules", source) + e.logger.Debug("Using module from Terraform cache .terraform/modules", log.String("source", source)) return def, nil } @@ -110,7 +111,11 @@ func (e *evaluator) loadModuleFromTerraformCache(ctx context.Context, b *terrafo } } - e.debug.Log("Module '%s' resolved to path '%s' in filesystem '%s' using modules.json", b.FullName(), modulePath, e.filesystem) + e.logger.Debug("Module resolved using modules.json", + log.String("block", b.FullName()), + log.String("source", source), + log.String("modulePath", modulePath), + ) moduleParser := e.parentParser.newModuleParser(e.filesystem, source, modulePath, b.Label(), b) if err := moduleParser.ParseFS(ctx, modulePath); err != nil { return nil, err @@ -126,7 +131,7 @@ func (e *evaluator) loadModuleFromTerraformCache(ctx context.Context, b *terrafo func (e *evaluator) loadExternalModule(ctx context.Context, b *terraform.Block, source string) (*ModuleDefinition, error) { - e.debug.Log("locating non-initialized module '%s'...", source) + e.logger.Debug("Locating non-initialized module", log.String("source", source)) version := b.GetAttribute("version").AsStringValueOrDefault("", b).Value() opt := resolvers.Options{ @@ -137,7 +142,7 @@ func (e *evaluator) loadExternalModule(ctx context.Context, b *terraform.Block, WorkingDir: e.projectRootPath, Name: b.FullName(), ModulePath: e.modulePath, - DebugLogger: e.debug.Extend("resolver"), + Logger: log.WithPrefix("module resolver"), AllowDownloads: e.allowDownloads, SkipCache: e.skipCachedModules, } @@ -147,7 +152,12 @@ func (e *evaluator) loadExternalModule(ctx context.Context, b *terraform.Block, return nil, err } prefix = path.Join(e.parentParser.moduleSource, prefix) - e.debug.Log("Module '%s' resolved to path '%s' in filesystem '%s' with prefix '%s'", b.FullName(), downloadPath, filesystem, prefix) + e.logger.Debug("Module resolved", + log.String("block", b.FullName()), + log.String("source", source), + log.String("prefix", prefix), + log.FilePath(downloadPath), + ) moduleParser := e.parentParser.newModuleParser(filesystem, prefix, downloadPath, b.Label(), b) if err := moduleParser.ParseFS(ctx, downloadPath); err != nil { return nil, err diff --git a/pkg/iac/scanners/terraform/parser/module_retrieval.go b/pkg/iac/scanners/terraform/parser/module_retrieval.go index 165f64eef1cb..cc374b68eeb7 100644 --- a/pkg/iac/scanners/terraform/parser/module_retrieval.go +++ b/pkg/iac/scanners/terraform/parser/module_retrieval.go @@ -6,6 +6,7 @@ import ( "io/fs" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser/resolvers" + "github.com/aquasecurity/trivy/pkg/log" ) type ModuleResolver interface { @@ -20,12 +21,13 @@ var defaultResolvers = []ModuleResolver{ } func resolveModule(ctx context.Context, current fs.FS, opt resolvers.Options) (filesystem fs.FS, sourcePrefix, downloadPath string, err error) { - opt.Debug("Resolving module '%s' with source: '%s'...", opt.Name, opt.Source) + opt.Logger.Debug("Resolving module", + log.String("name", opt.Name), log.String("source", opt.Source)) for _, resolver := range defaultResolvers { if filesystem, prefix, path, applies, err := resolver.Resolve(ctx, current, opt); err != nil { return nil, "", "", err } else if applies { - opt.Debug("Module path is %s", path) + opt.Logger.Debug("Module resolved", log.FilePath(path)) return filesystem, prefix, path, nil } } diff --git a/pkg/iac/scanners/terraform/parser/option.go b/pkg/iac/scanners/terraform/parser/option.go index 76146a4ea6bf..277cf2a58c61 100644 --- a/pkg/iac/scanners/terraform/parser/option.go +++ b/pkg/iac/scanners/terraform/parser/option.go @@ -4,75 +4,48 @@ import ( "io/fs" "github.com/zclconf/go-cty/cty" - - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) -type ConfigurableTerraformParser interface { - options.ConfigurableParser - SetTFVarsPaths(...string) - SetTFVars(vars map[string]cty.Value) - SetStopOnHCLError(bool) - SetWorkspaceName(string) - SetAllowDownloads(bool) - SetSkipCachedModules(bool) - SetConfigsFS(fsys fs.FS) -} - -type Option func(p ConfigurableTerraformParser) +type Option func(p *Parser) -func OptionWithTFVarsPaths(paths ...string) options.ParserOption { - return func(p options.ConfigurableParser) { - if tf, ok := p.(ConfigurableTerraformParser); ok { - tf.SetTFVarsPaths(paths...) - } +func OptionWithTFVarsPaths(paths ...string) Option { + return func(p *Parser) { + p.tfvarsPaths = paths } } -func OptionsWithTfVars(vars map[string]cty.Value) options.ParserOption { - return func(p options.ConfigurableParser) { - if tf, ok := p.(ConfigurableTerraformParser); ok { - tf.SetTFVars(vars) - } +func OptionStopOnHCLError(stop bool) Option { + return func(p *Parser) { + p.stopOnHCLError = stop } } -func OptionStopOnHCLError(stop bool) options.ParserOption { - return func(p options.ConfigurableParser) { - if tf, ok := p.(ConfigurableTerraformParser); ok { - tf.SetStopOnHCLError(stop) - } +func OptionsWithTfVars(vars map[string]cty.Value) Option { + return func(p *Parser) { + p.tfvars = vars } } -func OptionWithWorkspaceName(workspaceName string) options.ParserOption { - return func(p options.ConfigurableParser) { - if tf, ok := p.(ConfigurableTerraformParser); ok { - tf.SetWorkspaceName(workspaceName) - } +func OptionWithWorkspaceName(workspaceName string) Option { + return func(p *Parser) { + p.workspaceName = workspaceName } } -func OptionWithDownloads(allowed bool) options.ParserOption { - return func(p options.ConfigurableParser) { - if tf, ok := p.(ConfigurableTerraformParser); ok { - tf.SetAllowDownloads(allowed) - } +func OptionWithDownloads(allowed bool) Option { + return func(p *Parser) { + p.allowDownloads = allowed } } -func OptionWithSkipCachedModules(b bool) options.ParserOption { - return func(p options.ConfigurableParser) { - if tf, ok := p.(ConfigurableTerraformParser); ok { - tf.SetSkipCachedModules(b) - } +func OptionWithSkipCachedModules(b bool) Option { + return func(p *Parser) { + p.skipCachedModules = b } } -func OptionWithConfigsFS(fsys fs.FS) options.ParserOption { - return func(s options.ConfigurableParser) { - if p, ok := s.(ConfigurableTerraformParser); ok { - p.SetConfigsFS(fsys) - } +func OptionWithConfigsFS(fsys fs.FS) Option { + return func(p *Parser) { + p.configsFS = fsys } } diff --git a/pkg/iac/scanners/terraform/parser/parser.go b/pkg/iac/scanners/terraform/parser/parser.go index a5b2b909a462..60940d6c6241 100644 --- a/pkg/iac/scanners/terraform/parser/parser.go +++ b/pkg/iac/scanners/terraform/parser/parser.go @@ -15,11 +15,10 @@ import ( "github.com/hashicorp/hcl/v2/hclparse" "github.com/zclconf/go-cty/cty" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/ignore" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/terraform" tfcontext "github.com/aquasecurity/trivy/pkg/iac/terraform/context" + "github.com/aquasecurity/trivy/pkg/log" ) type sourceFile struct { @@ -27,8 +26,6 @@ type sourceFile struct { path string } -var _ ConfigurableTerraformParser = (*Parser)(nil) - // Parser is a tool for parsing terraform templates at a given file system location type Parser struct { projectRoot string @@ -44,48 +41,16 @@ type Parser struct { workspaceName string underlying *hclparse.Parser children []*Parser - options []options.ParserOption - debug debug.Logger + options []Option + logger *log.Logger allowDownloads bool skipCachedModules bool fsMap map[string]fs.FS configsFS fs.FS } -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "terraform", "parser", "<"+p.moduleName+">") -} - -func (p *Parser) SetTFVarsPaths(s ...string) { - p.tfvarsPaths = s -} - -func (p *Parser) SetTFVars(vars map[string]cty.Value) { - p.tfvars = vars -} - -func (p *Parser) SetStopOnHCLError(b bool) { - p.stopOnHCLError = b -} - -func (p *Parser) SetWorkspaceName(s string) { - p.workspaceName = s -} - -func (p *Parser) SetAllowDownloads(b bool) { - p.allowDownloads = b -} - -func (p *Parser) SetSkipCachedModules(b bool) { - p.skipCachedModules = b -} - -func (p *Parser) SetConfigsFS(fsys fs.FS) { - p.configsFS = fsys -} - // New creates a new Parser -func New(moduleFS fs.FS, moduleSource string, opts ...options.ParserOption) *Parser { +func New(moduleFS fs.FS, moduleSource string, opts ...Option) *Parser { p := &Parser{ workspaceName: "default", underlying: hclparse.NewParser(), @@ -95,6 +60,7 @@ func New(moduleFS fs.FS, moduleSource string, opts ...options.ParserOption) *Par moduleFS: moduleFS, moduleSource: moduleSource, configsFS: moduleFS, + logger: log.WithPrefix("terraform parser").With("module", "root"), tfvars: make(map[string]cty.Value), } @@ -110,6 +76,7 @@ func (p *Parser) newModuleParser(moduleFS fs.FS, moduleSource, modulePath, modul mp.modulePath = modulePath mp.moduleBlock = moduleBlock mp.moduleName = moduleName + mp.logger = log.WithPrefix("terraform parser").With("module", moduleName) mp.projectRoot = p.projectRoot p.children = append(p.children, mp) for _, option := range p.options { @@ -126,7 +93,7 @@ func (p *Parser) ParseFile(_ context.Context, fullPath string) error { return nil } - p.debug.Log("Parsing '%s'...", fullPath) + p.logger.Debug("Parsing", log.FilePath(fullPath)) f, err := p.moduleFS.Open(filepath.ToSlash(fullPath)) if err != nil { return err @@ -139,7 +106,7 @@ func (p *Parser) ParseFile(_ context.Context, fullPath string) error { } if dir := path.Dir(fullPath); p.projectRoot == "" { - p.debug.Log("Setting project/module root to '%s'", dir) + p.logger.Debug("Setting project/module root", log.FilePath(dir)) p.projectRoot = dir p.modulePath = dir } @@ -160,7 +127,7 @@ func (p *Parser) ParseFile(_ context.Context, fullPath string) error { path: fullPath, }) - p.debug.Log("Added file %s.", fullPath) + p.logger.Debug("Added file", log.FilePath(fullPath)) return nil } @@ -170,13 +137,13 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) error { dir = path.Clean(dir) if p.projectRoot == "" { - p.debug.Log("Setting project/module root to '%s'", dir) + p.logger.Debug("Setting project/module root", log.FilePath(dir)) p.projectRoot = dir p.modulePath = dir } slashed := filepath.ToSlash(dir) - p.debug.Log("Parsing FS from '%s'", slashed) + p.logger.Debug("Parsing FS", log.FilePath(slashed)) fileInfos, err := fs.ReadDir(p.moduleFS, slashed) if err != nil { return err @@ -196,7 +163,7 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) error { if p.stopOnHCLError { return err } - p.debug.Log("error parsing '%s': %s", path, err) + p.logger.Error("Error parsing file", log.FilePath(path), log.Err(err)) continue } } @@ -207,10 +174,10 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) error { var ErrNoFiles = errors.New("no files found") func (p *Parser) Load(ctx context.Context) (*evaluator, error) { - p.debug.Log("Evaluating module...") + p.logger.Debug("Loading module", log.String("module", p.moduleName)) if len(p.files) == 0 { - p.debug.Log("No files found, nothing to do.") + p.logger.Info("No files found, nothing to do.") return nil, ErrNoFiles } @@ -218,31 +185,43 @@ func (p *Parser) Load(ctx context.Context) (*evaluator, error) { if err != nil { return nil, err } - p.debug.Log("Read %d block(s) and %d ignore(s) for module '%s' (%d file[s])...", len(blocks), len(ignores), p.moduleName, len(p.files)) + p.logger.Debug("Read block(s) and ignore(s)", + log.Int("blocks", len(blocks)), log.Int("ignores", len(ignores))) var inputVars map[string]cty.Value switch { case p.moduleBlock != nil: inputVars = p.moduleBlock.Values().AsValueMap() - p.debug.Log("Added %d input variables from module definition.", len(inputVars)) + p.logger.Debug("Added input variables from module definition", + log.Int("count", len(inputVars))) case len(p.tfvars) > 0: inputVars = p.tfvars - p.debug.Log("Added %d input variables from tfvars.", len(inputVars)) + p.logger.Debug("Added input variables from tfvars.", log.Int("count", len(inputVars))) default: inputVars, err = loadTFVars(p.configsFS, p.tfvarsPaths) if err != nil { return nil, err } - p.debug.Log("Added %d variables from tfvars.", len(inputVars)) + p.logger.Debug("Added input variables from tfvars", log.Int("count", len(inputVars))) + + if missingVars := missingVariableValues(blocks, inputVars); len(missingVars) > 0 { + p.logger.Warn( + "Variable values was not found in the environment or variable files. Evaluating may not work correctly.", + log.String("variables", strings.Join(missingVars, ", ")), + ) + } } modulesMetadata, metadataPath, err := loadModuleMetadata(p.moduleFS, p.projectRoot) if err != nil && !errors.Is(err, os.ErrNotExist) { - p.debug.Log("Error loading module metadata: %s.", err) + p.logger.Error("Error loading module metadata", log.Err(err)) } else if err == nil { - p.debug.Log("Loaded module metadata for %d module(s) from %q.", len(modulesMetadata.Modules), metadataPath) + p.logger.Debug("Loaded module metadata for modules", + log.FilePath(metadataPath), + log.Int("count", len(modulesMetadata.Modules)), + ) } workingDir, err := os.Getwd() @@ -250,7 +229,7 @@ func (p *Parser) Load(ctx context.Context) (*evaluator, error) { return nil, err } - p.debug.Log("Working directory for module evaluation is %q", workingDir) + p.logger.Debug("Working directory for module evaluation", log.FilePath(workingDir)) return newEvaluator( p.moduleFS, p, @@ -263,12 +242,25 @@ func (p *Parser) Load(ctx context.Context) (*evaluator, error) { modulesMetadata, p.workspaceName, ignores, - p.debug.Extend("evaluator"), + log.WithPrefix("terraform evaluator"), p.allowDownloads, p.skipCachedModules, ), nil } +func missingVariableValues(blocks terraform.Blocks, inputVars map[string]cty.Value) []string { + var missing []string + for _, varBlock := range blocks.OfType("variable") { + if varBlock.GetAttribute("default") == nil { + if _, ok := inputVars[varBlock.TypeLabel()]; !ok { + missing = append(missing, varBlock.TypeLabel()) + } + } + } + + return missing +} + func (p *Parser) EvaluateAll(ctx context.Context) (terraform.Modules, cty.Value, error) { e, err := p.Load(ctx) @@ -279,7 +271,7 @@ func (p *Parser) EvaluateAll(ctx context.Context) (terraform.Modules, cty.Value, } modules, fsMap := e.EvaluateAll(ctx) - p.debug.Log("Finished parsing module '%s'.", p.moduleName) + p.logger.Debug("Finished parsing module") p.fsMap = fsMap return modules, e.exportOutputs(), nil } @@ -301,7 +293,7 @@ func (p *Parser) readBlocks(files []sourceFile) (terraform.Blocks, ignore.Rules, if p.stopOnHCLError { return nil, nil, err } - p.debug.Log("Encountered HCL parse error: %s", err) + p.logger.Error("Encountered HCL parse error", log.FilePath(file.path), log.Err(err)) continue } for _, fileBlock := range fileBlocks { diff --git a/pkg/iac/scanners/terraform/parser/parser_test.go b/pkg/iac/scanners/terraform/parser/parser_test.go index df06dd2aa393..e3bd817748f6 100644 --- a/pkg/iac/scanners/terraform/parser/parser_test.go +++ b/pkg/iac/scanners/terraform/parser/parser_test.go @@ -1,7 +1,9 @@ package parser import ( + "bytes" "context" + "log/slog" "os" "path/filepath" "sort" @@ -13,8 +15,8 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/aquasecurity/trivy/internal/testutil" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/log" ) func Test_BasicParsing(t *testing.T) { @@ -172,7 +174,7 @@ output "mod_result" { `, }) - parser := New(fs, "", OptionStopOnHCLError(true), options.ParserWithDebug(os.Stderr)) + parser := New(fs, "", OptionStopOnHCLError(true)) require.NoError(t, parser.ParseFS(context.TODO(), "code")) modules, _, err := parser.EvaluateAll(context.TODO()) @@ -1847,3 +1849,38 @@ resource "something" "blah" { assert.True(t, r2.IsResolvable()) assert.Equal(t, "us-east-2", r2.Value().AsString()) } + +func TestLogAboutMissingVariableValues(t *testing.T) { + var buf bytes.Buffer + slog.SetDefault(slog.New(log.NewHandler(&buf, nil))) + + fsys := fstest.MapFS{ + "main.tf": &fstest.MapFile{ + Data: []byte(` +variable "foo" {} + +variable "bar" { + default = "bar" +} + +variable "baz" {} +`), + }, + "main.tfvars": &fstest.MapFile{ + Data: []byte(`baz = "baz"`), + }, + } + + parser := New( + fsys, "", + OptionStopOnHCLError(true), + OptionWithTFVarsPaths("main.tfvars"), + ) + require.NoError(t, parser.ParseFS(context.TODO(), ".")) + + _, err := parser.Load(context.TODO()) + require.NoError(t, err) + + assert.Contains(t, buf.String(), "Variable values was not found in the environment or variable files.") + assert.Contains(t, buf.String(), "variables=\"foo\"") +} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/cache.go b/pkg/iac/scanners/terraform/parser/resolvers/cache.go index c8b2f660ed33..24f803f60139 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/cache.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/cache.go @@ -8,6 +8,8 @@ import ( "io/fs" "os" "path/filepath" + + "github.com/aquasecurity/trivy/pkg/log" ) type cacheResolver struct{} @@ -42,21 +44,21 @@ func locateCacheDir(cacheDir string) (string, error) { func (r *cacheResolver) Resolve(_ context.Context, _ fs.FS, opt Options) (filesystem fs.FS, prefix, downloadPath string, applies bool, err error) { if opt.SkipCache { - opt.Debug("Cache is disabled.") + opt.Logger.Debug("Module caching is disabled") return nil, "", "", false, nil } cacheFS, err := locateCacheFS(opt.CacheDir) if err != nil { - opt.Debug("No cache filesystem is available on this machine.") + opt.Logger.Debug("No cache filesystem is available on this machine.", log.Err(err)) return nil, "", "", false, nil } src, subdir := splitPackageSubdirRaw(opt.Source) key := cacheKey(src, opt.Version) - opt.Debug("Trying to resolve: %s", key) + opt.Logger.Debug("Trying to resolve module via cache", log.String("key", key)) if info, err := fs.Stat(cacheFS, filepath.ToSlash(key)); err == nil && info.IsDir() { - opt.Debug("Module '%s' resolving via cache...", opt.Name) + opt.Logger.Debug("Module resolved from cache", log.String("key", key)) cacheDir, err := locateCacheDir(opt.CacheDir) if err != nil { return nil, "", "", true, err diff --git a/pkg/iac/scanners/terraform/parser/resolvers/local.go b/pkg/iac/scanners/terraform/parser/resolvers/local.go index eb053741c8ab..a11a294edcfb 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/local.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/local.go @@ -5,6 +5,8 @@ import ( "io/fs" "path" "path/filepath" + + "github.com/aquasecurity/trivy/pkg/log" ) type localResolver struct{} @@ -17,11 +19,15 @@ func (r *localResolver) Resolve(_ context.Context, target fs.FS, opt Options) (f } joined := path.Clean(path.Join(opt.ModulePath, opt.Source)) if _, err := fs.Stat(target, filepath.ToSlash(joined)); err == nil { - opt.Debug("Module '%s' resolved locally to %s", opt.Name, joined) + opt.Logger.Debug("Module resolved locally", + log.String("name", opt.Name), log.FilePath(joined), + ) return target, "", joined, true, nil } clean := path.Clean(opt.Source) - opt.Debug("Module '%s' resolved locally to %s", opt.Name, clean) + opt.Logger.Debug("Module resolved locally", + log.String("name", opt.Name), log.FilePath(clean), + ) return target, "", clean, true, nil } diff --git a/pkg/iac/scanners/terraform/parser/resolvers/options.go b/pkg/iac/scanners/terraform/parser/resolvers/options.go index cdfde6b01bcc..73fd39689e84 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/options.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/options.go @@ -3,12 +3,12 @@ package resolvers import ( "strings" - "github.com/aquasecurity/trivy/pkg/iac/debug" + "github.com/aquasecurity/trivy/pkg/log" ) type Options struct { Source, OriginalSource, Version, OriginalVersion, WorkingDir, Name, ModulePath string - DebugLogger debug.Logger + Logger *log.Logger AllowDownloads bool SkipCache bool RelativePath string @@ -23,7 +23,3 @@ func (o *Options) hasPrefix(prefixes ...string) bool { } return false } - -func (o *Options) Debug(format string, args ...any) { - o.DebugLogger.Log(format, args...) -} diff --git a/pkg/iac/scanners/terraform/parser/resolvers/registry.go b/pkg/iac/scanners/terraform/parser/resolvers/registry.go index 8f2ab2ecdde1..471416463cad 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/registry.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/registry.go @@ -14,6 +14,7 @@ import ( "golang.org/x/net/idna" "github.com/aquasecurity/go-version/pkg/version" + "github.com/aquasecurity/trivy/pkg/log" ) type registryResolver struct { @@ -59,9 +60,11 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option token, err = getPrivateRegistryTokenFromEnvVars(hostname) if err == nil { - opt.Debug("Found a token for the registry at %s", hostname) + opt.Logger.Debug("Found a token for the registry", log.String("hostname", hostname)) } else { - opt.Debug(err.Error()) + opt.Logger.Error( + "Failed to find a token for the registry", + log.String("hostname", hostname), log.Err(err)) } } @@ -69,7 +72,8 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option if opt.Version != "" { versionUrl := fmt.Sprintf("https://%s/v1/modules/%s/versions", hostname, moduleName) - opt.Debug("Requesting module versions from registry using '%s'...", versionUrl) + opt.Logger.Debug("Requesting module versions from registry using", + log.String("url", versionUrl)) req, err := http.NewRequestWithContext(ctx, http.MethodGet, versionUrl, nil) if err != nil { return nil, "", "", true, err @@ -94,7 +98,8 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option if err != nil { return nil, "", "", true, err } - opt.Debug("Found version '%s' for constraint '%s'", opt.Version, inputVersion) + opt.Logger.Debug("Found module version", + log.String("version", opt.Version), log.String("constraint", inputVersion)) } var url string @@ -104,7 +109,7 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option url = fmt.Sprintf("https://%s/v1/modules/%s/%s/download", hostname, moduleName, opt.Version) } - opt.Debug("Requesting module source from registry using '%s'...", url) + opt.Logger.Debug("Requesting module source from registry", log.String("url", url)) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { @@ -145,7 +150,8 @@ func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Option return nil, "", "", true, fmt.Errorf("no source was found for the registry at %s", hostname) } - opt.Debug("Module '%s' resolved via registry to new source: '%s'", opt.Name, opt.Source) + opt.Logger.Debug("Module resolved via registry to new source", + log.String("source", opt.Source), log.String("name", moduleName)) filesystem, prefix, downloadPath, _, err = Remote.Resolve(ctx, target, opt) if err != nil { diff --git a/pkg/iac/scanners/terraform/parser/resolvers/remote.go b/pkg/iac/scanners/terraform/parser/resolvers/remote.go index 790b3509fd33..467f2cee6970 100644 --- a/pkg/iac/scanners/terraform/parser/resolvers/remote.go +++ b/pkg/iac/scanners/terraform/parser/resolvers/remote.go @@ -9,6 +9,8 @@ import ( "sync/atomic" "github.com/hashicorp/go-getter" + + "github.com/aquasecurity/trivy/pkg/log" ) type remoteResolver struct { @@ -20,9 +22,9 @@ var Remote = &remoteResolver{ } func (r *remoteResolver) incrementCount(o Options) { - o.Debug("Incrementing the download counter") + atomic.CompareAndSwapInt32(&r.count, r.count, r.count+1) - o.Debug("Download counter is now %d", r.count) + o.Logger.Debug("Incrementing the download counter", log.Int("count", int(r.count))) } func (r *remoteResolver) GetDownloadCount() int { @@ -40,7 +42,7 @@ func (r *remoteResolver) Resolve(ctx context.Context, _ fs.FS, opt Options) (fil src, subdir := splitPackageSubdirRaw(opt.OriginalSource) key := cacheKey(src, opt.OriginalVersion) - opt.Debug("Storing with cache key %s", key) + opt.Logger.Debug("Caching module", log.String("key", key)) baseCacheDir, err := locateCacheDir(opt.CacheDir) if err != nil { @@ -52,8 +54,10 @@ func (r *remoteResolver) Resolve(ctx context.Context, _ fs.FS, opt Options) (fil } r.incrementCount(opt) - opt.Debug("Successfully downloaded %s from %s", opt.Name, opt.Source) - opt.Debug("Module '%s' resolved via remote download.", opt.Name) + opt.Logger.Debug("Successfully resolve module via remote download", + log.String("name", opt.Name), + log.String("source", opt.Source), + ) return os.DirFS(cacheDir), opt.Source, subdir, true, nil } @@ -68,7 +72,7 @@ func (r *remoteResolver) download(ctx context.Context, opt Options, dst string) // Overwrite the file getter so that a file will be copied getter.Getters["file"] = &getter.FileGetter{Copy: true} - opt.Debug("Downloading %s...", opt.Source) + opt.Logger.Debug("Downloading module", log.String("source", opt.Source)) // Build the client client := &getter.Client{ diff --git a/pkg/iac/scanners/terraform/scanner.go b/pkg/iac/scanners/terraform/scanner.go index 5e7cea83abfd..080d26c0d155 100644 --- a/pkg/iac/scanners/terraform/scanner.go +++ b/pkg/iac/scanners/terraform/scanner.go @@ -11,7 +11,6 @@ import ( "strings" "sync" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" @@ -21,6 +20,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser" "github.com/aquasecurity/trivy/pkg/iac/terraform" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ scanners.FSScanner = (*Scanner)(nil) @@ -28,17 +28,18 @@ var _ options.ConfigurableScanner = (*Scanner)(nil) var _ ConfigurableTerraformScanner = (*Scanner)(nil) type Scanner struct { - mu sync.Mutex - options []options.ScannerOption - parserOpt []options.ParserOption - executorOpt []executor.Option - dirs map[string]struct{} - forceAllDirs bool - policyDirs []string - policyReaders []io.Reader - regoScanner *rego.Scanner - execLock sync.RWMutex - debug debug.Logger + mu sync.Mutex + logger *log.Logger + options []options.ScannerOption + parserOpt []parser.Option + executorOpt []executor.Option + dirs map[string]struct{} + forceAllDirs bool + policyDirs []string + policyReaders []io.Reader + regoScanner *rego.Scanner + execLock sync.RWMutex + frameworks []framework.Framework spec string loadEmbeddedLibraries bool @@ -76,7 +77,7 @@ func (s *Scanner) SetForceAllDirs(b bool) { s.forceAllDirs = b } -func (s *Scanner) AddParserOptions(opts ...options.ParserOption) { +func (s *Scanner) AddParserOptions(opts ...parser.Option) { s.parserOpt = append(s.parserOpt, opts...) } @@ -88,12 +89,6 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.parserOpt = append(s.parserOpt, options.ParserWithDebug(writer)) - s.executorOpt = append(s.executorOpt, executor.OptionWithDebugWriter(writer)) - s.debug = debug.New(writer, "terraform", "scanner") -} - func (s *Scanner) SetTraceWriter(_ io.Writer) { } @@ -120,6 +115,7 @@ func New(opts ...options.ScannerOption) *Scanner { s := &Scanner{ dirs: make(map[string]struct{}), options: opts, + logger: log.WithPrefix("terraform scanner"), } for _, opt := range opts { opt(s) @@ -134,8 +130,6 @@ func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceCloud, s.options...) - regoScanner.SetParentDebugLogger(s.debug) - if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { return nil, err } @@ -152,14 +146,14 @@ type terraformRootModule struct { func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, dir string) (scan.Results, error) { - s.debug.Log("Scanning [%s] at '%s'...", target, dir) + s.logger.Debug("Scanning directory", log.FilePath(dir)) // find directories which directly contain tf files modulePaths := s.findModules(target, dir, dir) sort.Strings(modulePaths) if len(modulePaths) == 0 { - s.debug.Log("no modules found") + s.logger.Info("No modules found, skipping directory", log.FilePath(dir)) return nil, nil } @@ -185,7 +179,7 @@ func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, dir string) (scan.Re // parse all root module directories for _, dir := range rootDirs { - s.debug.Log("Scanning root module '%s'...", dir) + s.logger.Info("Scanning root module", log.FilePath(dir)) p := parser.New(target, "", s.parserOpt...) @@ -295,7 +289,7 @@ func (s *Scanner) findModules(target fs.FS, scanDir string, dirs ...string) []st func (s *Scanner) isRootModule(target fs.FS, dir string) bool { files, err := fs.ReadDir(target, filepath.ToSlash(dir)) if err != nil { - s.debug.Log("failed to read dir '%s' from filesystem [%s]: %s", dir, target, err) + s.logger.Error("Failed to read dir", log.FilePath(dir), log.Err(err)) return false } for _, file := range files { diff --git a/pkg/iac/scanners/terraform/scanner_integration_test.go b/pkg/iac/scanners/terraform/scanner_integration_test.go index 6c60dd8a9f2c..098d6a91d450 100644 --- a/pkg/iac/scanners/terraform/scanner_integration_test.go +++ b/pkg/iac/scanners/terraform/scanner_integration_test.go @@ -1,9 +1,7 @@ package terraform import ( - "bytes" "context" - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -45,10 +43,7 @@ deny[res] { }`, }) - debugLog := bytes.NewBuffer([]byte{}) - scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyFilesystem(fs), options.ScannerWithPolicyDirs("rules"), options.ScannerWithEmbeddedPolicies(false), @@ -62,10 +57,6 @@ deny[res] { require.NoError(t, err) assert.Len(t, results.GetPassed(), 1) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_ScanChildUseRemoteModule(t *testing.T) { @@ -109,10 +100,7 @@ deny[res] { }`, }) - debugLog := bytes.NewBuffer([]byte{}) - scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyFilesystem(fs), options.ScannerWithPolicyDirs("rules"), options.ScannerWithEmbeddedPolicies(false), @@ -126,10 +114,6 @@ deny[res] { require.NoError(t, err) assert.Len(t, results.GetPassed(), 1) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_OptionWithSkipDownloaded(t *testing.T) { diff --git a/pkg/iac/scanners/terraform/scanner_test.go b/pkg/iac/scanners/terraform/scanner_test.go index 20e839ff91ab..5c85995027f4 100644 --- a/pkg/iac/scanners/terraform/scanner_test.go +++ b/pkg/iac/scanners/terraform/scanner_test.go @@ -1,7 +1,6 @@ package terraform import ( - "bytes" "context" "fmt" "strconv" @@ -11,31 +10,10 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/internal/testutil" - "github.com/aquasecurity/trivy/pkg/iac/providers" - "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" - "github.com/aquasecurity/trivy/pkg/iac/severity" - "github.com/aquasecurity/trivy/pkg/iac/terraform" ) -var alwaysFailRule = scan.Rule{ - Provider: providers.AWSProvider, - Service: "service", - ShortCode: "abc", - Severity: severity.High, - CustomChecks: scan.CustomChecks{ - Terraform: &scan.TerraformCustomCheck{ - RequiredTypes: []string{}, - RequiredLabels: []string{}, - Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { - results.Add("oh no", resourceBlock) - return - }, - }, - }, -} - const emptyBucketRule = ` # METADATA # schemas: @@ -56,33 +34,6 @@ deny[res] { } ` -func scanWithOptions(t *testing.T, code string, opt ...options.ScannerOption) scan.Results { - - fs := testutil.CreateFS(t, map[string]string{ - "project/main.tf": code, - }) - - scanner := New(opt...) - results, err := scanner.ScanFS(context.TODO(), fs, "project") - require.NoError(t, err) - return results -} - -func Test_OptionWithDebugWriter(t *testing.T) { - reg := rules.Register(alwaysFailRule) - defer rules.Deregister(reg) - - buffer := bytes.NewBuffer([]byte{}) - - scannerOpts := []options.ScannerOption{ - options.ScannerWithDebug(buffer), - } - _ = scanWithOptions(t, ` -resource "something" "else" {} -`, scannerOpts...) - require.Positive(t, buffer.Len()) -} - func Test_OptionWithPolicyDirs(t *testing.T) { fs := testutil.CreateFS(t, map[string]string{ @@ -119,9 +70,7 @@ deny[cause] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyFilesystem(fs), options.ScannerWithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), @@ -168,10 +117,6 @@ deny[cause] { }, }, actualCode.Lines) - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } - } func Test_OptionWithPolicyNamespaces(t *testing.T) { @@ -278,7 +223,6 @@ cause := bucket.name } } assert.Equal(t, test.wantFailure, found) - }) } @@ -320,9 +264,7 @@ deny[cause] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), ) @@ -332,10 +274,6 @@ deny[cause] { require.Len(t, results.GetFailed(), 1) assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_OptionWithRegoOnly_CodeHighlighting(t *testing.T) { @@ -374,9 +312,7 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), options.ScannerWithEmbeddedLibraries(true), @@ -388,10 +324,6 @@ deny[res] { require.Len(t, results.GetFailed(), 1) assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) assert.NotNil(t, results[0].Metadata().Range().GetFS()) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_IAMPolicyRego(t *testing.T) { @@ -448,21 +380,12 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), - options.ScannerWithTrace(debugLog), options.ScannerWithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), options.ScannerWithEmbeddedLibraries(true), ) - defer func() { - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } - }() - results, err := scanner.ScanFS(context.TODO(), fs, "code") require.NoError(t, err) @@ -541,9 +464,7 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), options.ScannerWithEmbeddedLibraries(true), @@ -556,9 +477,6 @@ deny[res] { assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) assert.NotNil(t, results[0].Metadata().Range().GetFS()) - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_S3_Linking(t *testing.T) { @@ -603,10 +521,7 @@ resource "aws_s3_bucket_public_access_block" "foo" { "code/main.tf": code, }) - debugLog := bytes.NewBuffer([]byte{}) - scanner := New( - options.ScannerWithDebug(debugLog), - ) + scanner := New() results, err := scanner.ScanFS(context.TODO(), fs, "code") require.NoError(t, err) @@ -622,10 +537,6 @@ resource "aws_s3_bucket_public_access_block" "foo" { // versioning assert.NotEqual(t, "AVD-AWS-0090", result.Rule().AVDID) } - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_S3_Linking_PublicAccess(t *testing.T) { @@ -673,10 +584,7 @@ resource "aws_s3_bucket_public_access_block" "testB" { "code/main.tf": code, }) - debugLog := bytes.NewBuffer([]byte{}) - scanner := New( - options.ScannerWithDebug(debugLog), - ) + scanner := New() results, err := scanner.ScanFS(context.TODO(), fs, "code") require.NoError(t, err) @@ -686,9 +594,6 @@ resource "aws_s3_bucket_public_access_block" "testB" { assert.NotEqual(t, "AVD-AWS-0094", result.Rule().AVDID) } - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } // PoC for replacing Go with Rego: AVD-AWS-0001 @@ -732,9 +637,7 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyFilesystem(fs), options.ScannerWithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), @@ -788,11 +691,6 @@ deny[res] { Annotation: "", }, }, actualCode.Lines) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } - } func Test_OptionWithConfigsFileSystem(t *testing.T) { @@ -814,9 +712,7 @@ bucket_name = "test" `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyDirs("rules"), options.ScannerWithPolicyFilesystem(fs), options.ScannerWithRegoOnly(true), @@ -832,10 +728,6 @@ bucket_name = "test" assert.Len(t, results, 1) assert.Len(t, results.GetPassed(), 1) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_OptionWithConfigsFileSystem_ConfigInCode(t *testing.T) { @@ -854,9 +746,7 @@ bucket_name = "test" `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyDirs("rules"), options.ScannerWithPolicyFilesystem(fs), options.ScannerWithRegoOnly(true), @@ -872,10 +762,6 @@ bucket_name = "test" assert.Len(t, results, 1) assert.Len(t, results.GetPassed(), 1) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_DoNotScanNonRootModules(t *testing.T) { @@ -953,9 +839,7 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyFilesystem(fs), options.ScannerWithPolicyDirs("rules"), options.ScannerWithEmbeddedPolicies(false), @@ -970,10 +854,6 @@ deny[res] { assert.Len(t, results.GetPassed(), 2) require.Len(t, results.GetFailed(), 1) assert.Equal(t, "AVD-AWS-0002", results.GetFailed()[0].Rule().AVDID) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func Test_RoleRefToOutput(t *testing.T) { @@ -1029,9 +909,7 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyDirs("rules"), options.ScannerWithPolicyFilesystem(fs), options.ScannerWithRegoOnly(true), @@ -1100,9 +978,7 @@ deny[res] { }`, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyDirs("rules"), options.ScannerWithPolicyFilesystem(fs), options.ScannerWithRegoOnly(true), @@ -1121,10 +997,6 @@ deny[res] { require.Len(t, results.GetPassed(), 1) assert.Equal(t, "AVD-AWS-0002", results.GetPassed()[0].Rule().AVDID) - - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } } func TestScanModuleWithCount(t *testing.T) { @@ -1170,9 +1042,7 @@ deny[res] { `, }) - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyDirs("rules"), options.ScannerWithPolicyFilesystem(fs), options.ScannerWithRegoOnly(true), diff --git a/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go b/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go index 6e7299e19dd1..c7ad3a3b4e0b 100644 --- a/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/snapshot/scanner_test.go @@ -1,7 +1,6 @@ package snapshot import ( - "bytes" "context" "os" "path" @@ -108,9 +107,7 @@ func Test_ScanFS(t *testing.T) { t.Run(tc.dir, func(t *testing.T) { fs := os.DirFS("testdata") - debugLog := bytes.NewBuffer([]byte{}) scanner := New( - options.ScannerWithDebug(debugLog), options.ScannerWithPolicyDirs(path.Join(tc.dir, "checks")), options.ScannerWithPolicyFilesystem(fs), options.ScannerWithRegoOnly(true), diff --git a/pkg/iac/scanners/terraformplan/tfjson/parser/option.go b/pkg/iac/scanners/terraformplan/tfjson/parser/option.go deleted file mode 100644 index 09e09b97bed5..000000000000 --- a/pkg/iac/scanners/terraformplan/tfjson/parser/option.go +++ /dev/null @@ -1,17 +0,0 @@ -package parser - -import "io" - -type Option func(p *Parser) - -func OptionWithDebugWriter(w io.Writer) Option { - return func(p *Parser) { - p.debugWriter = w - } -} - -func OptionStopOnHCLError(stop bool) Option { - return func(p *Parser) { - p.stopOnHCLError = stop - } -} diff --git a/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go b/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go index 85d565bd307f..a55cfa99384f 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go +++ b/pkg/iac/scanners/terraformplan/tfjson/parser/parser.go @@ -11,28 +11,17 @@ import ( "github.com/liamg/memoryfs" "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/aquasecurity/trivy/pkg/log" ) type Parser struct { - debugWriter io.Writer - stopOnHCLError bool + logger *log.Logger } -func New(options ...Option) *Parser { - parser := &Parser{} - - for _, o := range options { - o(parser) +func New() *Parser { + return &Parser{ + logger: log.WithPrefix("tfjson parser"), } - return parser -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debugWriter = writer -} - -func (p *Parser) SetStopOnHCLError(b bool) { - p.stopOnHCLError = b } func (p *Parser) ParseFile(filepath string) (*PlanFile, error) { diff --git a/pkg/iac/scanners/terraformplan/tfjson/scanner.go b/pkg/iac/scanners/terraformplan/tfjson/scanner.go index b390d4d10213..875644d1dd2a 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/scanner.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner.go @@ -8,13 +8,13 @@ import ( "github.com/bmatcuk/doublestar/v4" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" terraformScanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/executor" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson/parser" + "github.com/aquasecurity/trivy/pkg/log" ) var tfPlanExts = []string{ @@ -23,10 +23,8 @@ var tfPlanExts = []string{ } type Scanner struct { - parser *parser.Parser - parserOpt []parser.Option - debug debug.Logger - + parser *parser.Parser + logger *log.Logger options []options.ScannerOption spec string executorOpt []executor.Option @@ -69,12 +67,6 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.parserOpt = append(s.parserOpt, parser.OptionWithDebugWriter(writer)) - s.executorOpt = append(s.executorOpt, executor.OptionWithDebugWriter(writer)) - s.debug = debug.New(writer, "tfplan", "scanner") -} - func (s *Scanner) SetTraceWriter(_ io.Writer) { } @@ -126,17 +118,19 @@ func (s *Scanner) ScanFS(ctx context.Context, inputFS fs.FS, dir string) (scan.R func New(opts ...options.ScannerOption) *Scanner { scanner := &Scanner{ options: opts, + logger: log.WithPrefix("tfjson scanner"), + parser: parser.New(), } for _, o := range opts { o(scanner) } - scanner.parser = parser.New(scanner.parserOpt...) + return scanner } func (s *Scanner) ScanFile(filepath string, fsys fs.FS) (scan.Results, error) { - s.debug.Log("Scanning file %s", filepath) + s.logger.Debug("Scanning file", log.FilePath(filepath)) file, err := fsys.Open(filepath) if err != nil { return nil, err diff --git a/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go b/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go index 8a878e206bb3..678b32e00f48 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go @@ -1,9 +1,7 @@ package tfjson import ( - "bytes" "context" - "fmt" "os" "testing" @@ -130,8 +128,7 @@ deny[cause] { "/rules/test.rego": tc.inputRego, }) - debugLog := bytes.NewBuffer([]byte{}) - so := append(tc.options, options.ScannerWithDebug(debugLog), options.ScannerWithPolicyFilesystem(fs)) + so := append(tc.options, options.ScannerWithPolicyFilesystem(fs)) scanner := New(so...) results, err := scanner.ScanFS(context.TODO(), fs, "code") @@ -142,9 +139,6 @@ deny[cause] { failure := results.GetFailed()[0] assert.Equal(t, "AVD-TEST-0123", failure.Rule().AVDID) - if t.Failed() { - fmt.Printf("Debug logs:\n%s\n", debugLog.String()) - } }) } } diff --git a/pkg/iac/scanners/toml/parser/parser.go b/pkg/iac/scanners/toml/parser/parser.go index 6beed83b1908..95444389d74d 100644 --- a/pkg/iac/scanners/toml/parser/parser.go +++ b/pkg/iac/scanners/toml/parser/parser.go @@ -2,33 +2,23 @@ package parser import ( "context" - "io" "io/fs" "path/filepath" "github.com/BurntSushi/toml" - "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ options.ConfigurableParser = (*Parser)(nil) - type Parser struct { - debug debug.Logger -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "toml", "parser") + logger *log.Logger } // New creates a new parser -func New(opts ...options.ParserOption) *Parser { - p := &Parser{} - for _, opt := range opts { - opt(p) +func New() *Parser { + return &Parser{ + logger: log.WithPrefix("toml parser"), } - return p } func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string]any, error) { @@ -49,7 +39,7 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st df, err := p.ParseFile(ctx, target, path) if err != nil { - p.debug.Log("Parse error in '%s': %s", path, err) + p.logger.Error("Parse error", log.FilePath(path), log.Err(err)) return nil } files[path] = df diff --git a/pkg/iac/scanners/toml/scanner.go b/pkg/iac/scanners/toml/scanner.go index b7dc3510da8f..235cc65dada5 100644 --- a/pkg/iac/scanners/toml/scanner.go +++ b/pkg/iac/scanners/toml/scanner.go @@ -6,22 +6,21 @@ import ( "io/fs" "sync" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/scanners/toml/parser" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { mu sync.Mutex - debug debug.Logger + logger *log.Logger options []options.ScannerOption - parserOptions []options.ParserOption policyDirs []string policyReaders []io.Reader parser *parser.Parser @@ -61,11 +60,6 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "toml", "scanner") - s.parserOptions = append(s.parserOptions, options.ParserWithDebug(writer)) -} - func (s *Scanner) SetTraceWriter(_ io.Writer) {} func (s *Scanner) SetPerResultTracingEnabled(_ bool) {} @@ -88,11 +82,12 @@ func (s *Scanner) SetRegoErrorLimit(_ int) {} func NewScanner(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("toml scanner"), } for _, opt := range opts { opt(s) } - s.parser = parser.New(s.parserOptions...) + s.parser = parser.New() return s } @@ -128,7 +123,7 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R if err != nil { return nil, err } - s.debug.Log("Scanning %s...", path) + s.logger.Debug("Scanning", log.FilePath(path)) return s.scanRego(ctx, fsys, rego.Input{ Path: path, Contents: parsed, @@ -142,7 +137,6 @@ func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceTOML, s.options...) - regoScanner.SetParentDebugLogger(s.debug) if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { return nil, err } diff --git a/pkg/iac/scanners/yaml/parser/parser.go b/pkg/iac/scanners/yaml/parser/parser.go index febcfb100008..9b1f026ae248 100644 --- a/pkg/iac/scanners/yaml/parser/parser.go +++ b/pkg/iac/scanners/yaml/parser/parser.go @@ -9,33 +9,25 @@ import ( "gopkg.in/yaml.v3" - "github.com/aquasecurity/trivy/pkg/iac/debug" - "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/log" ) -var _ options.ConfigurableParser = (*Parser)(nil) - type Parser struct { - debug debug.Logger -} - -func (p *Parser) SetDebugWriter(writer io.Writer) { - p.debug = debug.New(writer, "yaml", "parser") + logger *log.Logger } -// New creates a new parser -func New(opts ...options.ParserOption) *Parser { - p := &Parser{} - for _, opt := range opts { - opt(p) +// New creates a new YAML parser +func New() *Parser { + return &Parser{ + logger: log.WithPrefix("yaml parser"), } - return p } func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string][]any, error) { files := make(map[string][]any) if err := fs.WalkDir(target, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error { + select { case <-ctx.Done(): return ctx.Err() @@ -50,7 +42,7 @@ func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[st df, err := p.ParseFile(ctx, target, path) if err != nil { - p.debug.Log("Parse error in '%s': %s", path, err) + p.logger.Error("Parse error", log.FilePath(path), log.Err(err)) return nil } files[path] = df diff --git a/pkg/iac/scanners/yaml/scanner.go b/pkg/iac/scanners/yaml/scanner.go index 3ec508fdd6f5..67b661ee5971 100644 --- a/pkg/iac/scanners/yaml/scanner.go +++ b/pkg/iac/scanners/yaml/scanner.go @@ -6,13 +6,13 @@ import ( "io/fs" "sync" - "github.com/aquasecurity/trivy/pkg/iac/debug" "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/scanners/yaml/parser" "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" ) var _ options.ConfigurableScanner = (*Scanner)(nil) @@ -20,8 +20,7 @@ var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { mu sync.Mutex options []options.ScannerOption - parserOptions []options.ParserOption - debug debug.Logger + logger *log.Logger policyDirs []string policyReaders []io.Reader parser *parser.Parser @@ -61,11 +60,6 @@ func (s *Scanner) SetPolicyReaders(readers []io.Reader) { s.policyReaders = readers } -func (s *Scanner) SetDebugWriter(writer io.Writer) { - s.debug = debug.New(writer, "yaml", "scanner") - s.parserOptions = append(s.parserOptions, options.ParserWithDebug(writer)) -} - func (s *Scanner) SetTraceWriter(_ io.Writer) {} func (s *Scanner) SetPerResultTracingEnabled(_ bool) {} @@ -87,11 +81,12 @@ func (s *Scanner) SetRegoErrorLimit(_ int) {} func NewScanner(opts ...options.ScannerOption) *Scanner { s := &Scanner{ options: opts, + logger: log.WithPrefix("yaml scanner"), + parser: parser.New(), } for _, opt := range opts { opt(s) } - s.parser = parser.New(s.parserOptions...) return s } @@ -129,7 +124,7 @@ func (s *Scanner) ScanFile(ctx context.Context, fsys fs.FS, path string) (scan.R if err != nil { return nil, err } - s.debug.Log("Scanning %s...", path) + s.logger.Debug("Scanning", log.String("path", path)) return s.scanRego(ctx, fsys, rego.Input{ Path: path, Contents: parsed, @@ -143,7 +138,6 @@ func (s *Scanner) initRegoScanner(srcFS fs.FS) (*rego.Scanner, error) { return s.regoScanner, nil } regoScanner := rego.NewScanner(types.SourceYAML, s.options...) - regoScanner.SetParentDebugLogger(s.debug) if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, srcFS, s.policyDirs, s.policyReaders); err != nil { return nil, err } diff --git a/pkg/log/logger.go b/pkg/log/logger.go index 6eb8bde70dc2..73c4231fd4af 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -6,7 +6,6 @@ import ( "io" "log/slog" "os" - "strings" "github.com/samber/lo" ) @@ -83,18 +82,3 @@ func Fatal(msg string, args ...any) { slog.Default().Log(context.Background(), LevelFatal, msg, args...) os.Exit(1) } - -// WriteLogger is a wrapper around Logger to implement io.Writer -type WriteLogger struct { - logger *Logger -} - -// NewWriteLogger creates a new WriteLogger -func NewWriteLogger(logger *Logger) *WriteLogger { - return &WriteLogger{logger: logger} -} - -func (l *WriteLogger) Write(p []byte) (n int, err error) { - l.logger.Debug(strings.TrimSpace(string(p))) - return len(p), nil -} diff --git a/pkg/misconf/scanner.go b/pkg/misconf/scanner.go index 5737b3b8d3bc..2e979caba5e9 100644 --- a/pkg/misconf/scanner.go +++ b/pkg/misconf/scanner.go @@ -51,7 +51,6 @@ var enablediacTypes = map[detection.FileType]types.ConfigType{ } type ScannerOption struct { - Debug bool Trace bool RegoOnly bool Namespaces []string @@ -237,10 +236,6 @@ func scannerOptions(t detection.FileType, opt ScannerOption) ([]options.ScannerO options.ScannerWithCustomSchemas(schemas), ) - if opt.Debug { - opts = append(opts, options.ScannerWithDebug(log.NewWriteLogger(log.WithPrefix("misconf")))) - } - if opt.Trace { opts = append(opts, options.ScannerWithPerResultTracing(true)) } From 9ef05fc6b171a264516a025b0b0bcbbc8cff10bc Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Fri, 23 Aug 2024 13:43:10 +0600 Subject: [PATCH 300/352] feat(misconf): ignore duplicate checks (#7317) Signed-off-by: nikpivkin --- pkg/iac/rego/embed.go | 28 ++++++++++++++++++---- pkg/iac/rego/embed_test.go | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/pkg/iac/rego/embed.go b/pkg/iac/rego/embed.go index 6f542d9a0b2b..e582508a396b 100644 --- a/pkg/iac/rego/embed.go +++ b/pkg/iac/rego/embed.go @@ -2,6 +2,7 @@ package rego import ( "context" + "fmt" "io/fs" "path/filepath" "strings" @@ -10,6 +11,7 @@ import ( checks "github.com/aquasecurity/trivy-checks" "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/log" ) func init() { @@ -47,17 +49,34 @@ func RegisterRegoRules(modules map[string]*ast.Module) { } retriever := NewMetadataRetriever(compiler) + regoCheckIDs := make(map[string]struct{}) + for _, module := range modules { metadata, err := retriever.RetrieveMetadata(ctx, module) if err != nil { + log.Warn("Failed to retrieve metadata", log.String("avdid", metadata.AVDID), log.Err(err)) continue } + if metadata.AVDID == "" { + log.Warn("Check ID is empty", log.FilePath(module.Package.Location.File)) continue } - rules.Register( - metadata.ToRule(), - ) + + if !metadata.Deprecated { + regoCheckIDs[metadata.AVDID] = struct{}{} + } + + rules.Register(metadata.ToRule()) + } + + for _, check := range rules.GetRegistered() { + if !check.Deprecated && check.CanCheck() { + if _, exists := regoCheckIDs[check.AVDID]; exists { + log.Warn("Ignore duplicate Go check", log.String("avdid", check.AVDID)) + rules.Deregister(check) + } + } } } @@ -95,8 +114,7 @@ func LoadPoliciesFromDirs(target fs.FS, paths ...string) (map[string]*ast.Module ProcessAnnotation: true, }) if err != nil { - // s.debug.Log("Failed to load module: %s, err: %s", filepath.ToSlash(path), err.Error()) - return err + return fmt.Errorf("failed to parse Rego module: %w", err) } modules[path] = module return nil diff --git a/pkg/iac/rego/embed_test.go b/pkg/iac/rego/embed_test.go index 15001119a934..5b6368dec2eb 100644 --- a/pkg/iac/rego/embed_test.go +++ b/pkg/iac/rego/embed_test.go @@ -2,6 +2,7 @@ package rego import ( "testing" + "testing/fstest" "github.com/open-policy-agent/opa/ast" "github.com/stretchr/testify/assert" @@ -10,6 +11,7 @@ import ( checks "github.com/aquasecurity/trivy-checks" "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scan" + "github.com/aquasecurity/trivy/pkg/iac/state" ) func Test_EmbeddedLoading(t *testing.T) { @@ -204,3 +206,49 @@ deny[res]{ }) } } + +func Test_IgnoreDuplicateChecks(t *testing.T) { + rules.Reset() + + r := scan.Rule{ + AVDID: "TEST001", + Check: func(s *state.State) (results scan.Results) { + for _, bucket := range s.AWS.S3.Buckets { + if bucket.Name.Value() == "evil" { + results.Add("Bucket name should not be evil", bucket.Name) + } + } + return + }, + } + reg := rules.Register(r) + defer rules.Deregister(reg) + + fsys := fstest.MapFS{ + "test.rego": &fstest.MapFile{ + Data: []byte(` +# METADATA +# title: "Test rego" +# scope: package +# schemas: +# - input: schema["cloud"] +# custom: +# avd_id: TEST001 +# severity: LOW +package user.test001 + +deny[res] { + res := result.new("test", {}) +} +`), + }, + } + + modules, err := LoadPoliciesFromDirs(fsys, ".") + require.NoError(t, err) + + RegisterRegoRules(modules) + registered := rules.GetRegistered() + assert.Len(t, registered, 1) + assert.Equal(t, "TEST001", registered[0].AVDID) +} From b65b32ddfa6fc62ac81ad9fa580e1f5a327864f5 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Sat, 24 Aug 2024 01:59:30 +0600 Subject: [PATCH 301/352] fix(misconf): init frameworks before updating them (#7376) Signed-off-by: nikpivkin --- pkg/iac/rego/metadata.go | 3 +++ pkg/iac/rego/metadata_test.go | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/pkg/iac/rego/metadata.go b/pkg/iac/rego/metadata.go index 907b8450bdcb..57602ce97b8c 100644 --- a/pkg/iac/rego/metadata.go +++ b/pkg/iac/rego/metadata.go @@ -54,6 +54,9 @@ func NewStaticMetadata(pkgPath string, inputOpt InputOptions) *StaticMetadata { } func (sm *StaticMetadata) Update(meta map[string]any) error { + if sm.Frameworks == nil { + sm.Frameworks = make(map[framework.Framework][]string) + } upd := func(field *string, key string) { if raw, ok := meta[key]; ok { diff --git a/pkg/iac/rego/metadata_test.go b/pkg/iac/rego/metadata_test.go index 6b4bb9773a92..6535e21c14ac 100644 --- a/pkg/iac/rego/metadata_test.go +++ b/pkg/iac/rego/metadata_test.go @@ -97,6 +97,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { References: []string{"r", "r1_n", "r2_n"}, CloudFormation: &scan.EngineMetadata{}, Terraform: &scan.EngineMetadata{}, + Frameworks: make(map[framework.Framework][]string), } assert.Equal(t, expected, sm) @@ -114,6 +115,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { References: []string{"r", "r1_n", "r2_n"}, CloudFormation: &scan.EngineMetadata{}, Terraform: &scan.EngineMetadata{}, + Frameworks: make(map[framework.Framework][]string), } assert.Equal(t, expected, sm) @@ -131,10 +133,19 @@ func Test_UpdateStaticMetadata(t *testing.T) { Deprecated: true, CloudFormation: &scan.EngineMetadata{}, Terraform: &scan.EngineMetadata{}, + Frameworks: make(map[framework.Framework][]string), } assert.Equal(t, expected, sm) }) + + t.Run("frameworks is not initialized", func(t *testing.T) { + sm := StaticMetadata{} + err := sm.Update(map[string]any{ + "frameworks": map[string]any{"all": []any{"a", "b", "c"}}, + }) + require.NoError(t, err) + }) } func Test_NewEngineMetadata(t *testing.T) { From 2a6c7ab3b338ce4a8f99d6ac3508c2531dcbe812 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Sat, 24 Aug 2024 02:00:09 +0600 Subject: [PATCH 302/352] fix(misconf): support deprecating for Go checks (#7377) Signed-off-by: nikpivkin --- pkg/iac/scanners/azure/arm/scanner.go | 38 ++++++++------ pkg/iac/scanners/cloudformation/scanner.go | 38 ++++++++------ .../scanners/terraform/executor/executor.go | 25 ++++++--- pkg/iac/scanners/terraform/executor/option.go | 6 +++ pkg/iac/scanners/terraform/scanner.go | 5 +- pkg/iac/scanners/terraform/scanner_test.go | 52 +++++++++++++++++++ 6 files changed, 123 insertions(+), 41 deletions(-) diff --git a/pkg/iac/scanners/azure/arm/scanner.go b/pkg/iac/scanners/azure/arm/scanner.go index 66bab2b4f806..e4b3258f823f 100644 --- a/pkg/iac/scanners/azure/arm/scanner.go +++ b/pkg/iac/scanners/azure/arm/scanner.go @@ -25,20 +25,24 @@ var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { - mu sync.Mutex - scannerOptions []options.ScannerOption - logger *log.Logger - frameworks []framework.Framework - regoOnly bool - loadEmbeddedPolicies bool - loadEmbeddedLibraries bool - policyDirs []string - policyReaders []io.Reader - regoScanner *rego.Scanner - spec string -} - -func (s *Scanner) SetIncludeDeprecatedChecks(b bool) {} + mu sync.Mutex + scannerOptions []options.ScannerOption + logger *log.Logger + frameworks []framework.Framework + regoOnly bool + loadEmbeddedPolicies bool + loadEmbeddedLibraries bool + policyDirs []string + policyReaders []io.Reader + regoScanner *rego.Scanner + spec string + includeDeprecatedChecks bool +} + +func (s *Scanner) SetIncludeDeprecatedChecks(b bool) { + s.includeDeprecatedChecks = b +} + func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) SetSpec(spec string) { @@ -150,9 +154,11 @@ func (s *Scanner) scanDeployment(ctx context.Context, deployment azure.Deploymen return nil, ctx.Err() default: } - if rule.GetRule().RegoPackage != "" { - continue + + if !s.includeDeprecatedChecks && rule.Deprecated { + continue // skip deprecated checks } + ruleResults := rule.Evaluate(deploymentState) if len(ruleResults) > 0 { results = append(results, ruleResults...) diff --git a/pkg/iac/scanners/cloudformation/scanner.go b/pkg/iac/scanners/cloudformation/scanner.go index 6db08c0d15bb..e7677926944d 100644 --- a/pkg/iac/scanners/cloudformation/scanner.go +++ b/pkg/iac/scanners/cloudformation/scanner.go @@ -48,22 +48,26 @@ var _ scanners.FSScanner = (*Scanner)(nil) var _ options.ConfigurableScanner = (*Scanner)(nil) type Scanner struct { - mu sync.Mutex - logger *log.Logger - policyDirs []string - policyReaders []io.Reader - parser *parser.Parser - regoScanner *rego.Scanner - regoOnly bool - loadEmbeddedPolicies bool - loadEmbeddedLibraries bool - options []options.ScannerOption - parserOptions []parser.Option - frameworks []framework.Framework - spec string + mu sync.Mutex + logger *log.Logger + policyDirs []string + policyReaders []io.Reader + parser *parser.Parser + regoScanner *rego.Scanner + regoOnly bool + loadEmbeddedPolicies bool + loadEmbeddedLibraries bool + options []options.ScannerOption + parserOptions []parser.Option + frameworks []framework.Framework + spec string + includeDeprecatedChecks bool +} + +func (s *Scanner) SetIncludeDeprecatedChecks(bool) { + s.includeDeprecatedChecks = true } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) {} func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) addParserOption(opt parser.Option) { @@ -211,9 +215,11 @@ func (s *Scanner) scanFileContext(ctx context.Context, regoScanner *rego.Scanner return nil, ctx.Err() default: } - if rule.GetRule().RegoPackage != "" { - continue + + if !s.includeDeprecatedChecks && rule.Deprecated { + continue // skip deprecated checks } + evalResult := rule.Evaluate(state) if len(evalResult) > 0 { for _, scanResult := range evalResult { diff --git a/pkg/iac/scanners/terraform/executor/executor.go b/pkg/iac/scanners/terraform/executor/executor.go index c6337a2bfd1d..e7561997305d 100644 --- a/pkg/iac/scanners/terraform/executor/executor.go +++ b/pkg/iac/scanners/terraform/executor/executor.go @@ -5,6 +5,7 @@ import ( "runtime" "sort" + "github.com/samber/lo" "github.com/zclconf/go-cty/cty" adapter "github.com/aquasecurity/trivy/pkg/iac/adapters/terraform" @@ -15,17 +16,19 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/terraform" "github.com/aquasecurity/trivy/pkg/iac/types" + ruleTypes "github.com/aquasecurity/trivy/pkg/iac/types/rules" "github.com/aquasecurity/trivy/pkg/log" ) // Executor scans HCL blocks by running all registered rules against them type Executor struct { - workspaceName string - logger *log.Logger - resultsFilters []func(scan.Results) scan.Results - regoScanner *rego.Scanner - regoOnly bool - frameworks []framework.Framework + workspaceName string + logger *log.Logger + resultsFilters []func(scan.Results) scan.Results + regoScanner *rego.Scanner + regoOnly bool + includeDeprecatedChecks bool + frameworks []framework.Framework } // New creates a new Executor @@ -53,8 +56,14 @@ func (e *Executor) Execute(modules terraform.Modules) (scan.Results, error) { e.logger.Debug("Using max routines", log.Int("count", threads)) - registeredRules := rules.GetRegistered(e.frameworks...) - e.logger.Debug("Initialized rule(s).", log.Int("count", len(registeredRules))) + registeredRules := lo.Filter(rules.GetRegistered(e.frameworks...), func(r ruleTypes.RegisteredRule, _ int) bool { + if !e.includeDeprecatedChecks && r.Deprecated { + return false // skip deprecated checks + } + + return true + }) + e.logger.Debug("Initialized Go check(s).", log.Int("count", len(registeredRules))) pool := NewPool(threads, registeredRules, modules, infra, e.regoScanner, e.regoOnly) diff --git a/pkg/iac/scanners/terraform/executor/option.go b/pkg/iac/scanners/terraform/executor/option.go index d2414878c98f..70dd1a9520c3 100644 --- a/pkg/iac/scanners/terraform/executor/option.go +++ b/pkg/iac/scanners/terraform/executor/option.go @@ -37,3 +37,9 @@ func OptionWithRegoOnly(regoOnly bool) Option { e.regoOnly = regoOnly } } + +func OptionWithIncludeDeprecatedChecks(b bool) Option { + return func(e *Executor) { + e.includeDeprecatedChecks = b + } +} diff --git a/pkg/iac/scanners/terraform/scanner.go b/pkg/iac/scanners/terraform/scanner.go index 080d26c0d155..d01afb8d962e 100644 --- a/pkg/iac/scanners/terraform/scanner.go +++ b/pkg/iac/scanners/terraform/scanner.go @@ -46,7 +46,10 @@ type Scanner struct { loadEmbeddedPolicies bool } -func (s *Scanner) SetIncludeDeprecatedChecks(b bool) {} +func (s *Scanner) SetIncludeDeprecatedChecks(b bool) { + s.executorOpt = append(s.executorOpt, executor.OptionWithIncludeDeprecatedChecks(b)) +} + func (s *Scanner) SetCustomSchemas(map[string][]byte) {} func (s *Scanner) SetSpec(spec string) { diff --git a/pkg/iac/scanners/terraform/scanner_test.go b/pkg/iac/scanners/terraform/scanner_test.go index 5c85995027f4..cd1663ec0eda 100644 --- a/pkg/iac/scanners/terraform/scanner_test.go +++ b/pkg/iac/scanners/terraform/scanner_test.go @@ -10,8 +10,13 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers" + "github.com/aquasecurity/trivy/pkg/iac/rules" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" + "github.com/aquasecurity/trivy/pkg/iac/severity" + "github.com/aquasecurity/trivy/pkg/iac/state" + "github.com/aquasecurity/trivy/pkg/iac/types" ) const emptyBucketRule = ` @@ -1065,3 +1070,50 @@ deny[res] { occurrences := failed[0].Occurrences() assert.Equal(t, "code/example/main.tf", occurrences[0].Filename) } + +func TestSkipDeprecatedGoChecks(t *testing.T) { + + check := scan.Rule{ + Provider: providers.AWSProvider, + Service: "service", + ShortCode: "abc", + Severity: severity.High, + Check: func(s *state.State) (results scan.Results) { + results.Add("Deny", types.NewTestMetadata()) + return + }, + } + + fsys := testutil.CreateFS(t, map[string]string{ + "main.tf": `resource "foo" "bar" {}`, + }) + + scanner := New( + options.ScannerWithPolicyFilesystem(fsys), + options.ScannerWithEmbeddedLibraries(false), + options.ScannerWithEmbeddedPolicies(false), + ScannerWithAllDirectories(true), + ) + + t.Run("deprecated", func(t *testing.T) { + check.Deprecated = true + reg := rules.Register(check) + defer rules.Deregister(reg) + + results, err := scanner.ScanFS(context.TODO(), fsys, ".") + require.NoError(t, err) + + require.Empty(t, results) + }) + + t.Run("not deprecated", func(t *testing.T) { + check.Deprecated = false + reg := rules.Register(check) + defer rules.Deregister(reg) + + results, err := scanner.ScanFS(context.TODO(), fsys, ".") + require.NoError(t, err) + + require.Len(t, results, 1) + }) +} From e9b43f81e67789b067352fcb6aa55bc9478bc518 Mon Sep 17 00:00:00 2001 From: afdesk Date: Sat, 24 Aug 2024 09:23:29 +0600 Subject: [PATCH 303/352] feat(python): use minimum version for pip packages (#7348) --- docs/docs/coverage/language/python.md | 15 +++++++-- pkg/dependency/parser/python/pip/parse.go | 27 +++++++++++++--- .../parser/python/pip/parse_test.go | 15 ++++++--- .../parser/python/pip/parse_testcase.go | 32 +++++++++++++++++++ .../pip/testdata/requirements_compatible.txt | 5 +++ pkg/fanal/analyzer/language/python/pip/pip.go | 16 ++++++---- 6 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 pkg/dependency/parser/python/pip/testdata/requirements_compatible.txt diff --git a/docs/docs/coverage/language/python.md b/docs/docs/coverage/language/python.md index 7a697b87d250..27b776ec2d75 100644 --- a/docs/docs/coverage/language/python.md +++ b/docs/docs/coverage/language/python.md @@ -23,7 +23,7 @@ The following table provides an outline of the features Trivy offers. | Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | |-----------------|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| -| pip | requirements.txt | - | Include | - | ✓ | - | +| pip | requirements.txt | - | Include | - | ✓ | ✓ | | Pipenv | Pipfile.lock | ✓ | Include | - | ✓ | Not needed | | Poetry | poetry.lock | ✓ | Exclude | ✓ | - | Not needed | @@ -42,8 +42,17 @@ Trivy parses your files generated by package managers in filesystem/repository s ### pip #### Dependency detection -Trivy only parses [version specifiers](https://packaging.python.org/en/latest/specifications/version-specifiers/#id5) with `==` comparison operator and without `.*`. -To convert unsupported version specifiers - use the `pip freeze` command. +By default, Trivy only parses [version specifiers](https://packaging.python.org/en/latest/specifications/version-specifiers/#id5) with `==` comparison operator and without `.*`. + +Using the [--detection-priority comprehensive](#detection-priority) option ensures that the tool establishes a minimum version, which is particularly useful in scenarios where identifying the exact version is challenging. +In such case Trivy parses specifiers `>=`,`~=` and a trailing `.*`. + +``` +keyring >= 4.1.1 # Minimum version 4.1.1 +Mopidy-Dirble ~= 1.1 # Minimum version 1.1 +python-gitlab==2.0.* # Minimum version 2.0.0 +``` +Also, there is a way to convert unsupported version specifiers - use the `pip freeze` command. ```bash $ cat requirements.txt diff --git a/pkg/dependency/parser/python/pip/parse.go b/pkg/dependency/parser/python/pip/parse.go index 0d9e040f952b..981b7def69c3 100644 --- a/pkg/dependency/parser/python/pip/parse.go +++ b/pkg/dependency/parser/python/pip/parse.go @@ -25,14 +25,29 @@ const ( ) type Parser struct { - logger *log.Logger + logger *log.Logger + useMinVersion bool } -func NewParser() *Parser { +func NewParser(useMinVersion bool) *Parser { return &Parser{ - logger: log.WithPrefix("pip"), + logger: log.WithPrefix("pip"), + useMinVersion: useMinVersion, } } +func (p *Parser) splitLine(line string) []string { + separators := []string{"~=", ">=", "=="} + // Without useMinVersion check only `==` + if !p.useMinVersion { + separators = []string{"=="} + } + for _, sep := range separators { + if result := strings.Split(line, sep); len(result) == 2 { + return result + } + } + return nil +} func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { // `requirements.txt` can use byte order marks (BOM) @@ -53,10 +68,14 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc line = rStripByKey(line, commentMarker) line = rStripByKey(line, endColon) line = rStripByKey(line, hashMarker) - s := strings.Split(line, "==") + + s := p.splitLine(line) if len(s) != 2 { continue } + if p.useMinVersion && strings.HasSuffix(s[1], ".*") { + s[1] = strings.TrimSuffix(s[1], "*") + "0" + } if !isValidName(s[0]) || !isValidVersion(s[1]) { p.logger.Debug("Invalid package name/version in requirements.txt.", log.String("line", text)) diff --git a/pkg/dependency/parser/python/pip/parse_test.go b/pkg/dependency/parser/python/pip/parse_test.go index 3a13c5272cc8..97964ea77a4a 100644 --- a/pkg/dependency/parser/python/pip/parse_test.go +++ b/pkg/dependency/parser/python/pip/parse_test.go @@ -12,9 +12,10 @@ import ( func TestParse(t *testing.T) { tests := []struct { - name string - filePath string - want []ftypes.Package + name string + filePath string + useMinVersion bool + want []ftypes.Package }{ { name: "happy path", @@ -66,6 +67,12 @@ func TestParse(t *testing.T) { filePath: "testdata/requirements_with_templating_engine.txt", want: nil, }, + { + name: "compatible versions", + filePath: "testdata/requirements_compatible.txt", + useMinVersion: true, + want: requirementsCompatibleVersions, + }, } for _, tt := range tests { @@ -73,7 +80,7 @@ func TestParse(t *testing.T) { f, err := os.Open(tt.filePath) require.NoError(t, err) - got, _, err := NewParser().Parse(f) + got, _, err := NewParser(tt.useMinVersion).Parse(f) require.NoError(t, err) assert.Equal(t, tt.want, got) diff --git a/pkg/dependency/parser/python/pip/parse_testcase.go b/pkg/dependency/parser/python/pip/parse_testcase.go index e8192ee1775d..e4a8d83d7117 100644 --- a/pkg/dependency/parser/python/pip/parse_testcase.go +++ b/pkg/dependency/parser/python/pip/parse_testcase.go @@ -3,6 +3,38 @@ package pip import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" var ( + requirementsCompatibleVersions = []ftypes.Package{ + { + Name: "keyring", + Version: "4.1.1", + Locations: []ftypes.Location{ + { + StartLine: 1, + EndLine: 1, + }, + }, + }, + { + Name: "Mopidy-Dirble", + Version: "1.1", + Locations: []ftypes.Location{ + { + StartLine: 2, + EndLine: 2, + }, + }, + }, + { + Name: "python-gitlab", + Version: "2.0.0", + Locations: []ftypes.Location{ + { + StartLine: 3, + EndLine: 3, + }, + }, + }, + } requirementsFlask = []ftypes.Package{ { Name: "click", diff --git a/pkg/dependency/parser/python/pip/testdata/requirements_compatible.txt b/pkg/dependency/parser/python/pip/testdata/requirements_compatible.txt new file mode 100644 index 000000000000..dbcde5b7ab10 --- /dev/null +++ b/pkg/dependency/parser/python/pip/testdata/requirements_compatible.txt @@ -0,0 +1,5 @@ +keyring >= 4.1.1 # Minimum version 4.1.1 +Mopidy-Dirble ~= 1.1 # Compatible release. Same as >= 1.1, == 1.* +python-gitlab==2.0.* +django==5.*.* # this dep should be skipped +django==4.*.1 \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/python/pip/pip.go b/pkg/fanal/analyzer/language/python/pip/pip.go index e08a90e7a70b..670dd195bb50 100644 --- a/pkg/fanal/analyzer/language/python/pip/pip.go +++ b/pkg/fanal/analyzer/language/python/pip/pip.go @@ -38,14 +38,16 @@ var pythonExecNames = []string{ } type pipLibraryAnalyzer struct { - logger *log.Logger - metadataParser packaging.Parser + logger *log.Logger + metadataParser packaging.Parser + detectionPriority types.DetectionPriority } -func newPipLibraryAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { +func newPipLibraryAnalyzer(opts analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { return pipLibraryAnalyzer{ - logger: log.WithPrefix("pip"), - metadataParser: *packaging.NewParser(), + logger: log.WithPrefix("pip"), + metadataParser: *packaging.NewParser(), + detectionPriority: opts.DetectionPriority, }, nil } @@ -62,8 +64,10 @@ func (a pipLibraryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAn return true } + useMinVersion := a.detectionPriority == types.PriorityComprehensive + if err = fsutils.WalkDir(input.FS, ".", required, func(pathPath string, d fs.DirEntry, r io.Reader) error { - app, err := language.Parse(types.Pip, pathPath, r, pip.NewParser()) + app, err := language.Parse(types.Pip, pathPath, r, pip.NewParser(useMinVersion)) if err != nil { return xerrors.Errorf("unable to parse requirements.txt: %w", err) } From 45a962705444e373e634cb0c47d0f83e9804d1db Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Sat, 24 Aug 2024 09:25:48 +0600 Subject: [PATCH 304/352] docs: add pkg flags to config file page (#7370) --- .../references/configuration/config-file.md | 20 +++++++++++++++++++ magefiles/docs.go | 1 + 2 files changed, 21 insertions(+) diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index 932710f6e2b9..6a54b8e27bdc 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -431,6 +431,26 @@ module: # Same as '--enable-modules' enable-modules: [] +``` +## Package options + +```yaml +pkg: + # Same as '--include-dev-deps' + include-dev-deps: false + + # Same as '--pkg-relationships' + relationships: + - unknown + - root + - direct + - indirect + + # Same as '--pkg-types' + types: + - os + - library + ``` ## Registry options diff --git a/magefiles/docs.go b/magefiles/docs.go index bdb85c5357cb..bfdd7480d2a9 100644 --- a/magefiles/docs.go +++ b/magefiles/docs.go @@ -69,6 +69,7 @@ func generateConfigDocs(filename string) error { flag.NewLicenseFlagGroup(), flag.NewMisconfFlagGroup(), flag.NewModuleFlagGroup(), + flag.NewPackageFlagGroup(), flag.NewRegistryFlagGroup(), flag.NewRegoFlagGroup(), flag.NewReportFlagGroup(), From be861265cafc89787fda09c59b2ef175e3d04204 Mon Sep 17 00:00:00 2001 From: simar7 <1254783+simar7@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:29:10 -0600 Subject: [PATCH 305/352] feat(misconf): Add support for using spec from on-disk bundle (#7179) --- pkg/compliance/spec/compliance.go | 41 +++++++++++++-- pkg/compliance/spec/compliance_test.go | 52 +++++++++++++++++++ .../content/specs/compliance/invalid.yaml | 1 + .../content/specs/compliance/testspec.yaml | 15 ++++++ .../testdata/testcache/policy/metadata.json | 1 + pkg/flag/report_flags.go | 3 +- 6 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/invalid.yaml create mode 100644 pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/testspec.yaml create mode 100644 pkg/compliance/spec/testdata/testcache/policy/metadata.json diff --git a/pkg/compliance/spec/compliance.go b/pkg/compliance/spec/compliance.go index 7b0b4f6cffdd..70355eaa926f 100644 --- a/pkg/compliance/spec/compliance.go +++ b/pkg/compliance/spec/compliance.go @@ -3,6 +3,7 @@ package spec import ( "fmt" "os" + "path/filepath" "strings" "github.com/samber/lo" @@ -11,6 +12,7 @@ import ( sp "github.com/aquasecurity/trivy-checks/pkg/spec" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" ) @@ -70,18 +72,41 @@ func scannerByCheckID(checkID string) types.Scanner { } } +func checksDir(cacheDir string) string { + return filepath.Join(cacheDir, "policy") +} + +func complianceSpecDir(cacheDir string) string { + return filepath.Join(checksDir(cacheDir), "content", "specs", "compliance") +} + // GetComplianceSpec accepct compliance flag name/path and return builtin or file system loaded spec -func GetComplianceSpec(specNameOrPath string) (ComplianceSpec, error) { +func GetComplianceSpec(specNameOrPath, cacheDir string) (ComplianceSpec, error) { + if specNameOrPath == "" { + return ComplianceSpec{}, nil + } + var b []byte var err error - if strings.HasPrefix(specNameOrPath, "@") { + if strings.HasPrefix(specNameOrPath, "@") { // load user specified spec from disk b, err = os.ReadFile(strings.TrimPrefix(specNameOrPath, "@")) if err != nil { return ComplianceSpec{}, fmt.Errorf("error retrieving compliance spec from path: %w", err) } + log.Debug("Compliance spec loaded from specified path", log.String("path", specNameOrPath)) } else { - // TODO: GetSpecByName() should return []byte - b = []byte(sp.NewSpecLoader().GetSpecByName(specNameOrPath)) + _, err := os.Stat(filepath.Join(checksDir(cacheDir), "metadata.json")) + if err != nil { // cache corrupt or bundle does not exist, load embedded version + b = []byte(sp.NewSpecLoader().GetSpecByName(specNameOrPath)) + log.Debug("Compliance spec loaded from embedded library", log.String("spec", specNameOrPath)) + } else { + // load from bundle on disk + b, err = LoadFromBundle(cacheDir, specNameOrPath) + if err != nil { + return ComplianceSpec{}, err + } + log.Debug("Compliance spec loaded from disk bundle", log.String("spec", specNameOrPath)) + } } var complianceSpec ComplianceSpec @@ -91,3 +116,11 @@ func GetComplianceSpec(specNameOrPath string) (ComplianceSpec, error) { return complianceSpec, nil } + +func LoadFromBundle(cacheDir, specNameOrPath string) ([]byte, error) { + b, err := os.ReadFile(filepath.Join(complianceSpecDir(cacheDir), specNameOrPath+".yaml")) + if err != nil { + return nil, fmt.Errorf("error retrieving compliance spec from bundle %s: %w", specNameOrPath, err) + } + return b, nil +} diff --git a/pkg/compliance/spec/compliance_test.go b/pkg/compliance/spec/compliance_test.go index a4ee4961973e..0b44702b4159 100644 --- a/pkg/compliance/spec/compliance_test.go +++ b/pkg/compliance/spec/compliance_test.go @@ -1,10 +1,12 @@ package spec_test import ( + "path/filepath" "sort" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/compliance/spec" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" @@ -239,3 +241,53 @@ func TestComplianceSpec_CheckIDs(t *testing.T) { }) } } + +func TestComplianceSpec_LoadFromDiskBundle(t *testing.T) { + + t.Run("load user specified spec from disk", func(t *testing.T) { + cs, err := spec.GetComplianceSpec(filepath.Join("@testdata", "testcache", "policy", "content", "specs", "compliance", "testspec.yaml"), filepath.Join("testdata", "testcache")) + require.NoError(t, err) + assert.Equal(t, spec.ComplianceSpec{Spec: iacTypes.Spec{ + ID: "test-spec-1.2", + Title: "Test Spec", + Description: "This is a test spec", + RelatedResources: []string{ + "https://www.google.ca", + }, + Version: "1.2", + Controls: []iacTypes.Control{ + { + Name: "moar-testing", + Description: "Test needs foo bar baz", + ID: "1.1", + Checks: []iacTypes.SpecCheck{ + {ID: "AVD-TEST-1234"}, + }, + Severity: "LOW", + }, + }, + }}, cs) + }) + + t.Run("load user specified spec from disk fails", func(t *testing.T) { + _, err := spec.GetComplianceSpec("@doesnotexist", "does-not-matter") + assert.Contains(t, err.Error(), "error retrieving compliance spec from path") + }) + + t.Run("bundle does not exist", func(t *testing.T) { + cs, err := spec.GetComplianceSpec("aws-cis-1.2", "does-not-matter") + require.NoError(t, err) + assert.Equal(t, "aws-cis-1.2", cs.Spec.ID) + }) + + t.Run("load spec from disk", func(t *testing.T) { + cs, err := spec.GetComplianceSpec("testspec", filepath.Join("testdata", "testcache")) + require.NoError(t, err) + assert.Equal(t, "test-spec-1.2", cs.Spec.ID) + }) + + t.Run("load spec yaml unmarshal failure", func(t *testing.T) { + _, err := spec.GetComplianceSpec("invalid", filepath.Join("testdata", "testcache")) + assert.Contains(t, err.Error(), "spec yaml decode error") + }) +} diff --git a/pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/invalid.yaml b/pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/invalid.yaml new file mode 100644 index 000000000000..29dcba2e15af --- /dev/null +++ b/pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/invalid.yaml @@ -0,0 +1 @@ +this is not yaml but easier to read \ No newline at end of file diff --git a/pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/testspec.yaml b/pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/testspec.yaml new file mode 100644 index 000000000000..ec1f75f52970 --- /dev/null +++ b/pkg/compliance/spec/testdata/testcache/policy/content/specs/compliance/testspec.yaml @@ -0,0 +1,15 @@ +spec: + id: test-spec-1.2 + title: Test Spec + description: This is a test spec + version: "1.2" + relatedResources: + - https://www.google.ca + controls: + - id: "1.1" + name: moar-testing + description: |- + Test needs foo bar baz + checks: + - id: AVD-TEST-1234 + severity: LOW \ No newline at end of file diff --git a/pkg/compliance/spec/testdata/testcache/policy/metadata.json b/pkg/compliance/spec/testdata/testcache/policy/metadata.json new file mode 100644 index 000000000000..ba37beda3850 --- /dev/null +++ b/pkg/compliance/spec/testdata/testcache/policy/metadata.json @@ -0,0 +1 @@ +{"Digest":"sha256:ef2d9ad4fce0f933b20a662004d7e55bf200987c180e7f2cd531af631f408bb3","DownloadedAt":"2024-08-07T20:07:48.917915-06:00"} \ No newline at end of file diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go index ce833cc1b13e..67d553b65553 100644 --- a/pkg/flag/report_flags.go +++ b/pkg/flag/report_flags.go @@ -9,6 +9,7 @@ import ( "golang.org/x/xerrors" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/compliance/spec" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/result" @@ -260,7 +261,7 @@ func loadComplianceTypes(compliance string) (spec.ComplianceSpec, error) { return spec.ComplianceSpec{}, xerrors.Errorf("unknown compliance : %v", compliance) } - cs, err := spec.GetComplianceSpec(compliance) + cs, err := spec.GetComplianceSpec(compliance, cache.DefaultDir()) if err != nil { return spec.ComplianceSpec{}, xerrors.Errorf("spec loading from file system error: %w", err) } From dd9733e950d3127aa2ac90c45ec7e2b88a2b47ca Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 27 Aug 2024 08:56:53 +0600 Subject: [PATCH 306/352] fix(report): escape `Message` field in `asff.tpl` template (#7401) --- contrib/asff.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/asff.tpl b/contrib/asff.tpl index d6833a1d7ff1..3cadbb03ce79 100644 --- a/contrib/asff.tpl +++ b/contrib/asff.tpl @@ -108,7 +108,7 @@ "Region": "{{ env "AWS_REGION" }}", "Details": { "Other": { - "Message": "{{ .Message }}", + "Message": "{{ escapeString .Message }}", "Filename": "{{ $target }}", "StartLine": "{{ .CauseMetadata.StartLine }}", "EndLine": "{{ .CauseMetadata.EndLine }}" From 0799770b8827a8276ad0d6d9ac7e0381c286757c Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Wed, 28 Aug 2024 04:07:07 +0600 Subject: [PATCH 307/352] fix(misconf): use module to log when metadata retrieval fails (#7405) Signed-off-by: nikpivkin --- pkg/iac/rego/embed.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/iac/rego/embed.go b/pkg/iac/rego/embed.go index e582508a396b..082870071c90 100644 --- a/pkg/iac/rego/embed.go +++ b/pkg/iac/rego/embed.go @@ -54,11 +54,11 @@ func RegisterRegoRules(modules map[string]*ast.Module) { for _, module := range modules { metadata, err := retriever.RetrieveMetadata(ctx, module) if err != nil { - log.Warn("Failed to retrieve metadata", log.String("avdid", metadata.AVDID), log.Err(err)) + log.Warn("Failed to retrieve metadata", log.String("package", module.Package.String()), log.Err(err)) continue } - if metadata.AVDID == "" { + if !metadata.Library && metadata.AVDID == "" { log.Warn("Check ID is empty", log.FilePath(module.Package.Location.File)) continue } From 44e468603d44b077cc4606327fb3e7d7ca435e05 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 29 Aug 2024 05:26:02 +0600 Subject: [PATCH 308/352] feat(misconf): support for ignore by nested attributes (#7205) Signed-off-by: nikpivkin --- docs/docs/scanner/misconfiguration/index.md | 7 +- .../scanners/terraform/executor/executor.go | 17 +- pkg/iac/scanners/terraform/ignore_test.go | 163 ++++++++++++++++++ pkg/iac/terraform/block.go | 115 ++++++++++++ 4 files changed, 287 insertions(+), 15 deletions(-) diff --git a/docs/docs/scanner/misconfiguration/index.md b/docs/docs/scanner/misconfiguration/index.md index 7615f3342265..9824936b8e53 100644 --- a/docs/docs/scanner/misconfiguration/index.md +++ b/docs/docs/scanner/misconfiguration/index.md @@ -534,7 +534,7 @@ If you want to ignore multiple resources on different attributes, you can specif #trivy:ignore:aws-ec2-no-public-ingress-sgr[from_port=5432] ``` -You can also ignore a resource on multiple attributes: +You can also ignore a resource on multiple attributes in the same rule: ```tf locals { rules = { @@ -563,10 +563,7 @@ resource "aws_security_group_rule" "example" { } ``` -Checks can also be ignored by nested attributes, but certain restrictions apply: - -- You cannot access an individual block using indexes, for example when working with dynamic blocks. -- Special variables like [each](https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#the-each-object) and [count](https://developer.hashicorp.com/terraform/language/meta-arguments/count#the-count-object) cannot be accessed. +Checks can also be ignored by nested attributes: ```tf #trivy:ignore:*[logging_config.prefix=myprefix] diff --git a/pkg/iac/scanners/terraform/executor/executor.go b/pkg/iac/scanners/terraform/executor/executor.go index e7561997305d..452e47dcc8de 100644 --- a/pkg/iac/scanners/terraform/executor/executor.go +++ b/pkg/iac/scanners/terraform/executor/executor.go @@ -135,26 +135,23 @@ func ignoreByParams(params map[string]string, modules terraform.Modules, m *type if block == nil { return true } - for key, val := range params { - attr, _ := block.GetNestedAttribute(key) - if attr.IsNil() || !attr.Value().IsKnown() { - return false - } - switch attr.Type() { + for key, param := range params { + val := block.GetValueByPath(key) + switch val.Type() { case cty.String: - if !attr.Equals(val) { + if val.AsString() != param { return false } case cty.Number: - bf := attr.Value().AsBigFloat() + bf := val.AsBigFloat() f64, _ := bf.Float64() comparableInt := fmt.Sprintf("%d", int(f64)) comparableFloat := fmt.Sprintf("%f", f64) - if val != comparableInt && val != comparableFloat { + if param != comparableInt && param != comparableFloat { return false } case cty.Bool: - if fmt.Sprintf("%t", attr.IsTrue()) != val { + if fmt.Sprintf("%t", val.True()) != param { return false } default: diff --git a/pkg/iac/scanners/terraform/ignore_test.go b/pkg/iac/scanners/terraform/ignore_test.go index 6183469d5296..4a2cbce14a86 100644 --- a/pkg/iac/scanners/terraform/ignore_test.go +++ b/pkg/iac/scanners/terraform/ignore_test.go @@ -442,6 +442,21 @@ resource "bad" "my-rule" { } } } +`, + assertLength: 0, + }, + { + name: "ignore by indexed dynamic block value", + inputOptions: ` +// trivy:ignore:*[secure_settings.0.enabled=false] +resource "bad" "my-rule" { + dynamic "secure_settings" { + for_each = ["false", "true"] + content { + enabled = secure_settings.value + } + } +} `, assertLength: 0, }, @@ -604,6 +619,154 @@ data "aws_iam_policy_document" "test_policy" { resources = ["*"] # trivy:ignore:aws-iam-enforce-mfa } } +`, + assertLength: 0, + }, + { + name: "ignore by each.value", + inputOptions: ` +// trivy:ignore:*[each.value=false] +resource "bad" "my-rule" { + for_each = toset(["false", "true", "false"]) + secure = each.value +} +`, + assertLength: 0, + }, + { + name: "ignore by nested each.value", + inputOptions: ` +locals { + vms = [ + { + ip_address = "10.0.0.1" + name = "vm-1" + }, + { + ip_address = "10.0.0.2" + name = "vm-2" + } + ] +} +// trivy:ignore:*[each.value.name=vm-2] +resource "bad" "my-rule" { + secure = false + for_each = { for vm in local.vms : vm.name => vm } + ip_address = each.value.ip_address +} +`, + assertLength: 1, + }, + { + name: "ignore resource with `count` meta-argument", + inputOptions: ` +// trivy:ignore:*[count.index=1] +resource "bad" "my-rule" { + count = 2 + secure = false +} +`, + assertLength: 1, + }, + { + name: "invalid index when accessing blocks", + inputOptions: ` +// trivy:ignore:*[ingress.99.port=9090] +// trivy:ignore:*[ingress.-10.port=9090] +resource "bad" "my-rule" { + secure = false + dynamic "ingress" { + for_each = [8080, 9090] + content { + port = ingress.value + } + } +} +`, + assertLength: 1, + }, + { + name: "ignore by list value", + inputOptions: ` +#trivy:ignore:*[someattr.1.Environment=dev] +resource "bad" "my-rule" { + secure = false + someattr = [ + { + Environment = "prod" + }, + { + Environment = "dev" + } + ] +} +`, + assertLength: 0, + }, + { + name: "ignore by list value with invalid index", + inputOptions: ` +#trivy:ignore:*[someattr.-2.Environment=dev] +resource "bad" "my-rule" { + secure = false + someattr = [ + { + Environment = "prod" + }, + { + Environment = "dev" + } + ] +} +`, + assertLength: 1, + }, + { + name: "ignore by object value", + inputOptions: ` +#trivy:ignore:*[tags.Environment=dev] +resource "bad" "my-rule" { + secure = false + tags = { + Environment = "dev" + } +} +`, + assertLength: 0, + }, + { + name: "ignore by object value in block", + inputOptions: ` +#trivy:ignore:*[someblock.tags.Environment=dev] +resource "bad" "my-rule" { + secure = false + someblock { + tags = { + Environment = "dev" + } + } +} +`, + assertLength: 0, + }, + { + name: "ignore by list value in map", + inputOptions: ` +variable "testvar" { + type = map(list(string)) + default = { + server1 = ["web", "dev"] + server2 = ["prod"] + } +} + +#trivy:ignore:*[someblock.someattr.server1.1=dev] +resource "bad" "my-rule" { + secure = false + someblock { + someattr = var.testvar + } +} `, assertLength: 0, }, diff --git a/pkg/iac/terraform/block.go b/pkg/iac/terraform/block.go index 9245ad6c38a8..dbf7352959d0 100644 --- a/pkg/iac/terraform/block.go +++ b/pkg/iac/terraform/block.go @@ -3,6 +3,7 @@ package terraform import ( "fmt" "io/fs" + "strconv" "strings" "github.com/google/uuid" @@ -298,6 +299,120 @@ func (b *Block) GetAttribute(name string) *Attribute { return nil } +// GetValueByPath returns the value of the attribute located at the given path. +// Supports special paths like "count.index," "each.key," and "each.value." +// The path may contain indices, keys and dots (used as separators). +func (b *Block) GetValueByPath(path string) cty.Value { + + if path == "count.index" || path == "each.key" || path == "each.value" { + return b.Context().GetByDot(path) + } + + if restPath, ok := strings.CutPrefix(path, "each.value."); ok { + if restPath == "" { + return cty.NilVal + } + + val := b.Context().GetByDot("each.value") + res, err := getValueByPath(val, strings.Split(restPath, ".")) + if err != nil { + return cty.NilVal + } + return res + } + + attr, restPath := b.getAttributeByPath(path) + + if attr == nil { + return cty.NilVal + } + + if !attr.IsIterable() || len(restPath) == 0 { + return attr.Value() + } + + res, err := getValueByPath(attr.Value(), restPath) + if err != nil { + return cty.NilVal + } + return res +} + +func (b *Block) getAttributeByPath(path string) (*Attribute, []string) { + steps := strings.Split(path, ".") + + if len(steps) == 1 { + return b.GetAttribute(steps[0]), nil + } + + var ( + attribute *Attribute + stepIndex int + ) + + for currentBlock := b; currentBlock != nil && stepIndex < len(steps); { + blocks := currentBlock.GetBlocks(steps[stepIndex]) + var nextBlock *Block + if !hasIndex(steps, stepIndex+1) && len(blocks) > 0 { + // if index is not provided then return the first block for backwards compatibility + nextBlock = blocks[0] + } else if len(blocks) > 1 && stepIndex < len(steps)-2 { + // handling the case when there are multiple blocks with the same name, + // e.g. when using a `dynamic` block + indexVal, err := strconv.Atoi(steps[stepIndex+1]) + if err == nil && indexVal >= 0 && indexVal < len(blocks) { + nextBlock = blocks[indexVal] + stepIndex++ + } + } + + if nextBlock == nil { + attribute = currentBlock.GetAttribute(steps[stepIndex]) + } + + currentBlock = nextBlock + stepIndex++ + } + + return attribute, steps[stepIndex:] +} + +func hasIndex(steps []string, idx int) bool { + if idx < 0 || idx >= len(steps) { + return false + } + _, err := strconv.Atoi(steps[idx]) + return err == nil +} + +func getValueByPath(val cty.Value, path []string) (cty.Value, error) { + var err error + for _, step := range path { + switch valType := val.Type(); { + case valType.IsMapType(): + val, err = cty.IndexStringPath(step).Apply(val) + case valType.IsObjectType(): + val, err = cty.GetAttrPath(step).Apply(val) + case valType.IsListType() || valType.IsTupleType(): + var idx int + idx, err = strconv.Atoi(step) + if err != nil { + return cty.NilVal, fmt.Errorf("index %q is not a number", step) + } + val, err = cty.IndexIntPath(idx).Apply(val) + default: + return cty.NilVal, fmt.Errorf( + "unexpected value type %s for path step %q", + valType.FriendlyName(), step, + ) + } + if err != nil { + return cty.NilVal, err + } + } + return val, nil +} + func (b *Block) GetNestedAttribute(name string) (*Attribute, *Block) { parts := strings.Split(name, ".") From 9d7264af8e85bcc0dba600b8366d0470d455251c Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 29 Aug 2024 05:51:25 +0600 Subject: [PATCH 309/352] fix(misconf): do not filter Terraform plan JSON by name (#7406) Signed-off-by: nikpivkin --- docs/docs/coverage/iac/index.md | 22 +++--- pkg/iac/detection/detect.go | 16 ++-- .../scanners/terraformplan/tfjson/scanner.go | 38 +++++----- .../terraformplan/tfjson/scanner_test.go | 73 ++++++++----------- .../tfjson/test/testdata/arbitrary_name.json | 1 + 5 files changed, 71 insertions(+), 79 deletions(-) create mode 100644 pkg/iac/scanners/terraformplan/tfjson/test/testdata/arbitrary_name.json diff --git a/docs/docs/coverage/iac/index.md b/docs/docs/coverage/iac/index.md index 963e9b11b8c5..40f0a88023dd 100644 --- a/docs/docs/coverage/iac/index.md +++ b/docs/docs/coverage/iac/index.md @@ -8,17 +8,17 @@ Trivy scans Infrastructure as Code (IaC) files for ## Supported configurations -| Config type | File patterns | -|-------------------------------------|-----------------------------------------------| -| [Kubernetes](kubernetes.md) | \*.yml, \*.yaml, \*.json | -| [Docker](docker.md) | Dockerfile, Containerfile | -| [Terraform](terraform.md) | \*.tf, \*.tf.json, \*.tfvars | -| [Terraform Plan](terraform.md) | tfplan, \*.tfplan, \*.tfplan.json, \*.tf.json | -| [CloudFormation](cloudformation.md) | \*.yml, \*.yaml, \*.json | -| [Azure ARM Template](azure-arm.md) | \*.json | -| [Helm](helm.md) | \*.yaml, \*.tpl, \*.tar.gz, etc. | -| [YAML][json-and-yaml] | \*.yaml, \*.yml | -| [JSON][json-and-yaml] | \*.json | +| Config type | File patterns | +|-------------------------------------|----------------------------------| +| [Kubernetes](kubernetes.md) | \*.yml, \*.yaml, \*.json | +| [Docker](docker.md) | Dockerfile, Containerfile | +| [Terraform](terraform.md) | \*.tf, \*.tf.json, \*.tfvars | +| [Terraform Plan](terraform.md) | tfplan, \*.tfplan, \*.json | +| [CloudFormation](cloudformation.md) | \*.yml, \*.yaml, \*.json | +| [Azure ARM Template](azure-arm.md) | \*.json | +| [Helm](helm.md) | \*.yaml, \*.tpl, \*.tar.gz, etc. | +| [YAML][json-and-yaml] | \*.yaml, \*.yml | +| [JSON][json-and-yaml] | \*.json | [misconf]: ../../scanner/misconfiguration/index.md [secret]: ../../scanner/secret.md diff --git a/pkg/iac/detection/detect.go b/pkg/iac/detection/detect.go index fcfab15f83b2..fcaad6a36179 100644 --- a/pkg/iac/detection/detect.go +++ b/pkg/iac/detection/detect.go @@ -88,12 +88,17 @@ func init() { contents := make(map[string]any) err := json.NewDecoder(r).Decode(&contents) - if err == nil { - if _, ok := contents["terraform_version"]; ok { - _, stillOk := contents["format_version"] - return stillOk + if err != nil { + return false + } + + for _, k := range []string{"terraform_version", "format_version"} { + if _, ok := contents[k]; !ok { + return false } } + + return true } return false } @@ -150,8 +155,7 @@ func init() { return false } - return (sniff.Parameters != nil && len(sniff.Parameters) > 0) || - (sniff.Resources != nil && len(sniff.Resources) > 0) + return len(sniff.Parameters) > 0 || len(sniff.Resources) > 0 } matchers[FileTypeDockerfile] = func(name string, _ io.ReadSeeker) bool { diff --git a/pkg/iac/scanners/terraformplan/tfjson/scanner.go b/pkg/iac/scanners/terraformplan/tfjson/scanner.go index 875644d1dd2a..8b16ecb43116 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/scanner.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner.go @@ -6,8 +6,6 @@ import ( "io" "io/fs" - "github.com/bmatcuk/doublestar/v4" - "github.com/aquasecurity/trivy/pkg/iac/framework" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" @@ -17,11 +15,6 @@ import ( "github.com/aquasecurity/trivy/pkg/log" ) -var tfPlanExts = []string{ - "**/*tfplan.json", - "**/*tf.json", -} - type Scanner struct { parser *parser.Parser logger *log.Logger @@ -93,25 +86,32 @@ func (s *Scanner) Name() string { return "Terraform Plan JSON" } -func (s *Scanner) ScanFS(ctx context.Context, inputFS fs.FS, dir string) (scan.Results, error) { - var filesFound []string +func (s *Scanner) ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) { + + var results scan.Results - for _, ext := range tfPlanExts { - files, err := doublestar.Glob(inputFS, ext, doublestar.WithFilesOnly()) + walkFn := func(path string, d fs.DirEntry, err error) error { if err != nil { - return nil, fmt.Errorf("unable to scan for terraform plan files: %w", err) + return err } - filesFound = append(filesFound, files...) - } - var results scan.Results - for _, f := range filesFound { - res, err := s.ScanFile(f, inputFS) + if d.IsDir() { + return nil + } + + res, err := s.ScanFile(path, fsys) if err != nil { - return nil, err + return fmt.Errorf("failed to scan %s: %w", path, err) } + results = append(results, res...) + return nil } + + if err := fs.WalkDir(fsys, dir, walkFn); err != nil { + return nil, err + } + return results, nil } @@ -148,7 +148,7 @@ func (s *Scanner) Scan(reader io.Reader) (scan.Results, error) { planFS, err := planFile.ToFS() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to convert plan to FS: %w", err) } scanner := terraformScanner.New(s.options...) diff --git a/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go b/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go index 678b32e00f48..289147f482dd 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/tfjson/scanner_test.go @@ -12,20 +12,7 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/scanners/options" ) -func Test_TerraformScanner(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - inputFile string - inputRego string - options []options.ScannerOption - }{ - { - name: "old rego metadata", - inputFile: "test/testdata/plan.json", - inputRego: ` -package defsec.abcdefg +const defaultCheck = `package defsec.abcdefg __rego_metadata__ := { "id": "TEST123", @@ -48,48 +35,40 @@ deny[cause] { bucket := input.aws.s3.buckets[_] bucket.name.value == "tfsec-plan-testing" cause := bucket.name -} -`, +}` + +func Test_TerraformScanner(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + inputFile string + check string + options []options.ScannerOption + }{ + { + name: "old rego metadata", + inputFile: "test/testdata/plan.json", + check: defaultCheck, options: []options.ScannerOption{ options.ScannerWithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedPolicies(false)}, + }, }, { name: "with user namespace", inputFile: "test/testdata/plan.json", - inputRego: ` -# METADATA -# title: Bad buckets are bad -# description: Bad buckets are bad because they are not good. -# scope: package -# schemas: -# - input: schema["input"] -# custom: -# avd_id: AVD-TEST-0123 -# severity: CRITICAL -# short_code: very-bad-misconfig -# recommended_action: "Fix the s3 bucket" - -package user.foobar.ABC001 - -deny[cause] { - bucket := input.aws.s3.buckets[_] - bucket.name.value == "tfsec-plan-testing" - cause := bucket.name -} -`, + check: defaultCheck, options: []options.ScannerOption{ options.ScannerWithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedPolicies(false), options.ScannerWithPolicyNamespaces("user"), }, }, { name: "with templated plan json", inputFile: "test/testdata/plan_with_template.json", - inputRego: ` + check: ` # METADATA # title: Bad buckets are bad # description: Bad buckets are bad because they are not good. @@ -113,19 +92,27 @@ deny[cause] { options: []options.ScannerOption{ options.ScannerWithPolicyDirs("rules"), options.ScannerWithRegoOnly(true), - options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithPolicyNamespaces("user"), + }, + }, + { + name: "plan with arbitrary name", + inputFile: "test/testdata/arbitrary_name.json", + check: defaultCheck, + options: []options.ScannerOption{ + options.ScannerWithPolicyDirs("rules"), + options.ScannerWithRegoOnly(true), options.ScannerWithPolicyNamespaces("user"), }, }, } for _, tc := range testCases { - tc := tc t.Run(tc.name, func(t *testing.T) { b, _ := os.ReadFile(tc.inputFile) fs := testutil.CreateFS(t, map[string]string{ "/code/main.tfplan.json": string(b), - "/rules/test.rego": tc.inputRego, + "/rules/test.rego": tc.check, }) so := append(tc.options, options.ScannerWithPolicyFilesystem(fs)) diff --git a/pkg/iac/scanners/terraformplan/tfjson/test/testdata/arbitrary_name.json b/pkg/iac/scanners/terraformplan/tfjson/test/testdata/arbitrary_name.json new file mode 100644 index 000000000000..e0109b0325a8 --- /dev/null +++ b/pkg/iac/scanners/terraformplan/tfjson/test/testdata/arbitrary_name.json @@ -0,0 +1 @@ +{"format_version":"1.2","terraform_version":"1.8.1","planned_values":{"root_module":{"resources":[{"address":"aws_s3_bucket.this","mode":"managed","type":"aws_s3_bucket","name":"this","provider_name":"registry.opentofu.org/hashicorp/aws","schema_version":0,"values":{"bucket":"tfsec-plan-testing","force_destroy":false,"tags":null,"timeouts":null},"sensitive_values":{"cors_rule":[],"grant":[],"lifecycle_rule":[],"logging":[],"object_lock_configuration":[],"replication_configuration":[],"server_side_encryption_configuration":[],"tags_all":{},"versioning":[],"website":[]}}]}},"resource_drift":[{"address":"aws_security_group.this","mode":"managed","type":"aws_security_group","name":"this","provider_name":"registry.opentofu.org/hashicorp/aws","change":{"actions":["delete"],"before":{"arn":"arn:aws:ec2:us-west-1:145167242158:security-group/sg-0f3ac859864629452","description":"Managed by Terraform","egress":[{"cidr_blocks":[],"description":"","from_port":0,"ipv6_cidr_blocks":[],"prefix_list_ids":[],"protocol":"-1","security_groups":[],"self":false,"to_port":0}],"id":"sg-0f3ac859864629452","ingress":[],"name":"test_sg","name_prefix":"","owner_id":"145167242158","revoke_rules_on_delete":false,"tags":null,"tags_all":{},"timeouts":null,"vpc_id":"vpc-06165c55f5a70fdfa"},"after":null,"after_unknown":{},"before_sensitive":{"egress":[{"cidr_blocks":[],"ipv6_cidr_blocks":[],"prefix_list_ids":[],"security_groups":[]}],"ingress":[],"tags_all":{}},"after_sensitive":false}}],"resource_changes":[{"address":"aws_s3_bucket.this","mode":"managed","type":"aws_s3_bucket","name":"this","provider_name":"registry.opentofu.org/hashicorp/aws","change":{"actions":["create"],"before":null,"after":{"bucket":"tfsec-plan-testing","force_destroy":false,"tags":null,"timeouts":null},"after_unknown":{"acceleration_status":true,"acl":true,"arn":true,"bucket_domain_name":true,"bucket_prefix":true,"bucket_regional_domain_name":true,"cors_rule":true,"grant":true,"hosted_zone_id":true,"id":true,"lifecycle_rule":true,"logging":true,"object_lock_configuration":true,"object_lock_enabled":true,"policy":true,"region":true,"replication_configuration":true,"request_payer":true,"server_side_encryption_configuration":true,"tags_all":true,"versioning":true,"website":true,"website_domain":true,"website_endpoint":true},"before_sensitive":false,"after_sensitive":{"cors_rule":[],"grant":[],"lifecycle_rule":[],"logging":[],"object_lock_configuration":[],"replication_configuration":[],"server_side_encryption_configuration":[],"tags_all":{},"versioning":[],"website":[]}}}],"configuration":{"provider_config":{"aws":{"name":"aws","full_name":"registry.opentofu.org/hashicorp/aws"}},"root_module":{"resources":[{"address":"aws_s3_bucket.this","mode":"managed","type":"aws_s3_bucket","name":"this","provider_config_key":"aws","expressions":{"bucket":{"constant_value":"tfsec-plan-testing"}},"schema_version":0}]}},"timestamp":"2024-08-28T04:27:38Z","errored":false} From 98e136eb7baa2b66f4233d96875c1490144e1594 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 29 Aug 2024 06:02:05 +0600 Subject: [PATCH 310/352] feat(misconf): port and protocol support for EC2 networks (#7146) Signed-off-by: nikpivkin --- .../cloudformation/aws/ec2/adapt_test.go | 23 ++++++++++--- .../adapters/cloudformation/aws/ec2/nacl.go | 15 +++++++- .../cloudformation/aws/ec2/security_group.go | 19 +++++++++++ pkg/iac/adapters/terraform/aws/ec2/subnet.go | 4 +-- .../adapters/terraform/aws/ec2/subnet_test.go | 2 +- pkg/iac/adapters/terraform/aws/ec2/vpc.go | 34 +++++++++---------- .../adapters/terraform/aws/ec2/vpc_test.go | 24 ++++++++++++- pkg/iac/providers/aws/ec2/vpc.go | 5 +++ .../parser/property_conversion.go | 8 ++++- 9 files changed, 106 insertions(+), 28 deletions(-) diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go b/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go index 01fb201768e9..b218b95a4e99 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go @@ -51,15 +51,15 @@ Resources: SecurityGroupIngress: - IpProtocol: tcp Description: ingress - FromPort: 80 + FromPort: "80" ToPort: 80 CidrIp: 0.0.0.0/0 SecurityGroupEgress: - - IpProtocol: tcp + - IpProtocol: -1 Description: egress FromPort: 80 - ToPort: 80 - CidrIp: 0.0.0.0/0 + ToPort: "80" + CidrIp: "0.0.0.0/0" myNetworkAcl: Type: AWS::EC2::NetworkAcl Properties: @@ -73,6 +73,9 @@ Resources: Protocol: 6 RuleAction: allow CidrBlock: 172.16.0.0/24 + PortRange: + From: 22 + To: "23" myLaunchConfig: Type: AWS::AutoScaling::LaunchConfiguration Properties: @@ -137,6 +140,9 @@ Resources: CIDRs: []types.StringValue{ types.StringTest("0.0.0.0/0"), }, + FromPort: types.IntTest(80), + ToPort: types.IntTest(80), + Protocol: types.StringTest("tcp"), }, }, EgressRules: []ec2.SecurityGroupRule{ @@ -145,6 +151,9 @@ Resources: CIDRs: []types.StringValue{ types.StringTest("0.0.0.0/0"), }, + FromPort: types.IntTest(80), + ToPort: types.IntTest(80), + Protocol: types.StringTest("-1"), }, }, }, @@ -159,6 +168,8 @@ Resources: CIDRs: []types.StringValue{ types.StringTest("172.16.0.0/24"), }, + FromPort: types.IntTest(22), + ToPort: types.IntTest(23), }, }, }, @@ -309,6 +320,8 @@ Resources: CIDRs: []types.StringValue{ types.StringTest("0.0.0.0/0"), }, + FromPort: types.IntTest(-1), + ToPort: types.IntTest(-1), }, }, EgressRules: []ec2.SecurityGroupRule{ @@ -317,6 +330,8 @@ Resources: CIDRs: []types.StringValue{ types.StringTest("0.0.0.0/0"), }, + FromPort: types.IntTest(-1), + ToPort: types.IntTest(-1), }, }, }, diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/nacl.go b/pkg/iac/adapters/cloudformation/aws/ec2/nacl.go index 0f908f78ae08..a91a12569994 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/nacl.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/nacl.go @@ -4,6 +4,7 @@ import ( "strconv" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) @@ -29,7 +30,8 @@ func getRules(id string, ctx parser.FileContext) (rules []ec2.NetworkACLRule) { Metadata: ruleResource.Metadata(), Type: iacTypes.StringDefault(ec2.TypeIngress, ruleResource.Metadata()), Action: iacTypes.StringDefault(ec2.ActionAllow, ruleResource.Metadata()), - Protocol: iacTypes.String("-1", ruleResource.Metadata()), + FromPort: iacTypes.IntDefault(-1, ruleResource.Metadata()), + ToPort: iacTypes.IntDefault(-1, ruleResource.Metadata()), CIDRs: nil, } @@ -62,6 +64,17 @@ func getRules(id string, ctx parser.FileContext) (rules []ec2.NetworkACLRule) { rule.CIDRs = append(rule.CIDRs, ipv6Cidr.AsStringValue()) } + portRange := ruleResource.GetProperty("PortRange") + fromPort := portRange.GetProperty("From").ConvertTo(cftypes.Int) + if fromPort.IsInt() { + rule.FromPort = fromPort.AsIntValue() + } + + toPort := portRange.GetProperty("To").ConvertTo(cftypes.Int) + if toPort.IsInt() { + rule.ToPort = toPort.AsIntValue() + } + rules = append(rules, rule) } } diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go index 6770062affb2..e85a91cdecb6 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go @@ -4,6 +4,7 @@ import ( "github.com/samber/lo" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) @@ -75,7 +76,10 @@ func adaptRule(r interface { rule := ec2.SecurityGroupRule{ Metadata: r.Metadata(), Description: r.GetStringProperty("Description"), + FromPort: types.IntDefault(-1, r.Metadata()), + ToPort: types.IntDefault(-1, r.Metadata()), } + v4Cidr := r.GetProperty("CidrIp") if v4Cidr.IsString() && v4Cidr.AsStringValue().IsNotEmpty() { rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v4Cidr.AsString(), v4Cidr.Metadata())) @@ -85,5 +89,20 @@ func adaptRule(r interface { rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v6Cidr.AsString(), v6Cidr.Metadata())) } + fromPort := r.GetProperty("FromPort").ConvertTo(cftypes.Int) + if fromPort.IsInt() { + rule.FromPort = fromPort.AsIntValue() + } + + toPort := r.GetProperty("ToPort").ConvertTo(cftypes.Int) + if toPort.IsInt() { + rule.ToPort = toPort.AsIntValue() + } + + protocol := r.GetProperty("IpProtocol").ConvertTo(cftypes.String) + if protocol.IsString() { + rule.Protocol = protocol.AsStringValue() + } + return rule } diff --git a/pkg/iac/adapters/terraform/aws/ec2/subnet.go b/pkg/iac/adapters/terraform/aws/ec2/subnet.go index 4e4e49c4f59e..41a68eb2832c 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/subnet.go +++ b/pkg/iac/adapters/terraform/aws/ec2/subnet.go @@ -9,13 +9,13 @@ func adaptSubnets(modules terraform.Modules) []ec2.Subnet { var subnets []ec2.Subnet for _, module := range modules { for _, resource := range module.GetResourcesByType("aws_subnet") { - subnets = append(subnets, adaptSubnet(resource, module)) + subnets = append(subnets, adaptSubnet(resource)) } } return subnets } -func adaptSubnet(resource *terraform.Block, module *terraform.Module) ec2.Subnet { +func adaptSubnet(resource *terraform.Block) ec2.Subnet { mapPublicIpOnLaunchAttr := resource.GetAttribute("map_public_ip_on_launch") mapPublicIpOnLaunchVal := mapPublicIpOnLaunchAttr.AsBoolValueOrDefault(false, resource) diff --git a/pkg/iac/adapters/terraform/aws/ec2/subnet_test.go b/pkg/iac/adapters/terraform/aws/ec2/subnet_test.go index c371ef4f504f..33a0a642245a 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/subnet_test.go +++ b/pkg/iac/adapters/terraform/aws/ec2/subnet_test.go @@ -61,7 +61,7 @@ func Test_adaptSubnet(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") - adapted := adaptSubnet(modules.GetBlocks()[0], modules[0]) + adapted := adaptSubnet(modules.GetBlocks()[0]) testutil.AssertDefsecEqual(t, test.expected, adapted) }) } diff --git a/pkg/iac/adapters/terraform/aws/ec2/vpc.go b/pkg/iac/adapters/terraform/aws/ec2/vpc.go index 92ee730e0947..6be6cbaf672d 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/vpc.go +++ b/pkg/iac/adapters/terraform/aws/ec2/vpc.go @@ -72,9 +72,9 @@ func (a *sgAdapter) adaptSecurityGroups(modules terraform.Modules) []ec2.Securit } for _, sgRule := range orphanResources { if sgRule.GetAttribute("type").Equals("ingress") { - orphanage.IngressRules = append(orphanage.IngressRules, adaptSGRule(sgRule, modules)) + orphanage.IngressRules = append(orphanage.IngressRules, adaptSGRule(sgRule)) } else if sgRule.GetAttribute("type").Equals("egress") { - orphanage.EgressRules = append(orphanage.EgressRules, adaptSGRule(sgRule, modules)) + orphanage.EgressRules = append(orphanage.EgressRules, adaptSGRule(sgRule)) } } securityGroups = append(securityGroups, orphanage) @@ -116,21 +116,21 @@ func (a *sgAdapter) adaptSecurityGroup(resource *terraform.Block, module terrafo ingressBlocks := resource.GetBlocks("ingress") for _, ingressBlock := range ingressBlocks { - ingressRules = append(ingressRules, adaptSGRule(ingressBlock, module)) + ingressRules = append(ingressRules, adaptSGRule(ingressBlock)) } egressBlocks := resource.GetBlocks("egress") for _, egressBlock := range egressBlocks { - egressRules = append(egressRules, adaptSGRule(egressBlock, module)) + egressRules = append(egressRules, adaptSGRule(egressBlock)) } rulesBlocks := module.GetReferencingResources(resource, "aws_security_group_rule", "security_group_id") for _, ruleBlock := range rulesBlocks { a.sgRuleIDs.Resolve(ruleBlock.ID()) if ruleBlock.GetAttribute("type").Equals("ingress") { - ingressRules = append(ingressRules, adaptSGRule(ruleBlock, module)) + ingressRules = append(ingressRules, adaptSGRule(ruleBlock)) } else if ruleBlock.GetAttribute("type").Equals("egress") { - egressRules = append(egressRules, adaptSGRule(ruleBlock, module)) + egressRules = append(egressRules, adaptSGRule(ruleBlock)) } } @@ -154,7 +154,7 @@ func (a *sgAdapter) adaptSecurityGroup(resource *terraform.Block, module terrafo } } -func adaptSGRule(resource *terraform.Block, modules terraform.Modules) ec2.SecurityGroupRule { +func adaptSGRule(resource *terraform.Block) ec2.SecurityGroupRule { ruleDescAttr := resource.GetAttribute("description") ruleDescVal := ruleDescAttr.AsStringValueOrDefault("", resource) @@ -162,16 +162,6 @@ func adaptSGRule(resource *terraform.Block, modules terraform.Modules) ec2.Secur cidrBlocks := resource.GetAttribute("cidr_blocks") ipv6cidrBlocks := resource.GetAttribute("ipv6_cidr_blocks") - varBlocks := modules.GetBlocks().OfType("variable") - - for _, vb := range varBlocks { - if cidrBlocks.IsNotNil() && cidrBlocks.ReferencesBlock(vb) { - cidrBlocks = vb.GetAttribute("default") - } - if ipv6cidrBlocks.IsNotNil() && ipv6cidrBlocks.ReferencesBlock(vb) { - ipv6cidrBlocks = vb.GetAttribute("default") - } - } if cidrBlocks.IsNotNil() { cidrs = cidrBlocks.AsStringValues() @@ -185,6 +175,9 @@ func adaptSGRule(resource *terraform.Block, modules terraform.Modules) ec2.Secur Metadata: resource.GetMetadata(), Description: ruleDescVal, CIDRs: cidrs, + FromPort: resource.GetAttribute("from_port").AsIntValueOrDefault(-1, resource), + ToPort: resource.GetAttribute("to_port").AsIntValueOrDefault(-1, resource), + Protocol: resource.GetAttribute("protocol").AsStringValueOrDefault("", resource), } } @@ -203,6 +196,9 @@ func adaptSingleSGRule(resource *terraform.Block) ec2.SecurityGroupRule { Metadata: resource.GetMetadata(), Description: description, CIDRs: cidrs, + FromPort: resource.GetAttribute("from_port").AsIntValueOrDefault(-1, resource), + ToPort: resource.GetAttribute("to_port").AsIntValueOrDefault(-1, resource), + Protocol: resource.GetAttribute("ip_protocol").AsStringValueOrDefault("", resource), } } @@ -236,7 +232,7 @@ func adaptNetworkACLRule(resource *terraform.Block) ec2.NetworkACLRule { actionVal := actionAttr.AsStringValueOrDefault("", resource) protocolAtrr := resource.GetAttribute("protocol") - protocolVal := protocolAtrr.AsStringValueOrDefault("-1", resource) + protocolVal := protocolAtrr.AsStringValueOrDefault("", resource) cidrAttr := resource.GetAttribute("cidr_block") if cidrAttr.IsNotNil() { @@ -253,5 +249,7 @@ func adaptNetworkACLRule(resource *terraform.Block) ec2.NetworkACLRule { Action: actionVal, Protocol: protocolVal, CIDRs: cidrs, + FromPort: resource.GetAttribute("from_port").AsIntValueOrDefault(-1, resource), + ToPort: resource.GetAttribute("to_port").AsIntValueOrDefault(-1, resource), } } diff --git a/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go b/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go index 611f8ca8527d..1acee8146d3e 100644 --- a/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go +++ b/pkg/iac/adapters/terraform/aws/ec2/vpc_test.go @@ -102,6 +102,9 @@ func Test_AdaptVPC(t *testing.T) { CIDRs: []iacTypes.StringValue{ iacTypes.String("4.5.6.7/32", iacTypes.NewTestMetadata()), }, + FromPort: iacTypes.IntTest(80), + ToPort: iacTypes.IntTest(80), + Protocol: iacTypes.StringTest("tcp"), }, { Metadata: iacTypes.NewTestMetadata(), @@ -111,6 +114,9 @@ func Test_AdaptVPC(t *testing.T) { iacTypes.String("1.2.3.4/32", iacTypes.NewTestMetadata()), iacTypes.String("4.5.6.7/32", iacTypes.NewTestMetadata()), }, + FromPort: iacTypes.IntTest(22), + ToPort: iacTypes.IntTest(22), + Protocol: iacTypes.StringTest("tcp"), }, }, @@ -121,6 +127,8 @@ func Test_AdaptVPC(t *testing.T) { CIDRs: []iacTypes.StringValue{ iacTypes.String("1.2.3.4/32", iacTypes.NewTestMetadata()), }, + FromPort: iacTypes.IntTest(-1), + ToPort: iacTypes.IntTest(-1), }, }, }, @@ -137,6 +145,8 @@ func Test_AdaptVPC(t *testing.T) { CIDRs: []iacTypes.StringValue{ iacTypes.String("10.0.0.0/16", iacTypes.NewTestMetadata()), }, + FromPort: iacTypes.IntTest(22), + ToPort: iacTypes.IntTest(22), }, }, IsDefaultRule: iacTypes.Bool(false, iacTypes.NewTestMetadata()), @@ -169,6 +179,8 @@ func Test_AdaptVPC(t *testing.T) { { Metadata: iacTypes.NewTestMetadata(), Description: iacTypes.String("", iacTypes.NewTestMetadata()), + FromPort: iacTypes.IntTest(-1), + ToPort: iacTypes.IntTest(-1), }, }, @@ -176,6 +188,8 @@ func Test_AdaptVPC(t *testing.T) { { Metadata: iacTypes.NewTestMetadata(), Description: iacTypes.String("", iacTypes.NewTestMetadata()), + FromPort: iacTypes.IntTest(-1), + ToPort: iacTypes.IntTest(-1), }, }, }, @@ -188,7 +202,9 @@ func Test_AdaptVPC(t *testing.T) { Metadata: iacTypes.NewTestMetadata(), Type: iacTypes.String("ingress", iacTypes.NewTestMetadata()), Action: iacTypes.String("", iacTypes.NewTestMetadata()), - Protocol: iacTypes.String("-1", iacTypes.NewTestMetadata()), + Protocol: iacTypes.String("", iacTypes.NewTestMetadata()), + FromPort: iacTypes.IntTest(-1), + ToPort: iacTypes.IntTest(-1), }, }, IsDefaultRule: iacTypes.Bool(false, iacTypes.NewTestMetadata()), @@ -252,6 +268,9 @@ resource "aws_vpc_security_group_ingress_rule" "test" { CIDRs: []iacTypes.StringValue{ iacTypes.StringTest("0.0.0.0/0"), }, + Protocol: iacTypes.StringTest("tcp"), + FromPort: iacTypes.IntTest(22), + ToPort: iacTypes.IntTest(22), }, }, EgressRules: []ec2.SecurityGroupRule{ @@ -259,6 +278,9 @@ resource "aws_vpc_security_group_ingress_rule" "test" { CIDRs: []iacTypes.StringValue{ iacTypes.StringTest("0.0.0.0/0"), }, + Protocol: iacTypes.StringTest("-1"), + FromPort: iacTypes.IntTest(-1), + ToPort: iacTypes.IntTest(-1), }, }, }, diff --git a/pkg/iac/providers/aws/ec2/vpc.go b/pkg/iac/providers/aws/ec2/vpc.go index bce7fb4de2a8..d6bff4fe7025 100644 --- a/pkg/iac/providers/aws/ec2/vpc.go +++ b/pkg/iac/providers/aws/ec2/vpc.go @@ -23,6 +23,9 @@ type SecurityGroupRule struct { Metadata iacTypes.Metadata Description iacTypes.StringValue CIDRs []iacTypes.StringValue + Protocol iacTypes.StringValue + FromPort iacTypes.IntValue + ToPort iacTypes.IntValue } type VPC struct { @@ -49,4 +52,6 @@ type NetworkACLRule struct { Action iacTypes.StringValue Protocol iacTypes.StringValue CIDRs []iacTypes.StringValue + FromPort iacTypes.IntValue + ToPort iacTypes.IntValue } diff --git a/pkg/iac/scanners/cloudformation/parser/property_conversion.go b/pkg/iac/scanners/cloudformation/parser/property_conversion.go index d286fa4dd797..35847bb35c5b 100644 --- a/pkg/iac/scanners/cloudformation/parser/property_conversion.go +++ b/pkg/iac/scanners/cloudformation/parser/property_conversion.go @@ -43,6 +43,8 @@ func (p *Property) isConvertableToBool() bool { case cftypes.Int: return p.EqualTo(1) || p.EqualTo(0) + case cftypes.Bool: + return true } return false } @@ -53,7 +55,7 @@ func (p *Property) isConvertableToInt() bool { if _, err := strconv.Atoi(p.AsString()); err == nil { return true } - case cftypes.Bool: + case cftypes.Bool, cftypes.Int: return true } return false @@ -61,6 +63,10 @@ func (p *Property) isConvertableToInt() bool { func (p *Property) ConvertTo(conversionType cftypes.CfType) *Property { + if p.Type() == conversionType { + return p + } + if !p.IsConvertableTo(conversionType) { _, _ = fmt.Fprintf(os.Stderr, "property of type %s cannot be converted to %s\n", p.Type(), conversionType) return p From 344dafd253b2a3ae2695e62525ed9cf8545dc967 Mon Sep 17 00:00:00 2001 From: aasish-r Date: Thu, 29 Aug 2024 15:32:40 +0530 Subject: [PATCH 311/352] chore: fix allow rule of ignoring test files to make it case insensitive (#7415) --- pkg/fanal/secret/builtin-allow-rules.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fanal/secret/builtin-allow-rules.go b/pkg/fanal/secret/builtin-allow-rules.go index 5e02f3241a04..634037fbceec 100644 --- a/pkg/fanal/secret/builtin-allow-rules.go +++ b/pkg/fanal/secret/builtin-allow-rules.go @@ -4,7 +4,7 @@ var builtinAllowRules = []AllowRule{ { ID: "tests", Description: "Avoid test files and paths", - Path: MustCompile(`(^test|\/test|-test|_test|\.test)`), + Path: MustCompile(`(^(?i)test|\/test|-test|_test|\.test)`), }, { ID: "examples", From 391448aba9fcb0a4138225e5ab305e4e6707c603 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:06:05 +0600 Subject: [PATCH 312/352] fix(secret): use only line with secret for long secret lines (#7412) --- pkg/fanal/secret/scanner.go | 4 +-- pkg/fanal/secret/scanner_test.go | 45 ++++++++++++++++++++++++ pkg/fanal/secret/testdata/jwt-secret.txt | 4 +++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 pkg/fanal/secret/testdata/jwt-secret.txt diff --git a/pkg/fanal/secret/scanner.go b/pkg/fanal/secret/scanner.go index b8591a69228c..98a01f08323e 100644 --- a/pkg/fanal/secret/scanner.go +++ b/pkg/fanal/secret/scanner.go @@ -504,8 +504,8 @@ func findLocation(start, end int, content []byte) (int, int, types.Code, string) } if lineEnd-lineStart > 100 { - lineStart = lo.Ternary(start-30 < 0, 0, start-30) - lineEnd = lo.Ternary(end+20 > len(content), len(content), end+20) + lineStart = lo.Ternary(start-lineStart-30 < 0, lineStart, start-30) + lineEnd = lo.Ternary(end+20 > lineEnd, lineEnd, end+20) } matchLine := string(content[lineStart:lineEnd]) endLineNum := startLineNum + bytes.Count(content[start:end], lineSep) diff --git a/pkg/fanal/secret/scanner_test.go b/pkg/fanal/secret/scanner_test.go index 86d813f4785e..44b5a2458e79 100644 --- a/pkg/fanal/secret/scanner_test.go +++ b/pkg/fanal/secret/scanner_test.go @@ -742,6 +742,42 @@ func TestSecretScanner(t *testing.T) { }, }, } + wantFindingJWT := types.SecretFinding{ + RuleID: "jwt-token", + Category: "JWT", + Title: "JWT token", + Severity: "MEDIUM", + StartLine: 3, + EndLine: 3, + Match: "jwt: ***********************************************************************************************************************************************************", + Code: types.Code{ + Lines: []types.Line{ + { + Number: 1, + Content: "asd", + Highlighted: "asd", + }, + { + Number: 2, + Content: "aaaa", + Highlighted: "aaaa", + }, + { + Number: 3, + Content: "jwt: ***********************************************************************************************************************************************************", + Highlighted: "jwt: ***********************************************************************************************************************************************************", + IsCause: true, + FirstCause: true, + LastCause: true, + }, + { + Number: 4, + Content: "asda", + Highlighted: "asda", + }, + }, + }, + } tests := []struct { name string @@ -822,6 +858,15 @@ func TestSecretScanner(t *testing.T) { Findings: []types.SecretFinding{wantFindingHuggingFace}, }, }, + { + name: "find JWT token", + configPath: filepath.Join("testdata", "config.yaml"), + inputFilePath: filepath.Join("testdata", "jwt-secret.txt"), + want: types.Secret{ + FilePath: filepath.Join("testdata", "jwt-secret.txt"), + Findings: []types.SecretFinding{wantFindingJWT}, + }, + }, { name: "include when keyword found", configPath: filepath.Join("testdata", "config-happy-keywords.yaml"), diff --git a/pkg/fanal/secret/testdata/jwt-secret.txt b/pkg/fanal/secret/testdata/jwt-secret.txt new file mode 100644 index 000000000000..b23e942b3d98 --- /dev/null +++ b/pkg/fanal/secret/testdata/jwt-secret.txt @@ -0,0 +1,4 @@ +asd +aaaa +jwt: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +asda \ No newline at end of file From 84118d0f3dab190009e9e452519a679702898367 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 29 Aug 2024 14:09:08 +0400 Subject: [PATCH 313/352] chore: update CODEOWNERS (#7398) Signed-off-by: knqyf263 --- .github/CODEOWNERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 46f4b5c85c25..9866e518d80f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,8 +15,8 @@ pkg/cloud/ @simar7 @nikpivkin pkg/iac/ @simar7 @nikpivkin # Helm chart -helm/trivy/ @chen-keinan +helm/trivy/ @afdesk # Kubernetes scanning -pkg/k8s/ @chen-keinan -docs/docs/target/kubernetes.md @chen-keinan +pkg/k8s/ @afdesk +docs/docs/target/kubernetes.md @afdesk From 4c6e8ca9cc9591799907cc73075f2d740e303b8f Mon Sep 17 00:00:00 2001 From: Ori <59772293+orizerah@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:59:54 +0530 Subject: [PATCH 314/352] feat(server): Make Trivy Server Multiplexer Exported (#7389) --- pkg/rpc/server/listen.go | 4 ++-- pkg/rpc/server/listen_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/rpc/server/listen.go b/pkg/rpc/server/listen.go index 2b6d1288b7ff..f1e39fa1b2f0 100644 --- a/pkg/rpc/server/listen.go +++ b/pkg/rpc/server/listen.go @@ -72,13 +72,13 @@ func (s Server) ListenAndServe(ctx context.Context, serverCache cache.Cache, ski } }() - mux := s.newServeMux(ctx, serverCache, dbUpdateWg, requestWg) + mux := s.NewServeMux(ctx, serverCache, dbUpdateWg, requestWg) log.Infof("Listening %s...", s.addr) return http.ListenAndServe(s.addr, mux) } -func (s Server) newServeMux(ctx context.Context, serverCache cache.Cache, dbUpdateWg, requestWg *sync.WaitGroup) *http.ServeMux { +func (s Server) NewServeMux(ctx context.Context, serverCache cache.Cache, dbUpdateWg, requestWg *sync.WaitGroup) *http.ServeMux { withWaitGroup := func(base http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Stop processing requests during DB update diff --git a/pkg/rpc/server/listen_test.go b/pkg/rpc/server/listen_test.go index fa61e228806c..7f97bdbcb31e 100644 --- a/pkg/rpc/server/listen_test.go +++ b/pkg/rpc/server/listen_test.go @@ -183,7 +183,7 @@ func TestServer_newServeMux(t *testing.T) { defer func() { _ = c.Close() }() s := NewServer("", "", "", tt.args.token, tt.args.tokenHeader, "", nil, ftypes.RegistryOptions{}) - ts := httptest.NewServer(s.newServeMux(context.Background(), c, dbUpdateWg, requestWg)) + ts := httptest.NewServer(s.NewServeMux(context.Background(), c, dbUpdateWg, requestWg)) defer ts.Close() var resp *http.Response @@ -214,7 +214,7 @@ func Test_VersionEndpoint(t *testing.T) { defer func() { _ = c.Close() }() s := NewServer("", "", "testdata/testcache", "", "", "", nil, ftypes.RegistryOptions{}) - ts := httptest.NewServer(s.newServeMux(context.Background(), c, dbUpdateWg, requestWg)) + ts := httptest.NewServer(s.NewServeMux(context.Background(), c, dbUpdateWg, requestWg)) defer ts.Close() resp, err := http.Get(ts.URL + "/version") From 7aea79dd93cfb61453766dbbb2e3fc0fbd317852 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Thu, 29 Aug 2024 14:35:04 +0400 Subject: [PATCH 315/352] feat(report): export modified findings in JSON (#7383) Signed-off-by: knqyf263 --- docs/docs/configuration/filtering.md | 2 +- pkg/report/json.go | 11 +++++++++-- pkg/report/writer.go | 5 +++-- pkg/types/report.go | 4 ++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/docs/configuration/filtering.md b/docs/docs/configuration/filtering.md index 030813bd1bce..befcc54180ac 100644 --- a/docs/docs/configuration/filtering.md +++ b/docs/docs/configuration/filtering.md @@ -238,7 +238,7 @@ You can filter the results by To show the suppressed results, use the `--show-suppressed` flag. !!! note - This flag is currently available only in the table format. + It's exported as `ExperimentalModifiedFindings` in the JSON output. ```bash $ trivy image --vex debian11.csaf.vex --ignorefile .trivyignore.yaml --show-suppressed debian:11 diff --git a/pkg/report/json.go b/pkg/report/json.go index 8529b5274560..e51d524d6509 100644 --- a/pkg/report/json.go +++ b/pkg/report/json.go @@ -14,8 +14,9 @@ import ( // JSONWriter implements result Writer type JSONWriter struct { - Output io.Writer - ListAllPkgs bool + Output io.Writer + ListAllPkgs bool + ShowSuppressed bool } // Write writes the results in JSON format @@ -26,6 +27,12 @@ func (jw JSONWriter) Write(_ context.Context, report types.Report) error { report.Results[i].Packages = nil } } + if !jw.ShowSuppressed { + // Delete suppressed findings + for i := range report.Results { + report.Results[i].ModifiedFindings = nil + } + } report.Results = lo.Filter(report.Results, func(r types.Result, _ int) bool { return r.Target != "" || !r.IsEmpty() }) diff --git a/pkg/report/writer.go b/pkg/report/writer.go index 041c9acdfe8e..a5b7ae231599 100644 --- a/pkg/report/writer.go +++ b/pkg/report/writer.go @@ -56,8 +56,9 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e } case types.FormatJSON: writer = &JSONWriter{ - Output: output, - ListAllPkgs: option.ListAllPkgs, + Output: output, + ListAllPkgs: option.ListAllPkgs, + ShowSuppressed: option.ShowSuppressed, } case types.FormatGitHub: writer = &github.Writer{ diff --git a/pkg/types/report.go b/pkg/types/report.go index 6937f8ce7960..af364c95a11d 100644 --- a/pkg/types/report.go +++ b/pkg/types/report.go @@ -120,8 +120,8 @@ type Result struct { // ModifiedFindings holds a list of findings that have been modified from their original state. // This can include vulnerabilities that have been marked as ignored, not affected, or have had - // their severity adjusted. It is currently available only in the table format. - ModifiedFindings []ModifiedFinding `json:"-"` + // their severity adjusted. It's still in an experimental stage and may change in the future. + ModifiedFindings []ModifiedFinding `json:"ExperimentalModifiedFindings,omitempty"` } func (r *Result) IsEmpty() bool { From c96dcdd440a14cdd1b01ac473b2c15e4698e387b Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:35:48 +0600 Subject: [PATCH 316/352] fix(sbom): use `NOASSERTION` for licenses fields in SPDX formats (#7403) --- integration/testdata/julia-spdx.json.golden | 12 +++---- pkg/sbom/spdx/marshal.go | 3 +- pkg/sbom/spdx/marshal_test.go | 40 ++++++++++----------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/integration/testdata/julia-spdx.json.golden b/integration/testdata/julia-spdx.json.golden index 8ae4ead23a7d..89a4edb3f287 100644 --- a/integration/testdata/julia-spdx.json.golden +++ b/integration/testdata/julia-spdx.json.golden @@ -31,8 +31,8 @@ "downloadLocation": "NONE", "filesAnalyzed": false, "sourceInfo": "package found in: Manifest.toml", - "licenseConcluded": "NONE", - "licenseDeclared": "NONE", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", "externalRefs": [ { "referenceCategory": "PACKAGE-MANAGER", @@ -54,8 +54,8 @@ "downloadLocation": "NONE", "filesAnalyzed": false, "sourceInfo": "package found in: Manifest.toml", - "licenseConcluded": "NONE", - "licenseDeclared": "NONE", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", "externalRefs": [ { "referenceCategory": "PACKAGE-MANAGER", @@ -77,8 +77,8 @@ "downloadLocation": "NONE", "filesAnalyzed": false, "sourceInfo": "package found in: Manifest.toml", - "licenseConcluded": "NONE", - "licenseDeclared": "NONE", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", "externalRefs": [ { "referenceCategory": "PACKAGE-MANAGER", diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index 35bd0448aa5d..33952ab41441 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -33,6 +33,7 @@ const ( CreatorOrganization = "aquasecurity" CreatorTool = "trivy" noneField = "NONE" + noAssertionField = "NOASSERTION" ) const ( @@ -378,7 +379,7 @@ func (m *Marshaler) spdxAttributionTexts(c *core.Component) []string { func (m *Marshaler) spdxLicense(c *core.Component) string { if len(c.Licenses) == 0 { - return noneField + return noAssertionField } return NormalizeLicense(c.Licenses) } diff --git a/pkg/sbom/spdx/marshal_test.go b/pkg/sbom/spdx/marshal_test.go index 3cd034803ffc..ef64cf4651d5 100644 --- a/pkg/sbom/spdx/marshal_test.go +++ b/pkg/sbom/spdx/marshal_test.go @@ -217,8 +217,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "actioncontroller", PackageVersion: "7.0.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageAttributionTexts: []string{ "PkgType: bundler", }, @@ -238,8 +238,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "actionpack", PackageVersion: "7.0.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageAttributionTexts: []string{ "PkgType: bundler", }, @@ -259,8 +259,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "actionpack", PackageVersion: "7.0.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageAttributionTexts: []string{ "PkgType: bundler", }, @@ -536,8 +536,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "actionpack", PackageVersion: "7.0.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: tspdx.CategoryPackageManager, @@ -561,8 +561,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "actionpack", PackageVersion: "7.0.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: tspdx.CategoryPackageManager, @@ -750,8 +750,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "actioncable", PackageVersion: "6.1.4.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: tspdx.CategoryPackageManager, @@ -771,8 +771,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "com.example:example", PackageVersion: "1.0.0", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: tspdx.CategoryPackageManager, @@ -889,8 +889,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "org.apache.logging.log4j:log4j-core", PackageVersion: "2.17.0", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: tspdx.CategoryPackageManager, @@ -1229,8 +1229,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageSPDXIdentifier: spdx.ElementID("Package-b1c3b9e2363f5ff7"), PackageDownloadLocation: "NONE", PackageName: "./private_repos/cnrm.googlesource.com/cnrm/", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, PackageSourceInfo: "package found in: /usr/local/bin/test", @@ -1243,8 +1243,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "golang.org/x/crypto", PackageVersion: "v0.0.1", - PackageLicenseConcluded: "NONE", - PackageLicenseDeclared: "NONE", + PackageLicenseConcluded: "NOASSERTION", + PackageLicenseDeclared: "NOASSERTION", PackageExternalReferences: []*spdx.PackageExternalReference{ { Category: tspdx.CategoryPackageManager, From a5aa63eff7e229744090f9ad300c1bec3259397e Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 29 Aug 2024 20:34:33 +0600 Subject: [PATCH 317/352] fix(misconf): do not register Rego libs in checks registry (#7420) Signed-off-by: nikpivkin --- pkg/iac/rego/embed.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/iac/rego/embed.go b/pkg/iac/rego/embed.go index 082870071c90..fddc069d9283 100644 --- a/pkg/iac/rego/embed.go +++ b/pkg/iac/rego/embed.go @@ -58,8 +58,10 @@ func RegisterRegoRules(modules map[string]*ast.Module) { continue } - if !metadata.Library && metadata.AVDID == "" { - log.Warn("Check ID is empty", log.FilePath(module.Package.Location.File)) + if metadata.AVDID == "" { + if !metadata.Library { + log.Warn("Check ID is empty", log.FilePath(module.Package.Location.File)) + } continue } From 39c80248bcafd296e0bb6712113da3abe70e4ce2 Mon Sep 17 00:00:00 2001 From: simar7 <1254783+simar7@users.noreply.github.com> Date: Fri, 30 Aug 2024 00:17:54 -0600 Subject: [PATCH 318/352] chore(deps): Bump trivy-checks (#7417) Signed-off-by: nikpivkin Co-authored-by: nikpivkin --- go.mod | 2 +- go.sum | 4 +- pkg/fanal/artifact/local/fs_test.go | 112 ++++-------------- pkg/iac/rego/load.go | 5 + pkg/iac/rego/metadata.go | 60 +++++++--- pkg/iac/rego/metadata_test.go | 16 +-- .../scanners/cloudformation/scanner_test.go | 4 +- pkg/iac/scanners/dockerfile/scanner_test.go | 27 ++--- pkg/iac/scanners/json/scanner_test.go | 4 +- pkg/iac/scanners/kubernetes/scanner_test.go | 8 +- .../terraformplan/tfjson/test/scanner_test.go | 2 +- pkg/iac/scanners/toml/scanner_test.go | 4 +- pkg/iac/scanners/yaml/scanner_test.go | 4 +- 13 files changed, 112 insertions(+), 140 deletions(-) diff --git a/go.mod b/go.mod index 230470d8f12c..844135ff79f8 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/aquasecurity/table v1.8.0 github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 github.com/aquasecurity/tml v0.6.1 - github.com/aquasecurity/trivy-checks v0.13.1-0.20240809030752-558fcff75807 + github.com/aquasecurity/trivy-checks v0.13.1-0.20240830035934-7761a83288cd github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b diff --git a/go.sum b/go.sum index 860b788ae605..e760459b4d51 100644 --- a/go.sum +++ b/go.sum @@ -348,8 +348,8 @@ github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 h1:b43UVqY github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8/go.mod h1:wXA9k3uuaxY3yu7gxrxZDPo/04FEMJtwyecdAlYrEIo= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= -github.com/aquasecurity/trivy-checks v0.13.1-0.20240809030752-558fcff75807 h1:yw2INXrbfekt1yHDQAlNZlHIUZQXMcSS+mWI9XWJUN0= -github.com/aquasecurity/trivy-checks v0.13.1-0.20240809030752-558fcff75807/go.mod h1:Xec/SMVGV66I7RgUqOX9MEr+YxBqHXDVLTYmpspPi3E= +github.com/aquasecurity/trivy-checks v0.13.1-0.20240830035934-7761a83288cd h1:/6sPLCU4JICPPYAmY2iUsLGpgYBXUH6M/0fy57AhNWY= +github.com/aquasecurity/trivy-checks v0.13.1-0.20240830035934-7761a83288cd/go.mod h1:zLBeXaTJkAvPZqKiRACAsP49ZywCEXFEjXMLa8kmc8Q= github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 h1:6/T8sFdNVG/AwOGoK6X55h7hF7LYqK8bsuPz8iEz8jM= github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04/go.mod h1:0T6oy2t1Iedt+yi3Ml5cpOYp5FZT4MI1/mx+3p+PIs8= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index ba6d2879bda2..dbef68e893bb 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -299,15 +299,6 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields: fields{ dir: "./testdata/misconfig/terraform/single-failure", }, - artifactOpt: artifact.Option{ - MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - DisableEmbeddedPolicies: true, - DisableEmbeddedLibraries: true, - }, - }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ BlobIDAnything: true, @@ -352,15 +343,6 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields: fields{ dir: "./testdata/misconfig/terraform/multiple-failures", }, - artifactOpt: artifact.Option{ - MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - DisableEmbeddedPolicies: true, - DisableEmbeddedLibraries: true, - }, - }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ BlobIDAnything: true, @@ -437,13 +419,6 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields: fields{ dir: "./testdata/misconfig/terraform/no-results", }, - artifactOpt: artifact.Option{ - MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - }, - }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ BlobIDAnything: true, @@ -467,15 +442,6 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields: fields{ dir: "./testdata/misconfig/terraform/passed", }, - artifactOpt: artifact.Option{ - MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - DisableEmbeddedPolicies: true, - DisableEmbeddedLibraries: true, - }, - }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ BlobIDAnything: true, @@ -516,15 +482,6 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields: fields{ dir: "./testdata/misconfig/terraform/busted-relative-paths/child/main.tf", }, - artifactOpt: artifact.Option{ - MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - DisableEmbeddedPolicies: true, - DisableEmbeddedLibraries: true, - }, - }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ BlobIDAnything: true, @@ -584,12 +541,7 @@ func TestTerraformMisconfigurationScan(t *testing.T) { }, artifactOpt: artifact.Option{ MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - TerraformTFVars: []string{"./testdata/misconfig/terraform/tfvar-outside/main.tfvars"}, - TfExcludeDownloaded: true, - DisableEmbeddedPolicies: true, + TerraformTFVars: []string{"./testdata/misconfig/terraform/tfvar-outside/main.tfvars"}, }, }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ @@ -632,14 +584,6 @@ func TestTerraformMisconfigurationScan(t *testing.T) { fields: fields{ dir: "./testdata/misconfig/terraform/relative-paths/child", }, - artifactOpt: artifact.Option{ - MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/terraform/rego"}, - DisableEmbeddedPolicies: true, - }, - }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ BlobIDAnything: true, @@ -726,6 +670,8 @@ func TestTerraformMisconfigurationScan(t *testing.T) { types.SystemFileFilteringPostHandler, } tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true + tt.artifactOpt.MisconfScannerOption.Namespaces = []string{"user"} + tt.artifactOpt.MisconfScannerOption.PolicyPaths = []string{"./testdata/misconfig/terraform/rego"} a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) require.NoError(t, err) @@ -972,9 +918,8 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { types.SystemFileFilteringPostHandler, }, MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - DisableEmbeddedPolicies: true, - + RegoOnly: true, + DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: false, Namespaces: []string{"user"}, PolicyPaths: []string{tmpDir}, @@ -983,7 +928,6 @@ func TestTerraformPlanSnapshotMisconfScan(t *testing.T) { SkipFiles: []string{"*.tf"}, }, } - a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), opt) require.NoError(t, err) @@ -1015,7 +959,6 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/cloudformation/single-failure/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1077,7 +1020,6 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/cloudformation/multiple-failures/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1161,7 +1103,6 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/cloudformation/no-results/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1194,7 +1135,6 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/cloudformation/params/code/rego"}, CloudFormationParamVars: []string{"./testdata/misconfig/cloudformation/params/cfparams.json"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1251,7 +1191,6 @@ func TestCloudFormationMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/cloudformation/passed/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1339,7 +1278,6 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/dockerfile/single-failure/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1397,7 +1335,6 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/dockerfile/multiple-failures/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1485,7 +1422,6 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/dockerfile/passed/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1543,6 +1479,7 @@ func TestDockerfileMisconfigurationScan(t *testing.T) { tt.artifactOpt.DisabledHandlers = []types.HandlerType{ types.SystemFileFilteringPostHandler, } + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) require.NoError(t, err) @@ -1574,7 +1511,6 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/kubernetes/single-failure/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1637,7 +1573,6 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/kubernetes/multiple-failures/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1753,7 +1688,6 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/kubernetes/passed/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -1811,6 +1745,7 @@ func TestKubernetesMisconfigurationScan(t *testing.T) { tt.artifactOpt.DisabledHandlers = []types.HandlerType{ types.SystemFileFilteringPostHandler, } + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) require.NoError(t, err) @@ -2068,6 +2003,7 @@ func TestAzureARMMisconfigurationScan(t *testing.T) { tt.artifactOpt.DisabledHandlers = []types.HandlerType{ types.SystemFileFilteringPostHandler, } + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) require.NoError(t, err) @@ -2099,7 +2035,6 @@ func TestMixedConfigurationScan(t *testing.T) { RegoOnly: true, Namespaces: []string{"user"}, PolicyPaths: []string{"./testdata/misconfig/mixed/rego"}, - DisableEmbeddedPolicies: true, DisableEmbeddedLibraries: true, }, }, @@ -2184,6 +2119,7 @@ func TestMixedConfigurationScan(t *testing.T) { tt.artifactOpt.DisabledHandlers = []types.HandlerType{ types.SystemFileFilteringPostHandler, } + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true a, err := NewArtifact(tt.fields.dir, c, walker.NewFS(), tt.artifactOpt) require.NoError(t, err) @@ -2217,10 +2153,9 @@ func TestJSONConfigScan(t *testing.T) { }, artifactOpt: artifact.Option{ MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/json/passed/checks"}, - DisableEmbeddedPolicies: true, + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/json/passed/checks"}, }, }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ @@ -2291,10 +2226,9 @@ func TestJSONConfigScan(t *testing.T) { }, artifactOpt: artifact.Option{ MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/json/with-schema/checks"}, - DisableEmbeddedPolicies: true, + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/json/with-schema/checks"}, }, }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ @@ -2342,6 +2276,7 @@ func TestJSONConfigScan(t *testing.T) { c := new(cache.MockArtifactCache) c.ApplyPutBlobExpectation(tt.putBlobExpectation) + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true if len(tt.fields.schemas) > 0 { schemas, err := misconf.LoadConfigSchemas(tt.fields.schemas) require.NoError(t, err) @@ -2381,10 +2316,9 @@ func TestYAMLConfigScan(t *testing.T) { }, artifactOpt: artifact.Option{ MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/yaml/passed/checks"}, - DisableEmbeddedPolicies: true, + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/yaml/passed/checks"}, }, }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ @@ -2455,10 +2389,9 @@ func TestYAMLConfigScan(t *testing.T) { }, artifactOpt: artifact.Option{ MisconfScannerOption: misconf.ScannerOption{ - RegoOnly: true, - Namespaces: []string{"user"}, - PolicyPaths: []string{"./testdata/misconfig/yaml/with-schema/checks"}, - DisableEmbeddedPolicies: true, + RegoOnly: true, + Namespaces: []string{"user"}, + PolicyPaths: []string{"./testdata/misconfig/yaml/with-schema/checks"}, }, }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ @@ -2506,6 +2439,7 @@ func TestYAMLConfigScan(t *testing.T) { c := new(cache.MockArtifactCache) c.ApplyPutBlobExpectation(tt.putBlobExpectation) + tt.artifactOpt.MisconfScannerOption.DisableEmbeddedPolicies = true if len(tt.fields.schemas) > 0 { schemas, err := misconf.LoadConfigSchemas(tt.fields.schemas) require.NoError(t, err) diff --git a/pkg/iac/rego/load.go b/pkg/iac/rego/load.go index 249e55992c2c..26a6c9e6f2d1 100644 --- a/pkg/iac/rego/load.go +++ b/pkg/iac/rego/load.go @@ -290,6 +290,11 @@ func (s *Scanner) filterModules(retriever *MetadataRetriever) error { if err != nil { return err } + + if !meta.hasAnyFramework(s.frameworks) { + continue + } + if len(meta.InputOptions.Selectors) == 0 { s.logger.Warn( "Module has no input selectors - it will be loaded for all inputs!", diff --git a/pkg/iac/rego/metadata.go b/pkg/iac/rego/metadata.go index 57602ce97b8c..cb1d38724e8a 100644 --- a/pkg/iac/rego/metadata.go +++ b/pkg/iac/rego/metadata.go @@ -49,11 +49,13 @@ func NewStaticMetadata(pkgPath string, inputOpt InputOptions) *StaticMetadata { Description: fmt.Sprintf("Rego module: %s", pkgPath), Package: pkgPath, InputOptions: inputOpt, - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, } } -func (sm *StaticMetadata) Update(meta map[string]any) error { +func (sm *StaticMetadata) update(meta map[string]any) error { if sm.Frameworks == nil { sm.Frameworks = make(map[framework.Framework][]string) } @@ -125,21 +127,31 @@ func (sm *StaticMetadata) Update(meta map[string]any) error { } func (sm *StaticMetadata) updateFrameworks(meta map[string]any) error { - if raw, ok := meta["frameworks"]; ok { - frameworks, ok := raw.(map[string]any) + raw, ok := meta["frameworks"] + if !ok { + return nil + } + + frameworks, ok := raw.(map[string]any) + if !ok { + return fmt.Errorf("frameworks metadata is not an object, got %T", raw) + } + + if len(frameworks) > 0 { + sm.Frameworks = make(map[framework.Framework][]string) + } + + for fw, rawIDs := range frameworks { + ids, ok := rawIDs.([]any) if !ok { - return fmt.Errorf("frameworks metadata is not an object, got %T", raw) + return fmt.Errorf("framework ids is not an array, got %T", rawIDs) } - for fw, rawIDs := range frameworks { - ids, ok := rawIDs.([]any) - if !ok { - return fmt.Errorf("framework ids is not an array, got %T", rawIDs) - } - fr := framework.Framework(fw) - for _, id := range ids { - if str, ok := id.(string); ok { - sm.Frameworks[fr] = append(sm.Frameworks[fr], str) - } + fr := framework.Framework(fw) + for _, id := range ids { + if str, ok := id.(string); ok { + sm.Frameworks[fr] = append(sm.Frameworks[fr], str) + } else { + sm.Frameworks[fr] = []string{} } } } @@ -166,7 +178,7 @@ func (sm *StaticMetadata) FromAnnotations(annotations *ast.Annotations) error { sm.References = append(sm.References, resource.Ref.String()) } if custom := annotations.Custom; custom != nil { - if err := sm.Update(custom); err != nil { + if err := sm.update(custom); err != nil { return err } } @@ -329,7 +341,7 @@ func (m *MetadataRetriever) RetrieveMetadata(ctx context.Context, module *ast.Mo return nil, fmt.Errorf("failed to parse metadata: not an object") } - if err := metadata.Update(meta); err != nil { + if err := metadata.update(meta); err != nil { return nil, err } @@ -436,3 +448,17 @@ func metadataFromRegoModule(module *ast.Module) (*StaticMetadata, error) { } return meta, nil } + +func (m *StaticMetadata) hasAnyFramework(frameworks []framework.Framework) bool { + if len(frameworks) == 0 { + frameworks = []framework.Framework{framework.Default} + } + + for _, fr := range frameworks { + if _, exists := m.Frameworks[fr]; exists { + return true + } + } + + return false +} diff --git a/pkg/iac/rego/metadata_test.go b/pkg/iac/rego/metadata_test.go index 6535e21c14ac..b421df69bb30 100644 --- a/pkg/iac/rego/metadata_test.go +++ b/pkg/iac/rego/metadata_test.go @@ -27,12 +27,9 @@ func Test_UpdateStaticMetadata(t *testing.T) { Provider: "pr", Service: "srvc", Library: false, - Frameworks: map[framework.Framework][]string{ - framework.Default: {"dd"}, - }, } - require.NoError(t, sm.Update( + require.NoError(t, sm.update( map[string]any{ "id": "i_n", "avd_id": "a_n", @@ -68,8 +65,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { Service: "srvc_n", Library: true, Frameworks: map[framework.Framework][]string{ - framework.Default: {"dd"}, - framework.ALL: {"aa"}, + framework.ALL: {"aa"}, }, CloudFormation: &scan.EngineMetadata{}, Terraform: &scan.EngineMetadata{}, @@ -82,7 +78,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { sm := StaticMetadata{ References: []string{"r"}, } - require.NoError(t, sm.Update(map[string]any{ + require.NoError(t, sm.update(map[string]any{ "related_resources": []map[string]any{ { "ref": "r1_n", @@ -107,7 +103,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { sm := StaticMetadata{ References: []string{"r"}, } - require.NoError(t, sm.Update(map[string]any{ + require.NoError(t, sm.update(map[string]any{ "related_resources": []string{"r1_n", "r2_n"}, })) @@ -125,7 +121,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { sm := StaticMetadata{ Deprecated: false, } - require.NoError(t, sm.Update(map[string]any{ + require.NoError(t, sm.update(map[string]any{ "deprecated": true, })) @@ -141,7 +137,7 @@ func Test_UpdateStaticMetadata(t *testing.T) { t.Run("frameworks is not initialized", func(t *testing.T) { sm := StaticMetadata{} - err := sm.Update(map[string]any{ + err := sm.update(map[string]any{ "frameworks": map[string]any{"all": []any{"a", "b", "c"}}, }) require.NoError(t, err) diff --git a/pkg/iac/scanners/cloudformation/scanner_test.go b/pkg/iac/scanners/cloudformation/scanner_test.go index baa8ed81ba59..21d185c5ec06 100644 --- a/pkg/iac/scanners/cloudformation/scanner_test.go +++ b/pkg/iac/scanners/cloudformation/scanner_test.go @@ -82,7 +82,9 @@ deny[res] { Terraform: (*scan.TerraformCustomCheck)(nil), }, RegoPackage: "data.builtin.dockerfile.DS006", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule()) failure := results.GetFailed()[0] diff --git a/pkg/iac/scanners/dockerfile/scanner_test.go b/pkg/iac/scanners/dockerfile/scanner_test.go index 437c37ec9660..4cbd86667a17 100644 --- a/pkg/iac/scanners/dockerfile/scanner_test.go +++ b/pkg/iac/scanners/dockerfile/scanner_test.go @@ -10,7 +10,6 @@ import ( "github.com/aquasecurity/trivy/internal/testutil" "github.com/aquasecurity/trivy/pkg/iac/framework" - "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/rego/schemas" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/options" @@ -252,7 +251,9 @@ USER root CustomChecks: scan.CustomChecks{ Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.dockerfile.DS006", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule(), ) @@ -553,27 +554,23 @@ res := true for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - regoMap := make(map[string]string) - libs, err := rego.LoadEmbeddedLibraries() - require.NoError(t, err) - for name, library := range libs { - regoMap["/rules/"+name] = library.String() - } - regoMap["/code/Dockerfile"] = `FROM golang:1.7.3 as dep + fsysMap := make(map[string]string) + fsysMap["/code/Dockerfile"] = `FROM golang:1.7.3 as dep COPY --from=dep /binary /` - regoMap["/rules/rule.rego"] = tc.inputRegoPolicy - regoMap["/rules/schemas/myfancydockerfile.json"] = string(schemas.Dockerfile) // just use the same for testing - fs := testutil.CreateFS(t, regoMap) + fsysMap["/rules/rule.rego"] = tc.inputRegoPolicy + fsysMap["/rules/schemas/myfancydockerfile.json"] = string(schemas.Dockerfile) // just use the same for testing + fsys := testutil.CreateFS(t, fsysMap) var traceBuf bytes.Buffer scanner := NewScanner( options.ScannerWithPolicyDirs("rules"), + options.ScannerWithEmbeddedLibraries(true), options.ScannerWithTrace(&traceBuf), options.ScannerWithRegoErrorLimits(0), ) - results, err := scanner.ScanFS(context.TODO(), fs, "code") + results, err := scanner.ScanFS(context.TODO(), fsys, "code") if tc.expectedError != "" && err != nil { require.Equal(t, tc.expectedError, err.Error(), tc.name) } else { @@ -605,7 +602,9 @@ COPY --from=dep /binary /` CustomChecks: scan.CustomChecks{ Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.dockerfile.DS006", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule(), ) diff --git a/pkg/iac/scanners/json/scanner_test.go b/pkg/iac/scanners/json/scanner_test.go index 7e7f1c308186..e126768e55d8 100644 --- a/pkg/iac/scanners/json/scanner_test.go +++ b/pkg/iac/scanners/json/scanner_test.go @@ -73,6 +73,8 @@ deny[res] { Terraform: (*scan.TerraformCustomCheck)(nil), }, RegoPackage: "data.builtin.json.lol", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule()) } diff --git a/pkg/iac/scanners/kubernetes/scanner_test.go b/pkg/iac/scanners/kubernetes/scanner_test.go index d972e52e8e80..c981634a0ee6 100644 --- a/pkg/iac/scanners/kubernetes/scanner_test.go +++ b/pkg/iac/scanners/kubernetes/scanner_test.go @@ -119,7 +119,9 @@ deny[res] { CloudFormation: &scan.EngineMetadata{}, CustomChecks: scan.CustomChecks{Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.kubernetes.KSV011", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule()) failure := results.GetFailed()[0] @@ -279,7 +281,9 @@ deny[res] { CloudFormation: &scan.EngineMetadata{}, CustomChecks: scan.CustomChecks{Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.kubernetes.KSV011", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule()) failure := results.GetFailed()[0] diff --git a/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go b/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go index 5b684dc77ed0..dd77c2d89f6e 100644 --- a/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go +++ b/pkg/iac/scanners/terraformplan/tfjson/test/scanner_test.go @@ -33,7 +33,7 @@ func Test_Scanning_Plan(t *testing.T) { failedResults = append(failedResults, r) } } - assert.Len(t, results, 15) + assert.Len(t, failedResults, 9) } diff --git a/pkg/iac/scanners/toml/scanner_test.go b/pkg/iac/scanners/toml/scanner_test.go index c2fdcda26faa..d3c4e51e3b63 100644 --- a/pkg/iac/scanners/toml/scanner_test.go +++ b/pkg/iac/scanners/toml/scanner_test.go @@ -76,7 +76,9 @@ deny[res] { CustomChecks: scan.CustomChecks{ Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.toml.lol", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule(), ) diff --git a/pkg/iac/scanners/yaml/scanner_test.go b/pkg/iac/scanners/yaml/scanner_test.go index 771bf45ef7d2..02468158fcf9 100644 --- a/pkg/iac/scanners/yaml/scanner_test.go +++ b/pkg/iac/scanners/yaml/scanner_test.go @@ -79,7 +79,9 @@ deny[res] { CustomChecks: scan.CustomChecks{ Terraform: (*scan.TerraformCustomCheck)(nil)}, RegoPackage: "data.builtin.yaml.lol", - Frameworks: make(map[framework.Framework][]string), + Frameworks: map[framework.Framework][]string{ + framework.Default: {}, + }, }, results.GetFailed()[0].Rule(), ) From 3a5d091759564496992a83fb2015a21c84a22213 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Fri, 30 Aug 2024 12:18:15 +0600 Subject: [PATCH 319/352] fix(misconf): do not recreate filesystem map (#7416) Signed-off-by: nikpivkin --- pkg/iac/scanners/terraform/parser/evaluator.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/iac/scanners/terraform/parser/evaluator.go b/pkg/iac/scanners/terraform/parser/evaluator.go index 811f618787b5..809498f35c63 100644 --- a/pkg/iac/scanners/terraform/parser/evaluator.go +++ b/pkg/iac/scanners/terraform/parser/evaluator.go @@ -175,7 +175,9 @@ func (e *evaluator) evaluateSubmodules(ctx context.Context, fsMap map[string]fs. var modules terraform.Modules for _, sm := range submodules { modules = append(modules, sm.modules...) - fsMap = lo.Assign(fsMap, sm.fsMap) + for k, v := range sm.fsMap { + fsMap[k] = v + } } e.logger.Debug("Finished processing submodule(s).", log.Int("count", len(modules))) From bf64003ac8b209f34b88f228918a96d4f9dac5e0 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 30 Aug 2024 13:15:10 +0600 Subject: [PATCH 320/352] fix(secret): use `.eyJ` keyword for JWT secret (#7410) --- pkg/fanal/secret/builtin-rules.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fanal/secret/builtin-rules.go b/pkg/fanal/secret/builtin-rules.go index 9cb0aa361025..a83d8eba35ba 100644 --- a/pkg/fanal/secret/builtin-rules.go +++ b/pkg/fanal/secret/builtin-rules.go @@ -604,7 +604,7 @@ var builtinRules = []Rule{ Title: "JWT token", Severity: "MEDIUM", Regex: MustCompile(`ey[a-zA-Z0-9]{17,}\.ey[a-zA-Z0-9\/\\_-]{17,}\.(?:[a-zA-Z0-9\/\\_-]{10,}={0,2})?`), - Keywords: []string{"jwt"}, + Keywords: []string{".eyJ"}, }, { ID: "linear-api-token", From 0cac3ac7075017628a21a7990941df04cbc16dbe Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Sat, 31 Aug 2024 13:06:34 +0600 Subject: [PATCH 321/352] fix(misconf): fix infer type for null value (#7424) Signed-off-by: nikpivkin --- .../scanners/cloudformation/cftypes/types.go | 3 +++ .../cloudformation/parser/parser_test.go | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/pkg/iac/scanners/cloudformation/cftypes/types.go b/pkg/iac/scanners/cloudformation/cftypes/types.go index 21949ae099cf..6a533aae179c 100644 --- a/pkg/iac/scanners/cloudformation/cftypes/types.go +++ b/pkg/iac/scanners/cloudformation/cftypes/types.go @@ -15,6 +15,9 @@ const ( ) func TypeFromGoValue(value any) CfType { + if value == nil { + return Unknown + } switch reflect.TypeOf(value).Kind() { case reflect.String: return String diff --git a/pkg/iac/scanners/cloudformation/parser/parser_test.go b/pkg/iac/scanners/cloudformation/parser/parser_test.go index 0d37440e2e1a..aa058c4df855 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser_test.go +++ b/pkg/iac/scanners/cloudformation/parser/parser_test.go @@ -419,3 +419,24 @@ func TestJsonWithNumbers(t *testing.T) { assert.Equal(t, 1, res[0].GetProperty("SomeIntProp").AsIntValue().Value()) assert.Equal(t, 0, res[0].GetProperty("SomeFloatProp").AsIntValue().Value()) } + +func TestParameterIsNull(t *testing.T) { + src := `--- +AWSTemplateFormatVersion: 2010-09-09 + +Parameters: + Email: + Type: String + +Conditions: + SubscribeEmail: !Not [!Equals [ !Ref Email, ""]] +` + + fsys := testutil.CreateFS(t, map[string]string{ + "main.yaml": src, + }) + + files, err := New().ParseFS(context.TODO(), fsys, ".") + require.NoError(t, err) + require.Len(t, files, 1) +} From feaef9699df5d8ca399770e701a59d7c0ff979a3 Mon Sep 17 00:00:00 2001 From: Kevin Conner Date: Sun, 1 Sep 2024 20:27:42 -0700 Subject: [PATCH 322/352] fix(aws): handle ECR repositories in different regions (#6217) Signed-off-by: Kevin Conner --- pkg/fanal/image/registry/azure/azure.go | 30 ++++---- pkg/fanal/image/registry/azure/azure_test.go | 2 +- pkg/fanal/image/registry/ecr/ecr.go | 54 +++++++++++---- pkg/fanal/image/registry/ecr/ecr_test.go | 68 +++++++++++++++++-- pkg/fanal/image/registry/google/google.go | 18 +++-- .../image/registry/google/google_test.go | 12 ++-- pkg/fanal/image/registry/intf/registry.go | 15 ++++ pkg/fanal/image/registry/token.go | 14 ++-- 8 files changed, 159 insertions(+), 54 deletions(-) create mode 100644 pkg/fanal/image/registry/intf/registry.go diff --git a/pkg/fanal/image/registry/azure/azure.go b/pkg/fanal/image/registry/azure/azure.go index 3203829f3d3d..67368c8bb3c8 100644 --- a/pkg/fanal/image/registry/azure/azure.go +++ b/pkg/fanal/image/registry/azure/azure.go @@ -14,15 +14,19 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/fanal/image/registry/intf" "github.com/aquasecurity/trivy/pkg/fanal/types" ) -type Registry struct { +type RegistryClient struct { domain string scope string cloud cloud.Configuration } +type Registry struct { +} + const ( azureURL = ".azurecr.io" chinaAzureURL = ".azurecr.cn" @@ -31,23 +35,25 @@ const ( scheme = "https" ) -func (r *Registry) CheckOptions(domain string, _ types.RegistryOptions) error { +func (r *Registry) CheckOptions(domain string, _ types.RegistryOptions) (intf.RegistryClient, error) { if strings.HasSuffix(domain, azureURL) { - r.domain = domain - r.scope = scope - r.cloud = cloud.AzurePublic - return nil + return &RegistryClient{ + domain: domain, + scope: scope, + cloud: cloud.AzurePublic, + }, nil } else if strings.HasSuffix(domain, chinaAzureURL) { - r.domain = domain - r.scope = chinaScope - r.cloud = cloud.AzureChina - return nil + return &RegistryClient{ + domain: domain, + scope: scope, + cloud: cloud.AzureChina, + }, nil } - return xerrors.Errorf("Azure registry: %w", types.InvalidURLPattern) + return nil, xerrors.Errorf("Azure registry: %w", types.InvalidURLPattern) } -func (r *Registry) GetCredential(ctx context.Context) (string, string, error) { +func (r *RegistryClient) GetCredential(ctx context.Context) (string, string, error) { opts := azcore.ClientOptions{Cloud: r.cloud} cred, err := azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ClientOptions: opts}) if err != nil { diff --git a/pkg/fanal/image/registry/azure/azure_test.go b/pkg/fanal/image/registry/azure/azure_test.go index 0fb4839e8fee..5be56f777a51 100644 --- a/pkg/fanal/image/registry/azure/azure_test.go +++ b/pkg/fanal/image/registry/azure/azure_test.go @@ -38,7 +38,7 @@ func TestRegistry_CheckOptions(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := azure.Registry{} - err := r.CheckOptions(tt.domain, types.RegistryOptions{}) + _, err := r.CheckOptions(tt.domain, types.RegistryOptions{}) if tt.wantErr != "" { assert.EqualError(t, err, tt.wantErr) } else { diff --git a/pkg/fanal/image/registry/ecr/ecr.go b/pkg/fanal/image/registry/ecr/ecr.go index 261030d1a584..c9bb0dea5330 100644 --- a/pkg/fanal/image/registry/ecr/ecr.go +++ b/pkg/fanal/image/registry/ecr/ecr.go @@ -3,6 +3,7 @@ package ecr import ( "context" "encoding/base64" + "regexp" "strings" "github.com/aws/aws-sdk-go-v2/aws" @@ -11,48 +12,73 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ecr" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/fanal/image/registry/intf" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" ) -const ecrURLSuffix = ".amazonaws.com" -const ecrURLPartial = ".dkr.ecr" - type ecrAPI interface { GetAuthorizationToken(ctx context.Context, params *ecr.GetAuthorizationTokenInput, optFns ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error) } type ECR struct { +} + +type ECRClient struct { Client ecrAPI } -func getSession(option types.RegistryOptions) (aws.Config, error) { +func getSession(domain, region string, option types.RegistryOptions) (aws.Config, error) { // create custom credential information if option is valid if option.AWSSecretKey != "" && option.AWSAccessKey != "" && option.AWSRegion != "" { + if region != option.AWSRegion { + log.Warnf("The region from AWS_REGION (%s) is being overridden. The region from domain (%s) was used.", option.AWSRegion, domain) + } return config.LoadDefaultConfig( context.TODO(), - config.WithRegion(option.AWSRegion), + config.WithRegion(region), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(option.AWSAccessKey, option.AWSSecretKey, option.AWSSessionToken)), ) } - return config.LoadDefaultConfig(context.TODO()) + return config.LoadDefaultConfig(context.TODO(), config.WithRegion(region)) } -func (e *ECR) CheckOptions(domain string, option types.RegistryOptions) error { - if !strings.HasSuffix(domain, ecrURLSuffix) && !strings.Contains(domain, ecrURLPartial) { - return xerrors.Errorf("ECR : %w", types.InvalidURLPattern) +func (e *ECR) CheckOptions(domain string, option types.RegistryOptions) (intf.RegistryClient, error) { + region := determineRegion(domain) + if region == "" { + return nil, xerrors.Errorf("ECR : %w", types.InvalidURLPattern) } - cfg, err := getSession(option) + cfg, err := getSession(domain, region, option) if err != nil { - return err + return nil, err } svc := ecr.NewFromConfig(cfg) - e.Client = svc - return nil + return &ECRClient{Client: svc}, nil +} + +// Endpoints take the form +// .dkr.ecr..amazonaws.com +// .dkr.ecr-fips..amazonaws.com +// .dkr.ecr..amazonaws.com.cn +// .dkr.ecr..sc2s.sgov.gov +// .dkr.ecr..c2s.ic.gov +// see +// - https://docs.aws.amazon.com/general/latest/gr/ecr.html +// - https://docs.amazonaws.cn/en_us/aws/latest/userguide/endpoints-arns.html +// - https://github.com/boto/botocore/blob/1.34.51/botocore/data/endpoints.json +var ecrEndpointMatch = regexp.MustCompile(`^[^.]+\.dkr\.ecr(?:-fips)?\.([^.]+)\.(?:amazonaws\.com(?:\.cn)?|sc2s\.sgov\.gov|c2s\.ic\.gov)$`) + +func determineRegion(domain string) string { + matches := ecrEndpointMatch.FindStringSubmatch(domain) + if matches != nil { + return matches[1] + } + return "" } -func (e *ECR) GetCredential(ctx context.Context) (username, password string, err error) { +func (e *ECRClient) GetCredential(ctx context.Context) (username, password string, err error) { input := &ecr.GetAuthorizationTokenInput{} result, err := e.Client.GetAuthorizationToken(ctx, input) if err != nil { diff --git a/pkg/fanal/image/registry/ecr/ecr_test.go b/pkg/fanal/image/registry/ecr/ecr_test.go index 68b1870f07da..321de7f8639d 100644 --- a/pkg/fanal/image/registry/ecr/ecr_test.go +++ b/pkg/fanal/image/registry/ecr/ecr_test.go @@ -8,14 +8,20 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ecr" awstypes "github.com/aws/aws-sdk-go-v2/service/ecr/types" + "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/fanal/types" ) +type testECRClient interface { + Options() ecr.Options +} + func TestCheckOptions(t *testing.T) { var tests = map[string]struct { - domain string - wantErr error + domain string + expectedRegion string + wantErr error }{ "InvalidURL": { domain: "alpine:3.9", @@ -30,19 +36,71 @@ func TestCheckOptions(t *testing.T) { wantErr: types.InvalidURLPattern, }, "NoOption": { - domain: "xxx.ecr.ap-northeast-1.amazonaws.com", + domain: "xxx.dkr.ecr.ap-northeast-1.amazonaws.com", + expectedRegion: "ap-northeast-1", + }, + "region-1": { + domain: "xxx.dkr.ecr.region-1.amazonaws.com", + expectedRegion: "region-1", + }, + "region-2": { + domain: "xxx.dkr.ecr.region-2.amazonaws.com", + expectedRegion: "region-2", + }, + "fips-region-1": { + domain: "xxx.dkr.ecr-fips.fips-region.amazonaws.com", + expectedRegion: "fips-region", + }, + "cn-region-1": { + domain: "xxx.dkr.ecr.region-1.amazonaws.com.cn", + expectedRegion: "region-1", + }, + "cn-region-2": { + domain: "xxx.dkr.ecr.region-2.amazonaws.com.cn", + expectedRegion: "region-2", + }, + "sc2s-region-1": { + domain: "xxx.dkr.ecr.sc2s-region.sc2s.sgov.gov", + expectedRegion: "sc2s-region", + }, + "c2s-region-1": { + domain: "xxx.dkr.ecr.c2s-region.c2s.ic.gov", + expectedRegion: "c2s-region", + }, + "invalid-ecr": { + domain: "xxx.dkrecr.region-1.amazonaws.com", + wantErr: types.InvalidURLPattern, + }, + "invalid-fips": { + domain: "xxx.dkr.ecrfips.fips-region.amazonaws.com", + wantErr: types.InvalidURLPattern, + }, + "invalid-cn": { + domain: "xxx.dkr.ecr.region-2.amazonaws.cn", + wantErr: types.InvalidURLPattern, + }, + "invalid-sc2s": { + domain: "xxx.dkr.ecr.sc2s-region.sc2s.sgov", + wantErr: types.InvalidURLPattern, + }, + "invalid-cs2": { + domain: "xxx.dkr.ecr.c2s-region.c2s.ic", + wantErr: types.InvalidURLPattern, }, } for testname, v := range tests { a := &ECR{} - err := a.CheckOptions(v.domain, types.RegistryOptions{}) + ecrClient, err := a.CheckOptions(v.domain, types.RegistryOptions{}) if err != nil { if !errors.Is(err, v.wantErr) { t.Errorf("[%s]\nexpected error based on %v\nactual : %v", testname, v.wantErr, err) } continue } + + client := (ecrClient.(*ECRClient)).Client.(testECRClient) + require.Equal(t, v.expectedRegion, client.Options().Region) } } @@ -90,7 +148,7 @@ func TestECRGetCredential(t *testing.T) { } for i, c := range cases { - e := ECR{ + e := ECRClient{ Client: mockedECR{Resp: c.Resp}, } username, password, err := e.GetCredential(context.Background()) diff --git a/pkg/fanal/image/registry/google/google.go b/pkg/fanal/image/registry/google/google.go index fe52c85f7493..3c58f0de005f 100644 --- a/pkg/fanal/image/registry/google/google.go +++ b/pkg/fanal/image/registry/google/google.go @@ -9,14 +9,18 @@ import ( "github.com/GoogleCloudPlatform/docker-credential-gcr/store" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/fanal/image/registry/intf" "github.com/aquasecurity/trivy/pkg/fanal/types" ) -type Registry struct { +type GoogleRegistryClient struct { Store store.GCRCredStore domain string } +type Registry struct { +} + // Google container registry const gcrURLDomain = "gcr.io" const gcrURLSuffix = ".gcr.io" @@ -24,18 +28,18 @@ const gcrURLSuffix = ".gcr.io" // Google artifact registry const garURLSuffix = "-docker.pkg.dev" -func (g *Registry) CheckOptions(domain string, option types.RegistryOptions) error { +func (g *Registry) CheckOptions(domain string, option types.RegistryOptions) (intf.RegistryClient, error) { if domain != gcrURLDomain && !strings.HasSuffix(domain, gcrURLSuffix) && !strings.HasSuffix(domain, garURLSuffix) { - return xerrors.Errorf("Google registry: %w", types.InvalidURLPattern) + return nil, xerrors.Errorf("Google registry: %w", types.InvalidURLPattern) } - g.domain = domain + client := GoogleRegistryClient{domain: domain} if option.GCPCredPath != "" { - g.Store = store.NewGCRCredStore(option.GCPCredPath) + client.Store = store.NewGCRCredStore(option.GCPCredPath) } - return nil + return &client, nil } -func (g *Registry) GetCredential(_ context.Context) (username, password string, err error) { +func (g *GoogleRegistryClient) GetCredential(_ context.Context) (username, password string, err error) { var credStore store.GCRCredStore if g.Store == nil { credStore, err = store.DefaultGCRCredStore() diff --git a/pkg/fanal/image/registry/google/google_test.go b/pkg/fanal/image/registry/google/google_test.go index 2e4ba64d663e..1bce72897d46 100644 --- a/pkg/fanal/image/registry/google/google_test.go +++ b/pkg/fanal/image/registry/google/google_test.go @@ -14,7 +14,7 @@ func TestCheckOptions(t *testing.T) { var tests = map[string]struct { domain string opt types.RegistryOptions - gcr *Registry + grc *GoogleRegistryClient wantErr error }{ "InvalidURL": { @@ -27,12 +27,12 @@ func TestCheckOptions(t *testing.T) { }, "NoOption": { domain: "gcr.io", - gcr: &Registry{domain: "gcr.io"}, + grc: &GoogleRegistryClient{domain: "gcr.io"}, }, "CredOption": { domain: "gcr.io", opt: types.RegistryOptions{GCPCredPath: "/path/to/file.json"}, - gcr: &Registry{ + grc: &GoogleRegistryClient{ domain: "gcr.io", Store: store.NewGCRCredStore("/path/to/file.json"), }, @@ -41,7 +41,7 @@ func TestCheckOptions(t *testing.T) { for testname, v := range tests { g := &Registry{} - err := g.CheckOptions(v.domain, v.opt) + grc, err := g.CheckOptions(v.domain, v.opt) if v.wantErr != nil { if err == nil { t.Errorf("%s : expected error but no error", testname) @@ -52,8 +52,8 @@ func TestCheckOptions(t *testing.T) { } continue } - if !reflect.DeepEqual(v.gcr, g) { - t.Errorf("[%s]\nexpected : %v\nactual : %v", testname, v.gcr, g) + if !reflect.DeepEqual(v.grc, grc) { + t.Errorf("[%s]\nexpected : %v\nactual : %v", testname, v.grc, grc) } } } diff --git a/pkg/fanal/image/registry/intf/registry.go b/pkg/fanal/image/registry/intf/registry.go new file mode 100644 index 000000000000..da8c5d3c1789 --- /dev/null +++ b/pkg/fanal/image/registry/intf/registry.go @@ -0,0 +1,15 @@ +package intf + +import ( + "context" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +type RegistryClient interface { + GetCredential(ctx context.Context) (string, string, error) +} + +type Registry interface { + CheckOptions(domain string, option types.RegistryOptions) (RegistryClient, error) +} diff --git a/pkg/fanal/image/registry/token.go b/pkg/fanal/image/registry/token.go index b959c6cc7bbc..72c569890196 100644 --- a/pkg/fanal/image/registry/token.go +++ b/pkg/fanal/image/registry/token.go @@ -8,12 +8,13 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/image/registry/azure" "github.com/aquasecurity/trivy/pkg/fanal/image/registry/ecr" "github.com/aquasecurity/trivy/pkg/fanal/image/registry/google" + "github.com/aquasecurity/trivy/pkg/fanal/image/registry/intf" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" ) var ( - registries []Registry + registries []intf.Registry ) func init() { @@ -22,23 +23,18 @@ func init() { RegisterRegistry(&azure.Registry{}) } -type Registry interface { - CheckOptions(domain string, option types.RegistryOptions) error - GetCredential(ctx context.Context) (string, string, error) -} - -func RegisterRegistry(registry Registry) { +func RegisterRegistry(registry intf.Registry) { registries = append(registries, registry) } func GetToken(ctx context.Context, domain string, opt types.RegistryOptions) (auth authn.Basic) { // check registry which particular to get credential for _, registry := range registries { - err := registry.CheckOptions(domain, opt) + client, err := registry.CheckOptions(domain, opt) if err != nil { continue } - username, password, err := registry.GetCredential(ctx) + username, password, err := client.GetCredential(ctx) if err != nil { // only skip check registry if error occurred log.Debug("Credential error", log.Err(err)) From c929290c3c0e4e91337264d69e75ccb60522bc65 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 2 Sep 2024 12:44:33 +0600 Subject: [PATCH 323/352] fix: logger initialization before flags parsing (#7372) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- pkg/log/deferred.go | 62 +++++++++++++++++++++++++++++++++++++++++++++ pkg/log/logger.go | 11 ++++++-- 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 pkg/log/deferred.go diff --git a/pkg/log/deferred.go b/pkg/log/deferred.go new file mode 100644 index 000000000000..9fbf81652f4d --- /dev/null +++ b/pkg/log/deferred.go @@ -0,0 +1,62 @@ +package log + +import ( + "context" + "log/slog" + "slices" +) + +func init() { + // Set the default logger so that logs are buffered until the logger is initialized. + slog.SetDefault(New(&DeferredHandler{records: new([]deferredRecord)})) +} + +// DeferredHandler is needed to save logs and print them after calling `PrintLogs()` command. +// For example, this may be necessary when the logger is not yet initialized, but messages need to be transmitted. +// In this case, the messages are saved and printed when the logger is initialized. +type DeferredHandler struct { + attrs []slog.Attr + + // Shared with all instances of the handler. + // NOTE: non-thread safe + records *[]deferredRecord +} + +type deferredRecord struct { + ctx context.Context + slog.Record +} + +func (*DeferredHandler) Enabled(context.Context, slog.Level) bool { + return true +} + +func (d *DeferredHandler) Handle(ctx context.Context, record slog.Record) error { + record.AddAttrs(d.attrs...) + *d.records = append(*d.records, deferredRecord{ + ctx: ctx, + Record: record, + }) + return nil +} + +func (d *DeferredHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + h := *d + h.attrs = slices.Clip(d.attrs) + h.attrs = append(h.attrs, attrs...) + return &h +} + +func (*DeferredHandler) WithGroup(_ string) slog.Handler { + panic("WithGroup is not implemented") +} + +func (d *DeferredHandler) Flush(h slog.Handler) { + for _, record := range *d.records { + if !h.Enabled(record.ctx, record.Level) { + continue + } + _ = h.Handle(record.ctx, record.Record) + } + d.records = nil +} diff --git a/pkg/log/logger.go b/pkg/log/logger.go index 73c4231fd4af..ac629d211157 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -33,11 +33,18 @@ func New(h slog.Handler) *Logger { return slog.New(h) } -// InitLogger initialize the logger variable +// InitLogger initializes the logger variable and flushes the buffered logs if needed. func InitLogger(debug, disable bool) { level := lo.Ternary(debug, slog.LevelDebug, slog.LevelInfo) out := lo.Ternary(disable, io.Discard, io.Writer(os.Stderr)) - slog.SetDefault(New(NewHandler(out, &Options{Level: level}))) + h := NewHandler(out, &Options{Level: level}) + + // Flush the buffered logs if needed. + if d, ok := slog.Default().Handler().(*DeferredHandler); ok { + d.Flush(h) + } + + slog.SetDefault(New(h)) } var ( From fd9ed3a330bc66e229bcbdc262dc296a3bf01f54 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:19:01 +0600 Subject: [PATCH 324/352] fix(nodejs): check all `importers` to detect dev deps from pnpm-lock.yaml file (#7387) --- pkg/dependency/parser/nodejs/pnpm/parse.go | 22 +++++++++++++------ .../parser/nodejs/pnpm/parse_test.go | 6 ----- .../parser/nodejs/pnpm/parse_testcase.go | 13 +++++++++++ .../nodejs/pnpm/testdata/pnpm-lock_v9.yaml | 21 ++++++++++++++++++ 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/pkg/dependency/parser/nodejs/pnpm/parse.go b/pkg/dependency/parser/nodejs/pnpm/parse.go index 8ccf9de0ae9a..6f85411f40d0 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse.go @@ -37,15 +37,11 @@ type LockFile struct { Packages map[string]PackageInfo `yaml:"packages,omitempty"` // V9 - Importers Importer `yaml:"importers,omitempty"` + Importers map[string]Importer `yaml:"importers,omitempty"` Snapshots map[string]Snapshot `yaml:"snapshots,omitempty"` } type Importer struct { - Root RootImporter `yaml:".,omitempty"` -} - -type RootImporter struct { Dependencies map[string]ImporterDepVersion `yaml:"dependencies,omitempty"` DevDependencies map[string]ImporterDepVersion `yaml:"devDependencies,omitempty"` } @@ -167,6 +163,18 @@ func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependen } + // Parse `Importers` to find all direct dependencies + devDeps := make(map[string]string) + deps := make(map[string]string) + for _, importer := range lockFile.Importers { + for n, v := range importer.DevDependencies { + devDeps[n] = v.Version + } + for n, v := range importer.Dependencies { + deps[n] = v.Version + } + } + for depPath, pkgInfo := range lockFile.Packages { name, ver, ref := p.parseDepPath(depPath, lockVer) parsedVer := p.parseVersion(depPath, ver, lockVer) @@ -179,10 +187,10 @@ func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependen // We will update `Dev` field later. dev := true relationship := ftypes.RelationshipIndirect - if dep, ok := lockFile.Importers.Root.DevDependencies[name]; ok && dep.Version == ver { + if v, ok := devDeps[name]; ok && p.trimPeerDeps(v, lockVer) == ver { relationship = ftypes.RelationshipDirect } - if dep, ok := lockFile.Importers.Root.Dependencies[name]; ok && p.trimPeerDeps(dep.Version, lockVer) == ver { + if v, ok := deps[name]; ok && p.trimPeerDeps(v, lockVer) == ver { relationship = ftypes.RelationshipDirect dev = false // mark root direct deps to update `dev` field of their child deps. } diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_test.go b/pkg/dependency/parser/nodejs/pnpm/parse_test.go index ec869d6ff492..ebdfe0e2442f 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_test.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_test.go @@ -59,12 +59,6 @@ func TestParse(t *testing.T) { want: pnpmV9, wantDeps: pnpmV9Deps, }, - { - name: "v9", - file: "testdata/pnpm-lock_v9.yaml", - want: pnpmV9, - wantDeps: pnpmV9Deps, - }, { name: "v9 with cyclic dependencies import", file: "testdata/pnpm-lock_v9_cyclic_import.yaml", diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go index 3c9383bd1e25..5649a9325559 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go @@ -752,6 +752,13 @@ var ( Version: "0.4.0", Relationship: ftypes.RelationshipIndirect, }, + { + ID: "await-sleep@0.0.1", + Name: "await-sleep", + Version: "0.0.1", + Dev: true, + Relationship: ftypes.RelationshipDirect, + }, { ID: "debug@4.3.4", Name: "debug", @@ -843,6 +850,12 @@ var ( Version: "8.1.0", Relationship: ftypes.RelationshipDirect, }, + { + ID: "sleep-utils@1.0.3", + Name: "sleep-utils", + Version: "1.0.3", + Relationship: ftypes.RelationshipDirect, + }, { ID: "statuses@1.4.0", Name: "statuses", diff --git a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml index 12a61f02b79a..ef8a229a3c88 100644 --- a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml +++ b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml @@ -40,6 +40,17 @@ importers: specifier: 2.0.0 version: 2.0.0 + subdir: + dependencies: + sleep-utils: + specifier: 1.0.3 + version: 1.0.3 + + devDependencies: + await-sleep: + specifier: ^0.0.1 + version: 0.0.1 + packages: '@babel/helper-string-parser@7.24.1': @@ -52,6 +63,9 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + await-sleep@0.0.1: + resolution: {integrity: sha512-H3X3eAxwGpeNIk/yvFOs8g7500Q1YvzrxjSC9TNgLGtjrMFxPwhDdcT34QNs2iGWpZ+5WKkMJdjDoYs+Sw+TaA==} + debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -117,6 +131,9 @@ packages: promise@8.1.0: resolution: {integrity: sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==} + sleep-utils@1.0.3: + resolution: {integrity: sha512-uJW7WDHISE1zJIdvoIewcdmis3pBvJhM30rni2gH7fHhV1NkTWLKw3J6CPRFdg3h+rFChFHzAgbkCKUErd4s8Q==} + statuses@1.4.0: resolution: {integrity: sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==} engines: {node: '>= 0.6'} @@ -134,6 +151,8 @@ snapshots: asynckit@0.4.0: {} + await-sleep@0.0.1: {} + debug@4.3.4(supports-color@8.1.1): dependencies: ms: 2.0.0 @@ -186,6 +205,8 @@ snapshots: optionalDependencies: asap: 2.0.6 + sleep-utils@1.0.3: {} + statuses@1.4.0: {} unpipe@1.0.0: {} From 1a6295c5e5a97415728c51feff3af6967fcb39e3 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:49:33 +0600 Subject: [PATCH 325/352] test: add integration plugin tests (#7299) --- integration/plugin_test.go | 111 ++++++++++++++++++ ...t-0.1.0-plugin-with-before-flag.txt.golden | 5 + .../testdata/count-0.2.0-plugin.txt.golden | 5 + 3 files changed, 121 insertions(+) create mode 100644 integration/plugin_test.go create mode 100644 integration/testdata/count-0.1.0-plugin-with-before-flag.txt.golden create mode 100644 integration/testdata/count-0.2.0-plugin.txt.golden diff --git a/integration/plugin_test.go b/integration/plugin_test.go new file mode 100644 index 000000000000..e4a6bf4d0014 --- /dev/null +++ b/integration/plugin_test.go @@ -0,0 +1,111 @@ +//go:build integration + +package integration + +import ( + "io" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/utils/fsutils" +) + +func TestPlugin(t *testing.T) { + tests := []struct { + name string + plugin string + pluginArgs string + golden string + }{ + { + name: "count plugin installed from `index`", + plugin: "count@v0.2.0", + golden: "testdata/count-0.2.0-plugin.txt.golden", + }, + { + name: "count plugin installed from github archive", + plugin: "https://github.com/aquasecurity/trivy-plugin-count/archive/refs/tags/v0.1.0.zip", + pluginArgs: "--published-before=2020-01-01", + golden: "testdata/count-0.1.0-plugin-with-before-flag.txt.golden", + }, + } + + // Set up testing DB + cacheDir := initDB(t) + tempStdOut := setTempStdout(t) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // We can overwrite stdout for `_default_Manager` only once. + // So we need to clear the temporary stdout file before each test case. + clearFile(t, tempStdOut) + + t.Setenv("XDG_DATA_HOME", t.TempDir()) + + // Install plugin + err := execute([]string{ + "plugin", + "install", + tt.plugin, + }) + require.NoError(t, err) + + // Get list of plugins + err = execute([]string{ + "plugin", + "list", + }) + require.NoError(t, err) + + // Run Trivy with plugin as output + args := []string{ + "--cache-dir", + cacheDir, + "fs", + "-f", + "json", + "-o", + "plugin=count", + "testdata/fixtures/repo/pip", + } + + if tt.pluginArgs != "" { + args = append(args, "--output-plugin-arg", tt.pluginArgs) + } + + err = execute(args) + + if *update { + fsutils.CopyFile(tempStdOut.Name(), tt.golden) + } + + compareRawFiles(t, tt.golden, tempStdOut.Name()) + }) + } +} + +func setTempStdout(t *testing.T) *os.File { + tmpFile := filepath.Join(t.TempDir(), "output.txt") + f, err := os.Create(tmpFile) + require.NoError(t, err) + + // Overwrite Stdout to get output of plugin + defaultStdout := os.Stdout + os.Stdout = f + t.Cleanup(func() { + os.Stdout = defaultStdout + f.Close() + }) + return f +} + +func clearFile(t *testing.T, file *os.File) { + _, err := file.Seek(0, io.SeekStart) + require.NoError(t, err) + + _, err = file.Write([]byte{}) + require.NoError(t, err) +} diff --git a/integration/testdata/count-0.1.0-plugin-with-before-flag.txt.golden b/integration/testdata/count-0.1.0-plugin-with-before-flag.txt.golden new file mode 100644 index 000000000000..4c9d2bbaeb41 --- /dev/null +++ b/integration/testdata/count-0.1.0-plugin-with-before-flag.txt.golden @@ -0,0 +1,5 @@ +Installed Plugins: + Name: count + Version: 0.1.0 + +Number of vulnerabilities: 1 diff --git a/integration/testdata/count-0.2.0-plugin.txt.golden b/integration/testdata/count-0.2.0-plugin.txt.golden new file mode 100644 index 000000000000..9f86fc48e6dd --- /dev/null +++ b/integration/testdata/count-0.2.0-plugin.txt.golden @@ -0,0 +1,5 @@ +Installed Plugins: + Name: count + Version: 0.2.0 + +Number of vulnerabilities: 2 From af1d257730422d238871beb674767f8f83c5d06a Mon Sep 17 00:00:00 2001 From: Bob Callaway Date: Tue, 3 Sep 2024 01:47:21 -0400 Subject: [PATCH 326/352] feat(sbom): set User-Agent header on requests to Rekor (#7396) Signed-off-by: Bob Callaway --- go.mod | 4 ++-- pkg/rekor/client.go | 15 +++++---------- pkg/rekor/client_test.go | 7 +++++++ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 844135ff79f8..2e8f5c7ad25c 100644 --- a/go.mod +++ b/go.mod @@ -47,8 +47,8 @@ require ( github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.17.0 github.com/go-git/go-git/v5 v5.12.0 - github.com/go-openapi/runtime v0.28.0 - github.com/go-openapi/strfmt v0.23.0 + github.com/go-openapi/runtime v0.28.0 // indirect + github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/go-containerregistry v0.20.2 diff --git a/pkg/rekor/client.go b/pkg/rekor/client.go index d748166d6d7f..c6390f9679db 100644 --- a/pkg/rekor/client.go +++ b/pkg/rekor/client.go @@ -2,11 +2,10 @@ package rekor import ( "context" - "net/url" + "fmt" "slices" - httptransport "github.com/go-openapi/runtime/client" - "github.com/go-openapi/strfmt" + pkgclient "github.com/sigstore/rekor/pkg/client" "github.com/sigstore/rekor/pkg/generated/client" eclient "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/client/index" @@ -14,6 +13,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/version/app" ) const ( @@ -64,15 +64,10 @@ type Client struct { } func NewClient(rekorURL string) (*Client, error) { - u, err := url.Parse(rekorURL) + c, err := pkgclient.GetRekorClient(rekorURL, pkgclient.WithUserAgent(fmt.Sprintf("trivy/%s", app.Version()))) if err != nil { - return nil, xerrors.Errorf("failed to parse url: %w", err) + return nil, xerrors.Errorf("failed to create rekor client: %w", err) } - - c := client.New( - httptransport.New(u.Host, client.DefaultBasePath, []string{u.Scheme}), - strfmt.Default, - ) return &Client{Rekor: c}, nil } diff --git a/pkg/rekor/client_test.go b/pkg/rekor/client_test.go index 9ea48d657cc5..f9390122fbf0 100644 --- a/pkg/rekor/client_test.go +++ b/pkg/rekor/client_test.go @@ -4,6 +4,7 @@ import ( "context" "net/http" "net/http/httptest" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -56,6 +57,9 @@ func TestClient_Search(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.HasPrefix(r.UserAgent(), "trivy/") { + t.Fatalf("User-Agent header was not specified") + } http.ServeFile(w, r, tt.mockResponseFile) return })) @@ -148,6 +152,9 @@ func TestClient_GetEntries(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.HasPrefix(r.UserAgent(), "trivy/") { + t.Fatalf("User-Agent header was not specified") + } http.ServeFile(w, r, tt.mockResponseFile) return })) From da4ebfa1a741f3f8b0b43289b4028afe763f7d43 Mon Sep 17 00:00:00 2001 From: vhash <29121316+LucasVanHaaren@users.noreply.github.com> Date: Tue, 3 Sep 2024 07:48:12 +0200 Subject: [PATCH 327/352] fix(helm): explicitly define `kind` and `apiVersion` of `volumeClaimTemplate` element (#7362) --- helm/trivy/templates/statefulset.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helm/trivy/templates/statefulset.yaml b/helm/trivy/templates/statefulset.yaml index efb90274fe2c..4a931d2ac0b0 100644 --- a/helm/trivy/templates/statefulset.yaml +++ b/helm/trivy/templates/statefulset.yaml @@ -17,7 +17,9 @@ spec: app.kubernetes.io/instance: {{ .Release.Name }} {{- if .Values.persistence.enabled }} volumeClaimTemplates: - - metadata: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: name: data spec: resources: From 870523d384add914cb4edf6271393986da7d69c9 Mon Sep 17 00:00:00 2001 From: simar7 <1254783+simar7@users.noreply.github.com> Date: Tue, 3 Sep 2024 00:31:17 -0600 Subject: [PATCH 328/352] chore(deps): Bump trivy-checks and pin OPA (#7427) Signed-off-by: nikpivkin Co-authored-by: nikpivkin --- .../configuration/cli/trivy_config.md | 2 +- .../configuration/cli/trivy_filesystem.md | 2 +- .../configuration/cli/trivy_image.md | 2 +- .../configuration/cli/trivy_kubernetes.md | 2 +- .../configuration/cli/trivy_repository.md | 2 +- .../configuration/cli/trivy_rootfs.md | 2 +- .../references/configuration/config-file.md | 2 +- go.mod | 18 ++++++---- go.sum | 34 ++++++++++--------- pkg/flag/rego_flags.go | 1 + pkg/iac/scanners/cloudformation/scanner.go | 4 +-- 11 files changed, 39 insertions(+), 32 deletions(-) diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index b32e74c1c752..91cd64292ea8 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -31,7 +31,7 @@ trivy config [flags] DIR -h, --help help for config --ignore-policy string specify the Rego file path to evaluate each vulnerability --ignorefile string specify .trivyignore file (default ".trivyignore") - --include-deprecated-checks include deprecated checks + --include-deprecated-checks include deprecated checks (default true) --include-non-failures include successes and exceptions, available with '--scanners misconfig' --k8s-version string specify k8s version to validate outdated api by it (example: 1.21.0) --misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot]) diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 16c5909549e3..e907c5d4f9d5 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -53,7 +53,7 @@ trivy filesystem [flags] PATH --ignore-unfixed display only fixed vulnerabilities --ignored-licenses strings specify a list of license to ignore --ignorefile string specify .trivyignore file (default ".trivyignore") - --include-deprecated-checks include deprecated checks + --include-deprecated-checks include deprecated checks (default true) --include-dev-deps include development dependencies in the report (supported: npm, yarn) --include-non-failures include successes and exceptions, available with '--scanners misconfig' --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index bbd75e690cfc..156582f047ad 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -71,7 +71,7 @@ trivy image [flags] IMAGE_NAME --ignorefile string specify .trivyignore file (default ".trivyignore") --image-config-scanners strings comma-separated list of what security issues to detect on container image configurations (misconfig,secret) --image-src strings image source(s) to use, in priority order (docker,containerd,podman,remote) (default [docker,containerd,podman,remote]) - --include-deprecated-checks include deprecated checks + --include-deprecated-checks include deprecated checks (default true) --include-non-failures include successes and exceptions, available with '--scanners misconfig' --input string input file path instead of image name --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 2bc84e905282..b7c626a7d476 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -66,7 +66,7 @@ trivy kubernetes [flags] [CONTEXT] --ignore-unfixed display only fixed vulnerabilities --ignorefile string specify .trivyignore file (default ".trivyignore") --image-src strings image source(s) to use, in priority order (docker,containerd,podman,remote) (default [docker,containerd,podman,remote]) - --include-deprecated-checks include deprecated checks + --include-deprecated-checks include deprecated checks (default true) --include-kinds strings indicate the kinds included in scanning (example: node) --include-namespaces strings indicate the namespaces included in scanning (example: kube-system) --include-non-failures include successes and exceptions, available with '--scanners misconfig' diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index eeef161725a8..04f3f26e919a 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -53,7 +53,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --ignore-unfixed display only fixed vulnerabilities --ignored-licenses strings specify a list of license to ignore --ignorefile string specify .trivyignore file (default ".trivyignore") - --include-deprecated-checks include deprecated checks + --include-deprecated-checks include deprecated checks (default true) --include-dev-deps include development dependencies in the report (supported: npm, yarn) --include-non-failures include successes and exceptions, available with '--scanners misconfig' --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 88f5bd197779..0e0dfa54d448 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -56,7 +56,7 @@ trivy rootfs [flags] ROOTDIR --ignore-unfixed display only fixed vulnerabilities --ignored-licenses strings specify a list of license to ignore --ignorefile string specify .trivyignore file (default ".trivyignore") - --include-deprecated-checks include deprecated checks + --include-deprecated-checks include deprecated checks (default true) --include-non-failures include successes and exceptions, available with '--scanners misconfig' --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") --license-confidence-level float specify license classifier's confidence level (default 0.9) diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index 6a54b8e27bdc..b2b25e47689e 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -477,7 +477,7 @@ rego: data: [] # Same as '--include-deprecated-checks' - include-deprecated-checks: false + include-deprecated-checks: true # Same as '--check-namespaces' namespaces: [] diff --git a/go.mod b/go.mod index 2e8f5c7ad25c..62c3dd5a6f18 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/aquasecurity/table v1.8.0 github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 github.com/aquasecurity/tml v0.6.1 - github.com/aquasecurity/trivy-checks v0.13.1-0.20240830035934-7761a83288cd + github.com/aquasecurity/trivy-checks v0.13.1-0.20240830230553-53ddbbade784 github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b @@ -41,7 +41,7 @@ require ( github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cenkalti/backoff/v4 v4.3.0 github.com/cheggaaa/pb/v3 v3.1.5 - github.com/containerd/containerd v1.7.20 + github.com/containerd/containerd v1.7.21 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 github.com/docker/docker v27.1.1+incompatible github.com/docker/go-connections v0.5.0 @@ -301,7 +301,8 @@ require ( github.com/moby/sys/mountinfo v0.7.1 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/signal v0.7.0 // indirect - github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -322,9 +323,9 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_golang v1.20.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.51.1 // indirect + github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect @@ -377,10 +378,10 @@ require ( go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/oauth2 v0.20.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sys v0.23.0 // indirect golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect - golang.org/x/time v0.5.0 // indirect + golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.23.0 // indirect google.golang.org/api v0.172.0 // indirect google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect @@ -418,3 +419,6 @@ require ( // cf. https://github.com/openvex/discovery/pull/40 replace github.com/openvex/discovery => github.com/knqyf263/discovery v0.1.1-0.20240726113521-97873005fd03 + +// see https://github.com/open-policy-agent/opa/pull/6970 +replace github.com/open-policy-agent/opa => github.com/nikpivkin/opa v0.0.0-20240829080621-16999fcb5464 diff --git a/go.sum b/go.sum index e760459b4d51..fdb1c5a2dcd6 100644 --- a/go.sum +++ b/go.sum @@ -348,8 +348,8 @@ github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 h1:b43UVqY github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8/go.mod h1:wXA9k3uuaxY3yu7gxrxZDPo/04FEMJtwyecdAlYrEIo= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= -github.com/aquasecurity/trivy-checks v0.13.1-0.20240830035934-7761a83288cd h1:/6sPLCU4JICPPYAmY2iUsLGpgYBXUH6M/0fy57AhNWY= -github.com/aquasecurity/trivy-checks v0.13.1-0.20240830035934-7761a83288cd/go.mod h1:zLBeXaTJkAvPZqKiRACAsP49ZywCEXFEjXMLa8kmc8Q= +github.com/aquasecurity/trivy-checks v0.13.1-0.20240830230553-53ddbbade784 h1:1rvPiCK8uQd3sarOuZ60nwksHpxsNdrvptz4eDW/V14= +github.com/aquasecurity/trivy-checks v0.13.1-0.20240830230553-53ddbbade784/go.mod h1:Ralz7PWmR3LirHlXxVtUXc+7CFmWE82jbLk7+TPvV/0= github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 h1:6/T8sFdNVG/AwOGoK6X55h7hF7LYqK8bsuPz8iEz8jM= github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04/go.mod h1:0T6oy2t1Iedt+yi3Ml5cpOYp5FZT4MI1/mx+3p+PIs8= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= @@ -479,8 +479,8 @@ github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= -github.com/containerd/containerd v1.7.20 h1:Sl6jQYk3TRavaU83h66QMbI2Nqg9Jm6qzwX57Vsn1SQ= -github.com/containerd/containerd v1.7.20/go.mod h1:52GsS5CwquuqPuLncsXwG0t2CiUce+KsNHJZQJvAgR0= +github.com/containerd/containerd v1.7.21 h1:USGXRK1eOC/SX0L195YgxTHb0a00anxajOzgfN0qrCA= +github.com/containerd/containerd v1.7.21/go.mod h1:e3Jz1rYRUZ2Lt51YrH9Rz0zPyJBOlSvB3ghr2jbVD8g= github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA= github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= @@ -1096,8 +1096,10 @@ github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5 github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI= github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= -github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= -github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1120,6 +1122,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nikpivkin/opa v0.0.0-20240829080621-16999fcb5464 h1:jhZ8nLVxOAslgzmPdKTyctfDJkMfRgksCypFriHzf4E= +github.com/nikpivkin/opa v0.0.0-20240829080621-16999fcb5464/go.mod h1:cvSIxY0dexL39hOPqXSZKdBYFNx2Rv8Fu5n3MmTjqtE= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 h1:Up6+btDp321ZG5/zdSLo48H9Iaq0UQGthrhWC6pCxzE= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481/go.mod h1:yKZQO8QE2bHlgozqWDiRVqTFlLQSj30K/6SAK8EeYFw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -1143,8 +1147,6 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= -github.com/open-policy-agent/opa v0.67.1 h1:rzy26J6g1X+CKknAcx0Vfbt41KqjuSzx4E0A8DAZf3E= -github.com/open-policy-agent/opa v0.67.1/go.mod h1:aqKlHc8E2VAAylYE9x09zJYr/fYzGX+JKne89UGqFzk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -1190,8 +1192,8 @@ github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjz github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.1 h1:IMJXHOD6eARkQpxo8KkhgEVFlBNm+nkrFUyGlIu7Na8= +github.com/prometheus/client_golang v1.20.1/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -1199,8 +1201,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw= -github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= @@ -1605,8 +1607,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= -golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1751,8 +1753,8 @@ golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/pkg/flag/rego_flags.go b/pkg/flag/rego_flags.go index 4b291f0a5eb3..5e856f30062e 100644 --- a/pkg/flag/rego_flags.go +++ b/pkg/flag/rego_flags.go @@ -11,6 +11,7 @@ var ( Name: "include-deprecated-checks", ConfigName: "rego.include-deprecated-checks", Usage: "include deprecated checks", + Default: true, } SkipCheckUpdateFlag = Flag[bool]{ Name: "skip-check-update", diff --git a/pkg/iac/scanners/cloudformation/scanner.go b/pkg/iac/scanners/cloudformation/scanner.go index e7677926944d..dc4deab0aff5 100644 --- a/pkg/iac/scanners/cloudformation/scanner.go +++ b/pkg/iac/scanners/cloudformation/scanner.go @@ -64,8 +64,8 @@ type Scanner struct { includeDeprecatedChecks bool } -func (s *Scanner) SetIncludeDeprecatedChecks(bool) { - s.includeDeprecatedChecks = true +func (s *Scanner) SetIncludeDeprecatedChecks(b bool) { + s.includeDeprecatedChecks = b } func (s *Scanner) SetCustomSchemas(map[string][]byte) {} From 2d97700d10665142d2f66d7910202bec82116209 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:28:49 +0600 Subject: [PATCH 329/352] feat(java): add `test` scope support for `pom.xml` files (#7414) --- docs/docs/coverage/language/java.md | 18 ++++++++---- pkg/dependency/parser/java/pom/artifact.go | 1 + pkg/dependency/parser/java/pom/parse.go | 4 ++- pkg/dependency/parser/java/pom/parse_test.go | 28 +++++++++++++++++++ pkg/dependency/parser/java/pom/pom.go | 1 + .../parser/java/pom/testdata/happy/pom.xml | 6 ++++ 6 files changed, 51 insertions(+), 7 deletions(-) diff --git a/docs/docs/coverage/language/java.md b/docs/docs/coverage/language/java.md index 67cd8c135b9d..26bad288e552 100644 --- a/docs/docs/coverage/language/java.md +++ b/docs/docs/coverage/language/java.md @@ -12,12 +12,12 @@ Each artifact supports the following scanners: The following table provides an outline of the features Trivy offers. -| Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | -|------------------|:---------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| -| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | Not needed | -| pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | - | -| *gradle.lockfile | - | Exclude | ✓ | ✓ | Not needed | -| *.sbt.lock | - | Exclude | - | ✓ | Not needed | +| Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|------------------|:---------------------:|:------------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | Not needed | +| pom.xml | Maven repository [^1] | [Exclude](#scopes) | ✓ | ✓[^7] | - | +| *gradle.lockfile | - | Exclude | ✓ | ✓ | Not needed | +| *.sbt.lock | - | Exclude | - | ✓ | Not needed | These may be enabled or disabled depending on the target. See [here](./index.md) for the detail. @@ -69,6 +69,11 @@ The vulnerability database will be downloaded anyway. !!! Warning Trivy may skip some dependencies (that were not found on your local machine) when the `--offline-scan` flag is passed. +### scopes +Trivy supports `runtime`, `compile`, `test` and `import` (for `dependencyManagement`) [dependency scopes][dependency-scopes]. +Dependencies without scope are also detected. + +By default, Trivy doesn't report dependencies with `test` scope. Use the `--include-dev-deps` flag to include them. ### maven-invoker-plugin Typically, the integration tests directory (`**/[src|target]/it/*/pom.xml`) of [maven-invoker-plugin][maven-invoker-plugin] doesn't contain actual `pom.xml` files and should be skipped to avoid noise. @@ -120,3 +125,4 @@ Make sure that you have cache[^8] directory to find licenses from `*.pom` depend [maven-pom-repos]: https://maven.apache.org/settings.html#repositories [sbt-dependency-lock]: https://stringbean.github.io/sbt-dependency-lock [detection-priority]: ../../scanner/vulnerability.md#detection-priority +[dependency-scopes]: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope diff --git a/pkg/dependency/parser/java/pom/artifact.go b/pkg/dependency/parser/java/pom/artifact.go index b2e97efb229b..f691afac5ebd 100644 --- a/pkg/dependency/parser/java/pom/artifact.go +++ b/pkg/dependency/parser/java/pom/artifact.go @@ -27,6 +27,7 @@ type artifact struct { Module bool Relationship ftypes.Relationship + Test bool Locations ftypes.Locations } diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index cbd7bf47db17..57f41a1d32f4 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -214,6 +214,7 @@ func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ft Licenses: result.artifact.Licenses, Relationship: art.Relationship, Locations: art.Locations, + Test: art.Test, } // save only dependency names @@ -234,6 +235,7 @@ func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ft Licenses: art.Licenses, Relationship: art.Relationship, Locations: art.Locations, + Dev: art.Test, } pkgs = append(pkgs, pkg) @@ -400,7 +402,7 @@ func (p *Parser) parseDependencies(deps []pomDependency, props map[string]string // Resolve dependencies d = d.Resolve(props, depManagement, rootDepManagement) - if (d.Scope != "" && d.Scope != "compile" && d.Scope != "runtime") || d.Optional { + if (d.Scope != "" && d.Scope != "compile" && d.Scope != "runtime" && d.Scope != "test") || d.Optional { continue } diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go index 934085d5d536..77a47b5ecdac 100644 --- a/pkg/dependency/parser/java/pom/parse_test.go +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -61,6 +61,19 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + ID: "org.example:example-test:2.0.0", + Name: "org.example:example-test", + Version: "2.0.0", + Relationship: ftypes.RelationshipDirect, + Dev: true, + Locations: ftypes.Locations{ + { + StartLine: 49, + EndLine: 54, + }, + }, + }, }, wantDeps: []ftypes.Dependency{ { @@ -68,6 +81,7 @@ func TestPom_Parse(t *testing.T) { DependsOn: []string{ "org.example:example-api:1.7.30", "org.example:example-runtime:1.0.0", + "org.example:example-test:2.0.0", }, }, }, @@ -109,6 +123,19 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + ID: "org.example:example-test:2.0.0", + Name: "org.example:example-test", + Version: "2.0.0", + Relationship: ftypes.RelationshipDirect, + Dev: true, + Locations: ftypes.Locations{ + { + StartLine: 49, + EndLine: 54, + }, + }, + }, }, wantDeps: []ftypes.Dependency{ { @@ -116,6 +143,7 @@ func TestPom_Parse(t *testing.T) { DependsOn: []string{ "org.example:example-api:1.7.30", "org.example:example-runtime:1.0.0", + "org.example:example-test:2.0.0", }, }, }, diff --git a/pkg/dependency/parser/java/pom/pom.go b/pkg/dependency/parser/java/pom/pom.go index 889d107c3c6c..d27f995217d6 100644 --- a/pkg/dependency/parser/java/pom/pom.go +++ b/pkg/dependency/parser/java/pom/pom.go @@ -303,6 +303,7 @@ func (d pomDependency) ToArtifact(opts analysisOptions) artifact { Exclusions: exclusions, Locations: locations, Relationship: ftypes.RelationshipIndirect, // default + Test: d.Scope == "test", } } diff --git a/pkg/dependency/parser/java/pom/testdata/happy/pom.xml b/pkg/dependency/parser/java/pom/testdata/happy/pom.xml index 1f3c9697a17d..9dfc1c75bd65 100644 --- a/pkg/dependency/parser/java/pom/testdata/happy/pom.xml +++ b/pkg/dependency/parser/java/pom/testdata/happy/pom.xml @@ -46,5 +46,11 @@ 999 provided + + org.example + example-test + 2.0.0 + test + From f80183c1139b21bb95bc64e216358f4a76001a65 Mon Sep 17 00:00:00 2001 From: psibre Date: Tue, 3 Sep 2024 10:31:55 +0200 Subject: [PATCH 330/352] fix(license): add license handling to JUnit template (#7409) --- contrib/junit.tpl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/contrib/junit.tpl b/contrib/junit.tpl index 08e649b9eb00..b01bb9fd22dd 100644 --- a/contrib/junit.tpl +++ b/contrib/junit.tpl @@ -33,5 +33,16 @@ {{- end }} + +{{- if .Licenses }} + {{- $licenses := len .Licenses }} + {{ range .Licenses }} + + + + {{- end }} + +{{- end }} + {{- end }} From 2d80769c34b118851640411fff9dac0b3e353e82 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:42:41 +0600 Subject: [PATCH 331/352] feat(go): use `toolchain` as `stdlib` version for `go.mod` files (#7163) --- docs/docs/coverage/language/golang.md | 21 +++- pkg/dependency/parser/golang/mod/parse.go | 81 +++++++++++-- .../parser/golang/mod/parse_test.go | 108 +++++++++++++++++- .../parser/golang/mod/parse_testcase.go | 62 ++++++++-- .../parser/golang/mod/testdata/normal/go.mod | 12 +- .../parser/golang/mod/testdata/normal/go.sum | 74 ++---------- .../parser/golang/mod/testdata/normal/main.go | 4 +- pkg/fanal/analyzer/language/golang/mod/mod.go | 4 +- 8 files changed, 270 insertions(+), 96 deletions(-) diff --git a/docs/docs/coverage/language/golang.md b/docs/docs/coverage/language/golang.md index cf3e160a608b..f2cbff03255a 100644 --- a/docs/docs/coverage/language/golang.md +++ b/docs/docs/coverage/language/golang.md @@ -18,7 +18,7 @@ The table below provides an outline of the features Trivy offers. | Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | Stdlib | [Detection Priority][detection-priority] | |----------|:-----------:|:-----------------|:------------------------------------:|:------:|:----------------------------------------:| -| Modules | ✅ | Include | ✅[^2] | - | - | +| Modules | ✅ | Include | ✅[^2] | ✅[^6] | [✅](#stdlib) | | Binaries | ✅ | Exclude | - | ✅[^4] | Not needed | !!! note @@ -65,6 +65,23 @@ To identify licenses and dependency relationships, you need to download modules such as `go mod download`, `go mod tidy`, etc. Trivy traverses `$GOPATH/pkg/mod` and collects those extra information. +#### stdlib +If [--detection-priority comprehensive][detection-priority] is passed, Trivy determines the minimum version of `Go` and saves it as a `stdlib` dependency. + +By default, `Go` selects the higher version from of `toolchan` or local version of `Go`. +See [toolchain] for more details. + +To obtain reproducible scan results Trivy doesn't check the local version of `Go`. +Trivy shows the minimum required version for the `go.mod` file, obtained from `toolchain` line (or from the `go` line, if `toolchain` line is omitted). + +!!! note + Trivy detects `stdlib` only for `Go` 1.21 or higher. + + The version from the `go` line (for `Go` 1.20 or early) is not a minimum required version. + For details, see [this](https://go.googlesource.com/proposal/+/master/design/57001-gotoolchain.md). + + + ### Go binaries Trivy scans binaries built by Go, which include [module information](https://tip.golang.org/doc/go1.18#go-version). If there is a Go binary in your container image, Trivy automatically finds and scans it. @@ -93,6 +110,8 @@ empty if it cannot do so[^5]. For the second case, the version of such packages [^3]: See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477 [^4]: Identify the Go version used to compile the binary and detect its vulnerabilities [^5]: See https://github.com/golang/go/issues/63432#issuecomment-1751610604 +[^6]: Only available if `toolchain` directive exists [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies +[toolchain]: https://go.dev/doc/toolchain [detection-priority]: ../../scanner/vulnerability.md#detection-priority diff --git a/pkg/dependency/parser/golang/mod/parse.go b/pkg/dependency/parser/golang/mod/parse.go index 508da6911521..bbf42926a766 100644 --- a/pkg/dependency/parser/golang/mod/parse.go +++ b/pkg/dependency/parser/golang/mod/parse.go @@ -29,12 +29,14 @@ var ( ) type Parser struct { - replace bool // 'replace' represents if the 'replace' directive should be taken into account. + replace bool // 'replace' represents if the 'replace' directive should be taken into account. + useMinVersion bool } -func NewParser(replace bool) *Parser { +func NewParser(replace, useMinVersion bool) *Parser { return &Parser{ - replace: replace, + replace: replace, + useMinVersion: useMinVersion, } } @@ -80,7 +82,20 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc skipIndirect := true if modFileParsed.Go != nil { // Old go.mod file may not include the go version. Go version for these files is less than 1.17 - skipIndirect = lessThan117(modFileParsed.Go.Version) + skipIndirect = lessThan(modFileParsed.Go.Version, 1, 17) + } + + // Use minimal required go version from `toolchain` line (or from `go` line if `toolchain` is omitted) as `stdlib`. + // Show `stdlib` only with `useMinVersion` flag. + if p.useMinVersion { + if toolchainVer := toolchainVersion(modFileParsed.Toolchain, modFileParsed.Go); toolchainVer != "" { + pkgs["stdlib"] = ftypes.Package{ + ID: packageID("stdlib", toolchainVer), + Name: "stdlib", + Version: toolchainVer, + Relationship: ftypes.RelationshipDirect, // Considered a direct dependency as the main module depends on the standard packages. + } + } } // Main module @@ -150,8 +165,12 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc return lo.Values(pkgs), nil, nil } -// Check if the Go version is less than 1.17 -func lessThan117(ver string) bool { +// lessThan checks if the Go version is less than `.` +func lessThan(ver string, majorVer, minorVer int) bool { + if ver == "" { + return false + } + ss := strings.Split(ver, ".") if len(ss) != 2 { return false @@ -165,7 +184,55 @@ func lessThan117(ver string) bool { return false } - return major <= 1 && minor < 17 + return major <= majorVer && minor < minorVer +} + +// toolchainVersion returns version from `toolchain`. +// If `toolchain` is omitted - return version from `go` line (if it is version in toolchain format) +// cf. https://go.dev/doc/toolchain +func toolchainVersion(toolchain *modfile.Toolchain, goVer *modfile.Go) string { + if toolchain != nil && toolchain.Name != "" { + // cf. https://go.dev/doc/toolchain#name + // `dropping the initial go and discarding off any suffix beginning with -` + // e.g. `go1.22.5-custom` => `1.22.5` + name, _, _ := strings.Cut(toolchain.Name, "-") + return strings.TrimPrefix(name, "go") + } + + if goVer != nil { + return toolchainVersionFromGoLine(goVer.Version) + } + return "" +} + +// toolchainVersionFromGoLine detects Go version from `go` line if `toolchain` line is omitted. +// `go` line supports the following formats: +// cf. https://go.dev/doc/toolchain#version +// - `1.N.P`. e.g. `1.22.0` +// - `1.N`. e.g. `1.22` +// - `1.NrcR`. e.g. `1.22rc1` +// - `1.NbetaR`. e.g. `1.18beta1` - only for Go 1.20 or earlier +func toolchainVersionFromGoLine(ver string) string { + var majorMinorVer string + + if ss := strings.Split(ver, "."); len(ss) > 2 { // `1.N.P` + majorMinorVer = strings.Join(ss[:2], ".") + } else if v, _, rcFound := strings.Cut(ver, "rc"); rcFound { // `1.NrcR` + majorMinorVer = v + } else { // `1.N` + majorMinorVer = ver + // Add `.0` suffix to avoid user confusing. + // See https://github.com/aquasecurity/trivy/pull/7163#discussion_r1682424315 + ver = v + ".0" + } + + // `toolchain` has been added in go 1.21. + // So we need to check that Go version is 1.21 or higher. + // cf. https://github.com/aquasecurity/trivy/pull/7163#discussion_r1682424315 + if lessThan(majorMinorVer, 1, 21) { + return "" + } + return ver } func packageID(name, version string) string { diff --git a/pkg/dependency/parser/golang/mod/parse_test.go b/pkg/dependency/parser/golang/mod/parse_test.go index 598cb3fe70f3..10bda7f01144 100644 --- a/pkg/dependency/parser/golang/mod/parse_test.go +++ b/pkg/dependency/parser/golang/mod/parse_test.go @@ -7,22 +7,31 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/mod/modfile" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParse(t *testing.T) { tests := []struct { - name string - file string - replace bool - want []ftypes.Package + name string + file string + replace bool + useMinVersion bool + want []ftypes.Package }{ + { + name: "normal with stdlib", + file: "testdata/normal/go.mod", + replace: true, + useMinVersion: true, + want: GoModNormal, + }, { name: "normal", file: "testdata/normal/go.mod", replace: true, - want: GoModNormal, + want: GoModNormalWithoutStdlib, }, { name: "without go version", @@ -85,7 +94,7 @@ func TestParse(t *testing.T) { f, err := os.Open(tt.file) require.NoError(t, err) - got, _, err := NewParser(tt.replace).Parse(f) + got, _, err := NewParser(tt.replace, tt.useMinVersion).Parse(f) require.NoError(t, err) sort.Sort(ftypes.Packages(got)) @@ -95,3 +104,90 @@ func TestParse(t *testing.T) { }) } } + +func TestToolchainVersion(t *testing.T) { + tests := []struct { + name string + modFile modfile.File + want string + }{ + { + name: "version from toolchain line", + modFile: modfile.File{ + Toolchain: &modfile.Toolchain{ + Name: "1.21.1", + }, + }, + want: "1.21.1", + }, + { + name: "version from toolchain line with suffix", + modFile: modfile.File{ + Toolchain: &modfile.Toolchain{ + Name: "1.21.1-custom", + }, + }, + want: "1.21.1", + }, + { + name: "'1.18rc1' from go line", + modFile: modfile.File{ + Go: &modfile.Go{ + Version: "1.18rc1", + }, + }, + want: "", + }, + { + name: "'1.18.1' from go line", + modFile: modfile.File{ + Go: &modfile.Go{ + Version: "1.18.1", + }, + }, + want: "", + }, + { + name: "'1.20' from go line", + modFile: modfile.File{ + Go: &modfile.Go{ + Version: "1.20", + }, + }, + want: "", + }, + { + name: "'1.21' from go line", + modFile: modfile.File{ + Go: &modfile.Go{ + Version: "1.21", + }, + }, + want: "1.21.0", + }, + { + name: "'1.21rc1' from go line", + modFile: modfile.File{ + Go: &modfile.Go{ + Version: "1.21rc1", + }, + }, + want: "1.21rc1", + }, + { + name: "'1.21.2' from go line", + modFile: modfile.File{ + Go: &modfile.Go{ + Version: "1.21.2", + }, + }, + want: "1.21.2", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, toolchainVersion(tt.modFile.Toolchain, tt.modFile.Go)) + }) + } +} diff --git a/pkg/dependency/parser/golang/mod/parse_testcase.go b/pkg/dependency/parser/golang/mod/parse_testcase.go index 5a07b939c549..4671ef3e6854 100644 --- a/pkg/dependency/parser/golang/mod/parse_testcase.go +++ b/pkg/dependency/parser/golang/mod/parse_testcase.go @@ -1,6 +1,10 @@ package mod -import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +import ( + "slices" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) var ( // execute go mod tidy in normal folder @@ -17,37 +21,71 @@ var ( }, }, { - ID: "github.com/aquasecurity/go-dep-parser@v0.0.0-20211224170007-df43bca6b6ff", - Name: "github.com/aquasecurity/go-dep-parser", - Version: "0.0.0-20211224170007-df43bca6b6ff", + ID: "stdlib@v1.22.5", + Name: "stdlib", + Version: "1.22.5", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "github.com/aquasecurity/go-version@v0.0.0-20240603093900-cf8a8d29271d", + Name: "github.com/aquasecurity/go-version", + Version: "0.0.0-20240603093900-cf8a8d29271d", Relationship: ftypes.RelationshipDirect, ExternalReferences: []ftypes.ExternalRef{ { Type: ftypes.RefVCS, - URL: "https://github.com/aquasecurity/go-dep-parser", + URL: "https://github.com/aquasecurity/go-version", }, }, }, { - ID: "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1", - Name: "golang.org/x/xerrors", - Version: "0.0.0-20200804184101-5ec99f83aff1", + ID: "github.com/davecgh/go-spew@v1.1.2-0.20180830191138-d8f796af33cc", + Name: "github.com/davecgh/go-spew", + Version: "1.1.2-0.20180830191138-d8f796af33cc", + Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ + { + Type: ftypes.RefVCS, + URL: "https://github.com/davecgh/go-spew", + }, + }, + }, + { + ID: "github.com/pmezard/go-difflib@v1.0.1-0.20181226105442-5d4384ee4fb2", + Name: "github.com/pmezard/go-difflib", + Version: "1.0.1-0.20181226105442-5d4384ee4fb2", Relationship: ftypes.RelationshipIndirect, + ExternalReferences: []ftypes.ExternalRef{ + { + Type: ftypes.RefVCS, + URL: "https://github.com/pmezard/go-difflib", + }, + }, }, { - ID: "gopkg.in/yaml.v3@v3.0.0-20210107192922-496545a6307b", - Name: "gopkg.in/yaml.v3", - Version: "3.0.0-20210107192922-496545a6307b", + ID: "github.com/stretchr/testify@v1.9.0", + Name: "github.com/stretchr/testify", + Version: "1.9.0", Relationship: ftypes.RelationshipIndirect, ExternalReferences: []ftypes.ExternalRef{ { Type: ftypes.RefVCS, - URL: "https://github.com/go-yaml/yaml", + URL: "https://github.com/stretchr/testify", }, }, }, + { + ID: "golang.org/x/xerrors@v0.0.0-20231012003039-104605ab7028", + Name: "golang.org/x/xerrors", + Version: "0.0.0-20231012003039-104605ab7028", + Relationship: ftypes.RelationshipIndirect, + }, } + GoModNormalWithoutStdlib = slices.DeleteFunc(slices.Clone(GoModNormal), func(f ftypes.Package) bool { + return f.Name == "stdlib" + }) + // execute go mod tidy in replaced folder GoModReplaced = []ftypes.Package{ { diff --git a/pkg/dependency/parser/golang/mod/testdata/normal/go.mod b/pkg/dependency/parser/golang/mod/testdata/normal/go.mod index 9d48b25e0079..564414c10583 100644 --- a/pkg/dependency/parser/golang/mod/testdata/normal/go.mod +++ b/pkg/dependency/parser/golang/mod/testdata/normal/go.mod @@ -1,10 +1,14 @@ module github.com/org/repo -go 1.17 +go 1.22.0 -require github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff +toolchain go1.22.5 + +require github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d require ( - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/stretchr/testify v1.9.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect ) diff --git a/pkg/dependency/parser/golang/mod/testdata/normal/go.sum b/pkg/dependency/parser/golang/mod/testdata/normal/go.sum index 032bb4727cfe..eefc0690f47d 100644 --- a/pkg/dependency/parser/golang/mod/testdata/normal/go.sum +++ b/pkg/dependency/parser/golang/mod/testdata/normal/go.sum @@ -1,62 +1,12 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff h1:JCKEV3TgUNh9fn+8hXyIdsF9yErA0rUbCkgt2flRKt4= -github.com/aquasecurity/go-dep-parser v0.0.0-20211224170007-df43bca6b6ff/go.mod h1:8fJ//Ob6/03lxbn4xa1F+G/giVtiVLxnZNpBp5xOxNk= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d h1:4zour5Sh9chOg+IqIinIcJ3qtr3cIf8FdFY6aArlXBw= +github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d/go.mod h1:1cPOp4BaQZ1G2F5fnw4dFz6pkOyXJI9KTuak8ghIl3U= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/dependency/parser/golang/mod/testdata/normal/main.go b/pkg/dependency/parser/golang/mod/testdata/normal/main.go index 0b51fb861f32..fe31e68d5da8 100644 --- a/pkg/dependency/parser/golang/mod/testdata/normal/main.go +++ b/pkg/dependency/parser/golang/mod/testdata/normal/main.go @@ -3,11 +3,11 @@ package main import ( "log" - "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" + "github.com/aquasecurity/go-version/pkg/version" ) func main() { - if _, err := mod.Parse(nil); err != nil { + if _, err := version.Parse("v0.1.2"); err != nil { log.Fatal(err) } } diff --git a/pkg/fanal/analyzer/language/golang/mod/mod.go b/pkg/fanal/analyzer/language/golang/mod/mod.go index 96d40ba1c954..398511fdc63c 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod.go @@ -56,9 +56,9 @@ type gomodAnalyzer struct { func newGoModAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { return &gomodAnalyzer{ - modParser: mod.NewParser(true), // Only the root module should replace + modParser: mod.NewParser(true, opt.DetectionPriority == types.PriorityComprehensive), // Only the root module should replace sumParser: sum.NewParser(), - leafModParser: mod.NewParser(false), + leafModParser: mod.NewParser(false, false), // Don't detect stdlib for non-root go.mod files licenseClassifierConfidenceLevel: opt.LicenseScannerOption.ClassifierConfidenceLevel, logger: log.WithPrefix("golang"), }, nil From 7a1e8b85b4acf912c6748bf23fee71dda002129d Mon Sep 17 00:00:00 2001 From: Aqua Security automated builds <54269356+aqua-bot@users.noreply.github.com> Date: Wed, 4 Sep 2024 02:51:23 +0300 Subject: [PATCH 332/352] release: v0.55.0 [main] (#7271) --- .release-please-manifest.json | 2 +- CHANGELOG.md | 65 +++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8f1dfd40939e..aeec62f4b8cc 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{".":"0.54.0"} +{".":"0.55.0"} diff --git a/CHANGELOG.md b/CHANGELOG.md index 04a57a25147e..19df5eb64851 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,70 @@ # Changelog +## [0.55.0](https://github.com/aquasecurity/trivy/compare/v0.54.0...v0.55.0) (2024-09-03) + + +### ⚠ BREAKING CHANGES + +* **cli:** delete deprecated SBOM flags ([#7266](https://github.com/aquasecurity/trivy/issues/7266)) + +### Features + +* **cli:** delete deprecated SBOM flags ([#7266](https://github.com/aquasecurity/trivy/issues/7266)) ([7024572](https://github.com/aquasecurity/trivy/commit/70245721372720027b7089bd61c693df48add865)) +* **go:** use `toolchain` as `stdlib` version for `go.mod` files ([#7163](https://github.com/aquasecurity/trivy/issues/7163)) ([2d80769](https://github.com/aquasecurity/trivy/commit/2d80769c34b118851640411fff9dac0b3e353e82)) +* **java:** add `test` scope support for `pom.xml` files ([#7414](https://github.com/aquasecurity/trivy/issues/7414)) ([2d97700](https://github.com/aquasecurity/trivy/commit/2d97700d10665142d2f66d7910202bec82116209)) +* **misconf:** Add support for using spec from on-disk bundle ([#7179](https://github.com/aquasecurity/trivy/issues/7179)) ([be86126](https://github.com/aquasecurity/trivy/commit/be861265cafc89787fda09c59b2ef175e3d04204)) +* **misconf:** ignore duplicate checks ([#7317](https://github.com/aquasecurity/trivy/issues/7317)) ([9ef05fc](https://github.com/aquasecurity/trivy/commit/9ef05fc6b171a264516a025b0b0bcbbc8cff10bc)) +* **misconf:** iterator argument support for dynamic blocks ([#7236](https://github.com/aquasecurity/trivy/issues/7236)) ([fe92072](https://github.com/aquasecurity/trivy/commit/fe9207255a4f7f984ec1447f8a9219ae60e560c4)) +* **misconf:** port and protocol support for EC2 networks ([#7146](https://github.com/aquasecurity/trivy/issues/7146)) ([98e136e](https://github.com/aquasecurity/trivy/commit/98e136eb7baa2b66f4233d96875c1490144e1594)) +* **misconf:** scanning support for YAML and JSON ([#7311](https://github.com/aquasecurity/trivy/issues/7311)) ([efdbd8f](https://github.com/aquasecurity/trivy/commit/efdbd8f19ab0ab0c3b48293d43e51c81b7b03b89)) +* **misconf:** support for ignore by nested attributes ([#7205](https://github.com/aquasecurity/trivy/issues/7205)) ([44e4686](https://github.com/aquasecurity/trivy/commit/44e468603d44b077cc4606327fb3e7d7ca435e05)) +* **misconf:** support for policy and bucket grants ([#7284](https://github.com/aquasecurity/trivy/issues/7284)) ([a817fae](https://github.com/aquasecurity/trivy/commit/a817fae85b7272b391b737ec86673a7cab722bae)) +* **misconf:** variable support for Terraform Plan ([#7228](https://github.com/aquasecurity/trivy/issues/7228)) ([db2c955](https://github.com/aquasecurity/trivy/commit/db2c95598da098ca610825089eb4ab63b789b215)) +* **python:** use minimum version for pip packages ([#7348](https://github.com/aquasecurity/trivy/issues/7348)) ([e9b43f8](https://github.com/aquasecurity/trivy/commit/e9b43f81e67789b067352fcb6aa55bc9478bc518)) +* **report:** export modified findings in JSON ([#7383](https://github.com/aquasecurity/trivy/issues/7383)) ([7aea79d](https://github.com/aquasecurity/trivy/commit/7aea79dd93cfb61453766dbbb2e3fc0fbd317852)) +* **sbom:** set User-Agent header on requests to Rekor ([#7396](https://github.com/aquasecurity/trivy/issues/7396)) ([af1d257](https://github.com/aquasecurity/trivy/commit/af1d257730422d238871beb674767f8f83c5d06a)) +* **server:** add internal `--path-prefix` flag for client/server mode ([#7321](https://github.com/aquasecurity/trivy/issues/7321)) ([24a4563](https://github.com/aquasecurity/trivy/commit/24a45636867b893ff54c5ce07197f3b5c6db1d9b)) +* **server:** Make Trivy Server Multiplexer Exported ([#7389](https://github.com/aquasecurity/trivy/issues/7389)) ([4c6e8ca](https://github.com/aquasecurity/trivy/commit/4c6e8ca9cc9591799907cc73075f2d740e303b8f)) +* **vm:** Support direct filesystem ([#7058](https://github.com/aquasecurity/trivy/issues/7058)) ([45b3f34](https://github.com/aquasecurity/trivy/commit/45b3f344042bcd90ca63ab696b69bff0e9ab4e36)) +* **vm:** support the Ext2/Ext3 filesystems ([#6983](https://github.com/aquasecurity/trivy/issues/6983)) ([35c60f0](https://github.com/aquasecurity/trivy/commit/35c60f030fa48de8d8e57958e5ba379814126831)) +* **vuln:** Add `--detection-priority` flag for accuracy tuning ([#7288](https://github.com/aquasecurity/trivy/issues/7288)) ([fd8348d](https://github.com/aquasecurity/trivy/commit/fd8348d610f20c6c33da81cd7b0e7d5504ce26be)) + + +### Bug Fixes + +* **aws:** handle ECR repositories in different regions ([#6217](https://github.com/aquasecurity/trivy/issues/6217)) ([feaef96](https://github.com/aquasecurity/trivy/commit/feaef9699df5d8ca399770e701a59d7c0ff979a3)) +* **flag:** incorrect behavior for deprected flag `--clear-cache` ([#7281](https://github.com/aquasecurity/trivy/issues/7281)) ([2a0e529](https://github.com/aquasecurity/trivy/commit/2a0e529c36057b572119815af59c28e4790034ca)) +* **helm:** explicitly define `kind` and `apiVersion` of `volumeClaimTemplate` element ([#7362](https://github.com/aquasecurity/trivy/issues/7362)) ([da4ebfa](https://github.com/aquasecurity/trivy/commit/da4ebfa1a741f3f8b0b43289b4028afe763f7d43)) +* **java:** Return error when trying to find a remote pom to avoid segfault ([#7275](https://github.com/aquasecurity/trivy/issues/7275)) ([49d5270](https://github.com/aquasecurity/trivy/commit/49d5270163e305f88fedcf50412973736e69dc69)) +* **license:** add license handling to JUnit template ([#7409](https://github.com/aquasecurity/trivy/issues/7409)) ([f80183c](https://github.com/aquasecurity/trivy/commit/f80183c1139b21bb95bc64e216358f4a76001a65)) +* logger initialization before flags parsing ([#7372](https://github.com/aquasecurity/trivy/issues/7372)) ([c929290](https://github.com/aquasecurity/trivy/commit/c929290c3c0e4e91337264d69e75ccb60522bc65)) +* **misconf:** change default TLS values for the Azure storage account ([#7345](https://github.com/aquasecurity/trivy/issues/7345)) ([aadb090](https://github.com/aquasecurity/trivy/commit/aadb09078843250c66087f46db9a2aa48094a118)) +* **misconf:** do not filter Terraform plan JSON by name ([#7406](https://github.com/aquasecurity/trivy/issues/7406)) ([9d7264a](https://github.com/aquasecurity/trivy/commit/9d7264af8e85bcc0dba600b8366d0470d455251c)) +* **misconf:** do not recreate filesystem map ([#7416](https://github.com/aquasecurity/trivy/issues/7416)) ([3a5d091](https://github.com/aquasecurity/trivy/commit/3a5d091759564496992a83fb2015a21c84a22213)) +* **misconf:** do not register Rego libs in checks registry ([#7420](https://github.com/aquasecurity/trivy/issues/7420)) ([a5aa63e](https://github.com/aquasecurity/trivy/commit/a5aa63eff7e229744090f9ad300c1bec3259397e)) +* **misconf:** do not set default value for default_cache_behavior ([#7234](https://github.com/aquasecurity/trivy/issues/7234)) ([f0ed5e4](https://github.com/aquasecurity/trivy/commit/f0ed5e4ced7e60af35c88d5d084aa4b7237f4973)) +* **misconf:** fix infer type for null value ([#7424](https://github.com/aquasecurity/trivy/issues/7424)) ([0cac3ac](https://github.com/aquasecurity/trivy/commit/0cac3ac7075017628a21a7990941df04cbc16dbe)) +* **misconf:** init frameworks before updating them ([#7376](https://github.com/aquasecurity/trivy/issues/7376)) ([b65b32d](https://github.com/aquasecurity/trivy/commit/b65b32ddfa6fc62ac81ad9fa580e1f5a327864f5)) +* **misconf:** load only submodule if it is specified in source ([#7112](https://github.com/aquasecurity/trivy/issues/7112)) ([a4180bd](https://github.com/aquasecurity/trivy/commit/a4180bddd43d86e479edf0afe0c362021d071482)) +* **misconf:** support deprecating for Go checks ([#7377](https://github.com/aquasecurity/trivy/issues/7377)) ([2a6c7ab](https://github.com/aquasecurity/trivy/commit/2a6c7ab3b338ce4a8f99d6ac3508c2531dcbe812)) +* **misconf:** use module to log when metadata retrieval fails ([#7405](https://github.com/aquasecurity/trivy/issues/7405)) ([0799770](https://github.com/aquasecurity/trivy/commit/0799770b8827a8276ad0d6d9ac7e0381c286757c)) +* **misconf:** wrap Azure PortRange in iac types ([#7357](https://github.com/aquasecurity/trivy/issues/7357)) ([c5c62d5](https://github.com/aquasecurity/trivy/commit/c5c62d5ff05420321f9cdbfb93e2591e0866a342)) +* **nodejs:** check all `importers` to detect dev deps from pnpm-lock.yaml file ([#7387](https://github.com/aquasecurity/trivy/issues/7387)) ([fd9ed3a](https://github.com/aquasecurity/trivy/commit/fd9ed3a330bc66e229bcbdc262dc296a3bf01f54)) +* **plugin:** do not call GitHub content API for releases and tags ([#7274](https://github.com/aquasecurity/trivy/issues/7274)) ([b3ee6da](https://github.com/aquasecurity/trivy/commit/b3ee6dac269bd7847674f3ce985a5ff7f8f0ba38)) +* **report:** escape `Message` field in `asff.tpl` template ([#7401](https://github.com/aquasecurity/trivy/issues/7401)) ([dd9733e](https://github.com/aquasecurity/trivy/commit/dd9733e950d3127aa2ac90c45ec7e2b88a2b47ca)) +* safely check if the directory exists ([#7353](https://github.com/aquasecurity/trivy/issues/7353)) ([05a8297](https://github.com/aquasecurity/trivy/commit/05a829715f99cd90b122c64cd2f40157854e467b)) +* **sbom:** use `NOASSERTION` for licenses fields in SPDX formats ([#7403](https://github.com/aquasecurity/trivy/issues/7403)) ([c96dcdd](https://github.com/aquasecurity/trivy/commit/c96dcdd440a14cdd1b01ac473b2c15e4698e387b)) +* **secret:** use `.eyJ` keyword for JWT secret ([#7410](https://github.com/aquasecurity/trivy/issues/7410)) ([bf64003](https://github.com/aquasecurity/trivy/commit/bf64003ac8b209f34b88f228918a96d4f9dac5e0)) +* **secret:** use only line with secret for long secret lines ([#7412](https://github.com/aquasecurity/trivy/issues/7412)) ([391448a](https://github.com/aquasecurity/trivy/commit/391448aba9fcb0a4138225e5ab305e4e6707c603)) +* **terraform:** add aws_region name to presets ([#7184](https://github.com/aquasecurity/trivy/issues/7184)) ([bb2e26a](https://github.com/aquasecurity/trivy/commit/bb2e26a0ab707b718f6a890cbc87e2492298b6e5)) + + +### Performance Improvements + +* **misconf:** do not convert contents of a YAML file to string ([#7292](https://github.com/aquasecurity/trivy/issues/7292)) ([85dadf5](https://github.com/aquasecurity/trivy/commit/85dadf56265647c000191561db10b08a4948c140)) +* **misconf:** optimize work with context ([#6968](https://github.com/aquasecurity/trivy/issues/6968)) ([2b6d8d9](https://github.com/aquasecurity/trivy/commit/2b6d8d9227fb6ecc9386a14333964c23c0370a52)) +* **misconf:** use json.Valid to check validity of JSON ([#7308](https://github.com/aquasecurity/trivy/issues/7308)) ([c766831](https://github.com/aquasecurity/trivy/commit/c766831069e188226efafeec184e41498685ed85)) + ## [0.54.0](https://github.com/aquasecurity/trivy/compare/v0.53.0...v0.54.0) (2024-07-30) From 4926da79de901fba73819d71845ec0355b68ae0f Mon Sep 17 00:00:00 2001 From: afdesk Date: Thu, 5 Sep 2024 16:20:29 +0600 Subject: [PATCH 333/352] fix(license): stop spliting a long license text (#7336) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- .../parser/python/packaging/parse.go | 2 +- .../language/python/packaging/packaging.go | 4 +- pkg/licensing/normalize.go | 45 +++++++ pkg/licensing/normalize_test.go | 7 ++ pkg/rpc/convert.go | 2 + pkg/rpc/convert_test.go | 4 + pkg/scanner/local/scan.go | 48 +++++--- pkg/scanner/local/scan_test.go | 38 ++++++ pkg/types/license.go | 3 + rpc/common/service.pb.go | 110 ++++++++++-------- rpc/common/service.proto | 1 + 11 files changed, 192 insertions(+), 72 deletions(-) diff --git a/pkg/dependency/parser/python/packaging/parse.go b/pkg/dependency/parser/python/packaging/parse.go index fa1758308860..58ecac200db5 100644 --- a/pkg/dependency/parser/python/packaging/parse.go +++ b/pkg/dependency/parser/python/packaging/parse.go @@ -80,7 +80,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc } if license == "" && h.Get("License-File") != "" { - license = "file://" + h.Get("License-File") + license = licensing.LicenseFilePrefix + h.Get("License-File") } return []ftypes.Package{ diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging.go b/pkg/fanal/analyzer/language/python/packaging/packaging.go index 51fd585d8a6c..73e5f446bc40 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging.go @@ -122,11 +122,11 @@ func (a packagingAnalyzer) fillAdditionalData(fsys fs.FS, app *types.Application // Parser adds `file://` prefix to filepath from `License-File` field // We need to read this file to find licenses // Otherwise, this is the name of the license - if !strings.HasPrefix(lic, "file://") { + if !strings.HasPrefix(lic, licensing.LicenseFilePrefix) { licenses = append(licenses, lic) continue } - licenseFilePath := path.Base(strings.TrimPrefix(lic, "file://")) + licenseFilePath := path.Base(strings.TrimPrefix(lic, licensing.LicenseFilePrefix)) findings, err := classifyLicense(app.FilePath, licenseFilePath, a.licenseClassifierConfidenceLevel, fsys) if err != nil { diff --git a/pkg/licensing/normalize.go b/pkg/licensing/normalize.go index 07105e329357..4ca8eab00030 100644 --- a/pkg/licensing/normalize.go +++ b/pkg/licensing/normalize.go @@ -159,6 +159,12 @@ var mapping = map[string]string{ "PUBLIC DOMAIN": Unlicense, } +const ( + LicenseTextPrefix = "text://" + LicenseFilePrefix = "file://" + CustomLicensePrefix = "CUSTOM License" +) + // pythonLicenseExceptions contains licenses that we cannot separate correctly using our logic. // first word after separator (or/and) => license name var pythonLicenseExceptions = map[string]string{ @@ -179,6 +185,39 @@ var pythonLicenseExceptions = map[string]string{ var licenseSplitRegexp = regexp.MustCompile("(,?[_ ]+(?:or|and)[_ ]+)|(,[ ]*)") +// Typical keywords for license texts +var licenseTextKeywords = []string{ + "http://", + "https://", + "(c)", + "as-is", + ";", + "hereby", + "permission to use", + "permission is", + "use in source", + "use, copy, modify", + "using", +} + +func isLicenseText(str string) bool { + for _, keyword := range licenseTextKeywords { + if strings.Contains(str, keyword) { + return true + } + } + return false +} + +func TrimLicenseText(text string) string { + s := strings.Split(text, " ") + n := len(s) + if n > 3 { + n = 3 + } + return strings.Join(s[:n], " ") + "..." +} + func Normalize(name string) string { name = strings.TrimSpace(name) if l, ok := mapping[strings.ToUpper(name)]; ok { @@ -191,6 +230,12 @@ func SplitLicenses(str string) []string { if str == "" { return nil } + if isLicenseText(strings.ToLower(str)) { + return []string{ + LicenseTextPrefix + str, + } + } + var licenses []string for _, maybeLic := range licenseSplitRegexp.Split(str, -1) { lower := strings.ToLower(maybeLic) diff --git a/pkg/licensing/normalize_test.go b/pkg/licensing/normalize_test.go index 28934f4f2340..16fa9eac6250 100644 --- a/pkg/licensing/normalize_test.go +++ b/pkg/licensing/normalize_test.go @@ -97,6 +97,13 @@ func TestSplitLicenses(t *testing.T) { "Historical Permission Notice and Disclaimer (HPND)", }, }, + { + name: "License text", + license: "* Permission to use this software in any way is granted without", + licenses: []string{ + "text://* Permission to use this software in any way is granted without", + }, + }, } for _, tt := range tests { diff --git a/pkg/rpc/convert.go b/pkg/rpc/convert.go index dcd81ce85ded..1662877bd597 100644 --- a/pkg/rpc/convert.go +++ b/pkg/rpc/convert.go @@ -436,6 +436,7 @@ func ConvertFromRPCDetectedLicenses(rpcLicenses []*common.DetectedLicense) []typ PkgName: l.PkgName, FilePath: l.FilePath, Name: l.Name, + Text: l.Text, Confidence: float64(l.Confidence), Link: l.Link, }) @@ -983,6 +984,7 @@ func ConvertToRPCLicenses(licenses []types.DetectedLicense) []*common.DetectedLi PkgName: l.PkgName, FilePath: l.FilePath, Name: l.Name, + Text: l.Text, Confidence: float32(l.Confidence), Link: l.Link, }) diff --git a/pkg/rpc/convert_test.go b/pkg/rpc/convert_test.go index f7c7b3d36d86..6f90c3b5cc8e 100644 --- a/pkg/rpc/convert_test.go +++ b/pkg/rpc/convert_test.go @@ -760,6 +760,7 @@ func TestConvertFromRPCLicenses(t *testing.T) { PkgName: "alpine-baselayout", FilePath: "some-path", Name: "GPL-2.0", + Text: "text://* Permission to use this software in any way is granted without", Confidence: 1, Link: "https://some-link", }, @@ -771,6 +772,7 @@ func TestConvertFromRPCLicenses(t *testing.T) { PkgName: "alpine-baselayout", FilePath: "some-path", Name: "GPL-2.0", + Text: "text://* Permission to use this software in any way is granted without", Confidence: 1, Link: "https://some-link", }, @@ -806,6 +808,7 @@ func TestConvertToRPCLicenses(t *testing.T) { PkgName: "alpine-baselayout", FilePath: "some-path", Name: "GPL-2.0", + Text: "text://* Permission to use this software in any way is granted without", Confidence: 1, Link: "https://some-link", }, @@ -817,6 +820,7 @@ func TestConvertToRPCLicenses(t *testing.T) { PkgName: "alpine-baselayout", FilePath: "some-path", Name: "GPL-2.0", + Text: "text://* Permission to use this software in any way is granted without", Confidence: 1, Link: "https://some-link", }, diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 3b0c719721e2..f34723058e06 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -261,14 +261,7 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions var osPkgLicenses []types.DetectedLicense for _, pkg := range target.Packages { for _, license := range pkg.Licenses { - category, severity := scanner.Scan(license) - osPkgLicenses = append(osPkgLicenses, types.DetectedLicense{ - Severity: severity, - Category: category, - PkgName: pkg.Name, - Name: license, - Confidence: 1.0, - }) + osPkgLicenses = append(osPkgLicenses, toDetectedLicense(scanner, license, pkg.Name, "")) } } results = append(results, types.Result{ @@ -282,17 +275,11 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions var langLicenses []types.DetectedLicense for _, lib := range app.Packages { for _, license := range lib.Licenses { - category, severity := scanner.Scan(license) - langLicenses = append(langLicenses, types.DetectedLicense{ - Severity: severity, - Category: category, - PkgName: lib.Name, - Name: license, - // Lock files use app.FilePath - https://github.com/aquasecurity/trivy/blob/6ccc0a554b07b05fd049f882a1825a0e1e0aabe1/pkg/fanal/types/artifact.go#L245-L246 - // Applications use lib.FilePath - https://github.com/aquasecurity/trivy/blob/6ccc0a554b07b05fd049f882a1825a0e1e0aabe1/pkg/fanal/types/artifact.go#L93-L94 - FilePath: lo.Ternary(lib.FilePath != "", lib.FilePath, app.FilePath), - Confidence: 1.0, - }) + // Lock files use app.FilePath - https://github.com/aquasecurity/trivy/blob/6ccc0a554b07b05fd049f882a1825a0e1e0aabe1/pkg/fanal/types/artifact.go#L245-L246 + // Applications use lib.FilePath - https://github.com/aquasecurity/trivy/blob/6ccc0a554b07b05fd049f882a1825a0e1e0aabe1/pkg/fanal/types/artifact.go#L93-L94 + filePath := lo.Ternary(lib.FilePath != "", lib.FilePath, app.FilePath) + + langLicenses = append(langLicenses, toDetectedLicense(scanner, license, lib.Name, filePath)) } } @@ -390,6 +377,29 @@ func toDetectedMisconfiguration(res ftypes.MisconfResult, defaultSeverity dbType } } +func toDetectedLicense(scanner licensing.Scanner, license, pkgName, filePath string) types.DetectedLicense { + var category ftypes.LicenseCategory + var severity, licenseText string + if strings.HasPrefix(license, licensing.LicenseTextPrefix) { // License text + licenseText = strings.TrimPrefix(license, licensing.LicenseTextPrefix) + category = ftypes.CategoryUnknown + severity = dbTypes.SeverityUnknown.String() + license = licensing.CustomLicensePrefix + ": " + licensing.TrimLicenseText(licenseText) + } else { // License name + category, severity = scanner.Scan(license) + } + + return types.DetectedLicense{ + Severity: severity, + Category: category, + PkgName: pkgName, + FilePath: filePath, + Name: license, + Text: licenseText, + Confidence: 1.0, + } +} + func ShouldScanMisconfigOrRbac(scanners types.Scanners) bool { return scanners.AnyEnabled(types.MisconfigScanner, types.RBACScanner) } diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go index 7d60697fa23a..f3e6cac5d3fa 100644 --- a/pkg/scanner/local/scan_test.go +++ b/pkg/scanner/local/scan_test.go @@ -64,6 +64,25 @@ var ( }, Licenses: []string{"MIT"}, } + python39min = ftypes.Package{ + Name: "python3.9-minimal", + Version: "3.9.1", + FilePath: "/usr/lib/python/site-packages/python3.9-minimal/METADATA", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + Licenses: []string{"text://Redistribution and use in source and binary forms, with or without"}, + } + menuinstPkg = ftypes.Package{ + Name: "menuinst", + Version: "2.0.2", + FilePath: "opt/conda/lib/python3.11/site-packages/menuinst-2.0.2.dist-info/METADATA", + Layer: ftypes.Layer{ + DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", + }, + Licenses: []string{"text://(c) 2016 Continuum Analytics, Inc. / http://continuum.io All Rights Reserved"}, + } + laravelPkg = ftypes.Package{ Name: "laravel/framework", Version: "6.0.0", @@ -225,6 +244,7 @@ func TestScanner_Scan(t *testing.T) { }, Packages: []ftypes.Package{ muslPkg, + python39min, }, Applications: []ftypes.Application{ { @@ -239,6 +259,7 @@ func TestScanner_Scan(t *testing.T) { FilePath: "", Packages: []ftypes.Package{ urllib3Pkg, + menuinstPkg, }, }, }, @@ -257,6 +278,14 @@ func TestScanner_Scan(t *testing.T) { Name: "MIT", Confidence: 1, }, + { + Severity: "UNKNOWN", + Category: "unknown", + PkgName: python39min.Name, + Name: "CUSTOM License: Redistribution and use...", + Text: "Redistribution and use in source and binary forms, with or without", + Confidence: 1, + }, }, }, { @@ -286,6 +315,15 @@ func TestScanner_Scan(t *testing.T) { Name: "MIT", Confidence: 1, }, + { + Severity: "UNKNOWN", + Category: "unknown", + PkgName: menuinstPkg.Name, + FilePath: "opt/conda/lib/python3.11/site-packages/menuinst-2.0.2.dist-info/METADATA", + Name: "CUSTOM License: (c) 2016 Continuum...", + Text: "(c) 2016 Continuum Analytics, Inc. / http://continuum.io All Rights Reserved", + Confidence: 1, + }, }, }, { diff --git a/pkg/types/license.go b/pkg/types/license.go index baca0328a457..22dd6bcb2040 100644 --- a/pkg/types/license.go +++ b/pkg/types/license.go @@ -22,6 +22,9 @@ type DetectedLicense struct { // Name holds a detected license name Name string + // Text holds a long license text if Trivy detects a license name as a license text + Text string + // Confidence is level of the match. The confidence level is between 0.0 and 1.0, with 1.0 indicating an // exact match and 0.0 indicating a complete mismatch Confidence float64 diff --git a/rpc/common/service.pb.go b/rpc/common/service.pb.go index c8d829762956..4c7f0af79464 100644 --- a/rpc/common/service.pb.go +++ b/rpc/common/service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.0 -// protoc v5.26.1 +// protoc v3.19.4 // source: rpc/common/service.proto package common @@ -2084,6 +2084,7 @@ type DetectedLicense struct { Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"` Confidence float32 `protobuf:"fixed32,6,opt,name=confidence,proto3" json:"confidence,omitempty"` Link string `protobuf:"bytes,7,opt,name=link,proto3" json:"link,omitempty"` + Text string `protobuf:"bytes,8,opt,name=text,proto3" json:"text,omitempty"` } func (x *DetectedLicense) Reset() { @@ -2167,6 +2168,13 @@ func (x *DetectedLicense) GetLink() string { return "" } +func (x *DetectedLicense) GetText() string { + if x != nil { + return x.Text + } + return "" +} + type LicenseFile struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2718,7 +2726,7 @@ var file_rpc_common_service_proto_rawDesc = []byte{ 0x64, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x73, 0x22, 0x85, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, + 0x67, 0x73, 0x22, 0x99, 0x02, 0x0a, 0x0f, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, @@ -2734,54 +2742,56 @@ var file_rpc_common_service_proto_rawDesc = []byte{ 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0xed, 0x01, 0x0a, 0x0b, 0x4c, - 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x6c, 0x69, - 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x45, 0x6e, 0x75, 0x6d, - 0x52, 0x0b, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, - 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x6b, - 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6b, - 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x67, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, 0x6e, 0x67, 0x73, 0x12, - 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x61, - 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x4c, - 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x3e, 0x0a, - 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, - 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2e, 0x45, - 0x6e, 0x75, 0x6d, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x63, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0x95, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, - 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x22, 0x81, 0x01, 0x0a, 0x04, 0x45, 0x6e, - 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, - 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, 0x49, 0x44, 0x44, 0x45, 0x4e, - 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x53, 0x54, 0x52, 0x49, 0x43, 0x54, 0x45, 0x44, - 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x43, 0x49, 0x50, 0x52, 0x4f, 0x43, 0x41, 0x4c, - 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x54, 0x49, 0x43, 0x45, 0x10, 0x04, 0x12, 0x0e, - 0x0a, 0x0a, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x56, 0x45, 0x10, 0x05, 0x12, 0x10, - 0x0a, 0x0c, 0x55, 0x4e, 0x45, 0x4e, 0x43, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x45, 0x44, 0x10, 0x06, - 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x07, 0x22, 0x4e, 0x0a, - 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3f, 0x0a, 0x04, - 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x50, 0x4b, 0x47, 0x10, 0x01, 0x12, - 0x0a, 0x0a, 0x06, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4c, - 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a, 0x44, 0x0a, - 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, - 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, - 0x0a, 0x0a, 0x06, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x48, - 0x49, 0x47, 0x48, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x49, 0x54, 0x49, 0x43, 0x41, - 0x4c, 0x10, 0x04, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, - 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3b, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, + 0x78, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0xed, + 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x41, + 0x0a, 0x0c, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x2e, + 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x0b, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, + 0x0a, 0x08, 0x70, 0x6b, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x6b, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x69, 0x6e, + 0x67, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, + 0x73, 0x65, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x67, 0x69, + 0x6e, 0x67, 0x73, 0x12, 0x29, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x22, 0x98, + 0x01, 0x0a, 0x0e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x12, 0x3e, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, + 0x72, 0x79, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, + 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, + 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0x95, 0x01, 0x0a, 0x0f, 0x4c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x22, 0x81, 0x01, + 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x42, 0x49, + 0x44, 0x44, 0x45, 0x4e, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x53, 0x54, 0x52, 0x49, + 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x52, 0x45, 0x43, 0x49, 0x50, 0x52, + 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x54, 0x49, 0x43, 0x45, + 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x56, 0x45, + 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x55, 0x4e, 0x45, 0x4e, 0x43, 0x55, 0x4d, 0x42, 0x45, 0x52, + 0x45, 0x44, 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x07, 0x22, 0x4e, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x22, 0x3f, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x50, 0x4b, + 0x47, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, + 0x10, 0x0a, 0x0c, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, + 0x03, 0x2a, 0x44, 0x0a, 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x0b, 0x0a, + 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x4f, + 0x57, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, 0x02, 0x12, + 0x08, 0x0a, 0x04, 0x48, 0x49, 0x47, 0x48, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x49, + 0x54, 0x49, 0x43, 0x41, 0x4c, 0x10, 0x04, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, + 0x74, 0x79, 0x2f, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( diff --git a/rpc/common/service.proto b/rpc/common/service.proto index 91ed89dd534f..d80bf57e3b86 100644 --- a/rpc/common/service.proto +++ b/rpc/common/service.proto @@ -226,6 +226,7 @@ message DetectedLicense { string name = 5; float confidence = 6; string link = 7; + string text = 8; } message LicenseFile { From 412fb764f0464c206fc2e546f208c8a245c782f3 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Fri, 6 Sep 2024 12:55:51 +0600 Subject: [PATCH 334/352] refactor(java): add error/statusCode for logs when we can't get pom.xml/maven-metadata.xml from remote repo (#7451) --- pkg/dependency/parser/java/pom/parse.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index 57f41a1d32f4..46c859538529 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -714,8 +714,11 @@ func (p *Parser) fetchPomFileNameFromMavenMetadata(repo string, paths []string) client := &http.Client{} resp, err := client.Do(req) - if err != nil || resp.StatusCode != http.StatusOK { - p.logger.Debug("Failed to fetch", log.String("url", req.URL.String())) + if err != nil { + p.logger.Debug("Failed to fetch", log.String("url", req.URL.String()), log.Err(err)) + return "", nil + } else if resp.StatusCode != http.StatusOK { + p.logger.Debug("Failed to fetch", log.String("url", req.URL.String()), log.Int("statusCode", resp.StatusCode)) return "", nil } defer resp.Body.Close() @@ -745,8 +748,11 @@ func (p *Parser) fetchPOMFromRemoteRepository(repo string, paths []string) (*pom client := &http.Client{} resp, err := client.Do(req) - if err != nil || resp.StatusCode != http.StatusOK { - p.logger.Debug("Failed to fetch", log.String("url", req.URL.String())) + if err != nil { + p.logger.Debug("Failed to fetch", log.String("url", req.URL.String()), log.Err(err)) + return nil, nil + } else if resp.StatusCode != http.StatusOK { + p.logger.Debug("Failed to fetch", log.String("url", req.URL.String()), log.Int("statusCode", resp.StatusCode)) return nil, nil } defer resp.Body.Close() From e2118e8dfa977f22de336df230628139bff79757 Mon Sep 17 00:00:00 2001 From: afdesk Date: Fri, 6 Sep 2024 13:19:33 +0600 Subject: [PATCH 335/352] chore(helm): bump up Trivy Helm chart (#7441) --- helm/trivy/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/trivy/Chart.yaml b/helm/trivy/Chart.yaml index 3fc94a4b4968..3695c704a2e8 100644 --- a/helm/trivy/Chart.yaml +++ b/helm/trivy/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: trivy -version: 0.7.0 -appVersion: 0.37.2 +version: 0.8.0 +appVersion: 0.55.0 description: Trivy helm chart keywords: - scanner From 5375cd27ad1d455504aba69a33a6c9d2989027bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 07:44:35 +0000 Subject: [PATCH 336/352] chore(deps): bump the common group across 1 directory with 19 updates (#7436) Signed-off-by: dependabot[bot] Signed-off-by: knqyf263 Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: knqyf263 --- go.mod | 66 ++++++++++++------------ go.sum | 155 +++++++++++++++++++++++++++------------------------------ 2 files changed, 108 insertions(+), 113 deletions(-) diff --git a/go.mod b/go.mod index 62c3dd5a6f18..e2191c3c2800 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/CycloneDX/cyclonedx-go v0.9.0 github.com/GoogleCloudPlatform/docker-credential-gcr v2.0.5+incompatible - github.com/Masterminds/sprig/v3 v3.2.3 + github.com/Masterminds/sprig/v3 v3.3.0 github.com/NYTimes/gziphandler v1.1.1 github.com/alecthomas/chroma v0.10.0 github.com/alicebob/miniredis/v2 v2.33.0 @@ -30,12 +30,12 @@ require ( github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b github.com/aws/aws-sdk-go-v2 v1.30.4 - github.com/aws/aws-sdk-go-v2/config v1.27.28 - github.com/aws/aws-sdk-go-v2/credentials v1.17.28 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.175.1 - github.com/aws/aws-sdk-go-v2/service/ecr v1.32.1 - github.com/aws/aws-sdk-go-v2/service/s3 v1.59.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.31 + github.com/aws/aws-sdk-go-v2/credentials v1.17.30 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.0 + github.com/aws/aws-sdk-go-v2/service/ecr v1.32.2 + github.com/aws/aws-sdk-go-v2/service/s3 v1.61.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 // indirect github.com/aws/smithy-go v1.20.4 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 @@ -43,7 +43,7 @@ require ( github.com/cheggaaa/pb/v3 v3.1.5 github.com/containerd/containerd v1.7.21 github.com/csaf-poc/csaf_distribution/v3 v3.0.0 - github.com/docker/docker v27.1.1+incompatible + github.com/docker/docker v27.2.0+incompatible github.com/docker/go-connections v0.5.0 github.com/fatih/color v1.17.0 github.com/go-git/go-git/v5 v5.12.0 @@ -56,14 +56,14 @@ require ( github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/uuid v1.6.0 github.com/google/wire v0.6.0 - github.com/hashicorp/go-getter v1.7.5 + github.com/hashicorp/go-getter v1.7.6 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/hc-install v0.8.0 - github.com/hashicorp/hcl/v2 v2.21.0 + github.com/hashicorp/hcl/v2 v2.22.0 github.com/hashicorp/terraform-exec v0.21.0 github.com/in-toto/in-toto-golang v0.9.0 github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f @@ -87,17 +87,17 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 - github.com/moby/buildkit v0.15.1 + github.com/moby/buildkit v0.15.2 github.com/open-policy-agent/opa v0.67.1 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/openvex/discovery v0.1.0 github.com/openvex/go-vex v0.2.5 github.com/owenrumney/go-sarif/v2 v2.3.3 - github.com/owenrumney/squealer v1.2.3 + github.com/owenrumney/squealer v1.2.4 github.com/package-url/packageurl-go v0.1.3 github.com/quasilyte/go-ruleguard/dsl v0.3.22 - github.com/samber/lo v1.46.0 + github.com/samber/lo v1.47.0 github.com/secure-systems-lab/go-securesystemslib v0.8.0 github.com/sigstore/rekor v1.3.6 github.com/sirupsen/logrus v1.9.3 @@ -108,15 +108,15 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.32.0 - github.com/testcontainers/testcontainers-go/modules/localstack v0.32.0 - github.com/tetratelabs/wazero v1.7.3 + github.com/testcontainers/testcontainers-go v0.33.0 + github.com/testcontainers/testcontainers-go/modules/localstack v0.33.0 + github.com/tetratelabs/wazero v1.8.0 github.com/twitchtv/twirp v8.1.3+incompatible github.com/xeipuuv/gojsonschema v1.2.0 github.com/xlab/treeprint v1.2.0 github.com/zclconf/go-cty v1.15.0 github.com/zclconf/go-cty-yaml v1.0.3 - go.etcd.io/bbolt v1.3.10 + go.etcd.io/bbolt v1.3.11 golang.org/x/crypto v0.26.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/mod v0.20.0 @@ -128,9 +128,9 @@ require ( golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.15.3 + helm.sh/helm/v3 v3.15.4 k8s.io/api v0.30.3 - k8s.io/utils v0.0.0-20231127182322-b307cd553661 + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 modernc.org/sqlite v1.32.0 sigs.k8s.io/yaml v1.4.0 ) @@ -140,7 +140,7 @@ require ( cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/storage v1.39.1 // indirect - dario.cat/mergo v1.0.0 // indirect + dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect @@ -156,7 +156,7 @@ require ( github.com/Intevation/jsonpath v0.2.1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.12.0 // indirect @@ -222,6 +222,7 @@ require ( github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-chi/chi v4.1.2+incompatible // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect @@ -267,7 +268,7 @@ require ( github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/terraform-json v0.22.1 // indirect - github.com/huandu/xstrings v1.4.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -297,7 +298,7 @@ require ( github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/spdystream v0.4.0 // indirect github.com/moby/sys/mountinfo v0.7.1 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/signal v0.7.0 // indirect @@ -340,7 +341,7 @@ require ( github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shirou/gopsutil/v3 v3.24.2 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect - github.com/shopspring/decimal v1.3.1 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/sigstore/cosign/v2 v2.2.4 // indirect github.com/sigstore/sigstore v1.8.3 // indirect github.com/sigstore/timestamp-authority v1.2.2 // indirect @@ -361,6 +362,7 @@ require ( github.com/vbatts/tar-split v0.11.5 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -394,15 +396,15 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiextensions-apiserver v0.30.0 // indirect - k8s.io/apimachinery v0.30.3 // indirect - k8s.io/apiserver v0.30.0 // indirect - k8s.io/cli-runtime v0.30.2 // indirect - k8s.io/client-go v0.30.2 // indirect - k8s.io/component-base v0.30.1 // indirect - k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/apiextensions-apiserver v0.30.3 // indirect + k8s.io/apimachinery v0.31.0 // indirect + k8s.io/apiserver v0.30.3 // indirect + k8s.io/cli-runtime v0.30.3 // indirect + k8s.io/client-go v0.30.3 // indirect + k8s.io/component-base v0.30.3 // indirect + k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/kubectl v0.30.1 // indirect + k8s.io/kubectl v0.30.3 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect diff --git a/go.sum b/go.sum index fdb1c5a2dcd6..d51f8099be1c 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,8 @@ cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e h1:GwCVItFUPxw cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e/go.mod h1:ApHceQLLwcOkCEXM1+DyCXTHEJhNGDpJ2kmV6axsx24= cuelang.org/go v0.8.1 h1:VFYsxIFSPY5KgSaH1jQ2GxHOrbu6Ga3kEI70yCZwnOg= cuelang.org/go v0.8.1/go.mod h1:CoDbYolfMms4BhWUlhD+t5ORnihR7wvjcfgyO9lL5FI= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= @@ -255,11 +255,10 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= @@ -367,10 +366,10 @@ github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8= github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= -github.com/aws/aws-sdk-go-v2/config v1.27.28 h1:OTxWGW/91C61QlneCtnD62NLb4W616/NM1jA8LhJqbg= -github.com/aws/aws-sdk-go-v2/config v1.27.28/go.mod h1:uzVRVtJSU5EFv6Fu82AoVFKozJi2ZCY6WRCXj06rbvs= -github.com/aws/aws-sdk-go-v2/credentials v1.17.28 h1:m8+AHY/ND8CMHJnPoH7PJIRakWGa4gbfbxuY9TGTUXM= -github.com/aws/aws-sdk-go-v2/credentials v1.17.28/go.mod h1:6TF7dSc78ehD1SL6KpRIPKMA1GyyWflIkjqg+qmf4+c= +github.com/aws/aws-sdk-go-v2/config v1.27.31 h1:kxBoRsjhT3pq0cKthgj6RU6bXTm/2SgdoUMyrVw0rAI= +github.com/aws/aws-sdk-go-v2/config v1.27.31/go.mod h1:z04nZdSWFPaDwK3DdJOG2r+scLQzMYuJeW0CujEm9FM= +github.com/aws/aws-sdk-go-v2/credentials v1.17.30 h1:aau/oYFtibVovr2rDt8FHlU17BTicFEMAi29V1U+L5Q= +github.com/aws/aws-sdk-go-v2/credentials v1.17.30/go.mod h1:BPJ/yXV92ZVq6G8uYvbU0gSl8q94UB63nMT5ctNO38g= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY= @@ -381,10 +380,10 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvK github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.175.1 h1:7B5ppg4i5N2B6t+aH77WLbAu8sD98MLlzruWzq5scyY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.175.1/go.mod h1:ISODge3zgdwOEa4Ou6WM9PKbxJWJ15DYKnr2bfmCAIA= -github.com/aws/aws-sdk-go-v2/service/ecr v1.32.1 h1:PxM8EHsv1sd9eWGamMQCvqBEjxytK5kAwjrxlfG3tac= -github.com/aws/aws-sdk-go-v2/service/ecr v1.32.1/go.mod h1:kdk+WJbHcGVbIlRQfSrKyuKkbWDdD8I9NScyS5vZ8eQ= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.0 h1:LAdDRIj5BEZM9fLDTUWUyPzWvv5A++nCEps/RGmZNOo= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.0/go.mod h1:ISODge3zgdwOEa4Ou6WM9PKbxJWJ15DYKnr2bfmCAIA= +github.com/aws/aws-sdk-go-v2/service/ecr v1.32.2 h1:2RjzMZp/8PXJUMqiKkDSp7RVj6inF5DpVel35THjV+I= +github.com/aws/aws-sdk-go-v2/service/ecr v1.32.2/go.mod h1:kdk+WJbHcGVbIlRQfSrKyuKkbWDdD8I9NScyS5vZ8eQ= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 h1:PpbXaecV3sLAS6rjQiaKw4/jyq3Z8gNzmoJupHAoBp0= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2/go.mod h1:fUHpGXr4DrXkEDpGAjClPsviWf+Bszeb0daKE0blxv8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= @@ -393,14 +392,14 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHC github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 h1:yS0JkEdV6h9JOo8sy2JSpjX+i7vsKifU8SIeHrqiDhU= github.com/aws/aws-sdk-go-v2/service/kms v1.30.0/go.mod h1:+I8VUUSVD4p5ISQtzpgSva4I8cJ4SQ4b1dcBcof7O+g= -github.com/aws/aws-sdk-go-v2/service/s3 v1.59.0 h1:Cso4Ev/XauMVsbwdhYEoxg8rxZWw43CFqqaPB5w3W2c= -github.com/aws/aws-sdk-go-v2/service/s3 v1.59.0/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.0 h1:Wb544Wh+xfSXqJ/j3R4aX9wrKUoZsJNmilBYZb3mKQ4= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.0/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI= github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 h1:iAckBT2OeEK/kBDyN/jDtpEExhjeeA/Im2q4X0rJZT8= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.4/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 h1:OMsEmCyz2i89XwRwPouAJvhj81wINh+4UK+k/0Yo/q8= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.5/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M= @@ -546,8 +545,8 @@ github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2 github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -605,6 +604,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -686,7 +687,8 @@ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqw github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= @@ -817,8 +819,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= @@ -829,7 +831,6 @@ github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4= github.com/google/trillian v1.6.0/go.mod h1:Yu3nIMITzNhhMJEHjAtp6xKiu+H/iHu2Oq5FjV2mCWI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -857,7 +858,6 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= @@ -873,8 +873,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.7.5 h1:dT58k9hQ/vbxNMwoI5+xFYAJuv6152UNvdHokfI5wE4= -github.com/hashicorp/go-getter v1.7.5/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-getter v1.7.6 h1:5jHuM+aH373XNtXl9TNTUH5Qd69Trve11tHIrB+6yj4= +github.com/hashicorp/go-getter v1.7.6/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -906,8 +906,8 @@ github.com/hashicorp/hc-install v0.8.0 h1:LdpZeXkZYMQhoKPCecJHlKvUkQFixN/nvyR1Cd github.com/hashicorp/hc-install v0.8.0/go.mod h1:+MwJYjDfCruSD/udvBmRB22Nlkwwkwf5sAB6uTIhSaU= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= -github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= -github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M= +github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= @@ -917,12 +917,10 @@ github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJ github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= -github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= @@ -1064,7 +1062,6 @@ github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -1077,19 +1074,18 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4 github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= 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/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.15.1 h1:J6wrew7hphKqlq1wuu6yaUb/1Ra7gEzDAovylGztAKM= -github.com/moby/buildkit v0.15.1/go.mod h1:Yis8ZMUJTHX9XhH9zVyK2igqSHV3sxi3UN0uztZocZk= +github.com/moby/buildkit v0.15.2 h1:DnONr0AoceTWyv+plsQ7IhkSaj+6o0WyoaxYPyTFIxs= +github.com/moby/buildkit v0.15.2/go.mod h1:Yis8ZMUJTHX9XhH9zVyK2igqSHV3sxi3UN0uztZocZk= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -1139,8 +1135,8 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= -github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= @@ -1162,8 +1158,8 @@ github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88 github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= github.com/owenrumney/go-sarif/v2 v2.3.3 h1:ubWDJcF5i3L/EIOER+ZyQ03IfplbSU1BLOE26uKQIIU= github.com/owenrumney/go-sarif/v2 v2.3.3/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= -github.com/owenrumney/squealer v1.2.3 h1:7v2BGNReEHYGyopOpjnurbnowk5WWagpN/u9KEu0uUU= -github.com/owenrumney/squealer v1.2.3/go.mod h1:F3PF/UaTAzaexT/cvvMYCSRHLRPBCiUcPClz3SZ6618= +github.com/owenrumney/squealer v1.2.4 h1:77CEDP10mgvFLWHzUIBTfFIj9RkJ5h36YQhZ48GtjsQ= +github.com/owenrumney/squealer v1.2.4/go.mod h1:F3PF/UaTAzaexT/cvvMYCSRHLRPBCiUcPClz3SZ6618= github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= @@ -1234,8 +1230,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= -github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= @@ -1257,9 +1253,9 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sigstore/cosign/v2 v2.2.4 h1:iY4vtEacmu2hkNj1Fh+8EBqBwKs2DHM27/lbNWDFJro= github.com/sigstore/cosign/v2 v2.2.4/go.mod h1:JZlRD2uaEjVAvZ1XJ3QkkZJhTqSDVtLaet+C/TMR81Y= @@ -1297,7 +1293,6 @@ github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XO github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= @@ -1336,12 +1331,12 @@ github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BG github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= -github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME= -github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E= -github.com/testcontainers/testcontainers-go/modules/localstack v0.32.0 h1:FITjE+DSDD136HQho7ThA6cEtUouZzDf7FvMBL2Muog= -github.com/testcontainers/testcontainers-go/modules/localstack v0.32.0/go.mod h1:JasdXHmUT8MTDYfyJza3JjO/k+QA3m8K2GQfnFQM++g= -github.com/tetratelabs/wazero v1.7.3 h1:PBH5KVahrt3S2AHgEjKu4u+LlDbbk+nsGE3KLucy6Rw= -github.com/tetratelabs/wazero v1.7.3/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= +github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw= +github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8= +github.com/testcontainers/testcontainers-go/modules/localstack v0.33.0 h1:AhbUGUjneEnMyTV5aTsPYzDiAWrba1duPtiV+Z9CKdY= +github.com/testcontainers/testcontainers-go/modules/localstack v0.33.0/go.mod h1:J5vMq1fXXiTfwcJplMClHhn+j8+MbIMv7Lic4d9E8qU= +github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g= +github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= @@ -1375,6 +1370,8 @@ github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4= github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -1418,8 +1415,8 @@ github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JApr github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= -go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -1472,7 +1469,6 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= @@ -1574,7 +1570,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= @@ -1711,7 +1706,6 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1727,7 +1721,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= @@ -2082,8 +2075,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.15.3 h1:HcZDaVFe9uHa6hpsR54mJjYyRy4uz/pc6csg27nxFOc= -helm.sh/helm/v3 v3.15.3/go.mod h1:FzSIP8jDQaa6WAVg9F+OkKz7J0ZmAga4MABtTbsb9WQ= +helm.sh/helm/v3 v3.15.4 h1:UFHd6oZ1IN3FsUZ7XNhOQDyQ2QYknBNWRHH57e9cbHY= +helm.sh/helm/v3 v3.15.4/go.mod h1:phOwlxqGSgppCY/ysWBNRhG3MtnpsttOzxaTK+Mt40E= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -2093,26 +2086,26 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ= k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04= -k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= -k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= -k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc= -k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M= -k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= -k8s.io/cli-runtime v0.30.2 h1:ooM40eEJusbgHNEqnHziN9ZpLN5U4WcQGsdLKVxpkKE= -k8s.io/cli-runtime v0.30.2/go.mod h1:Y4g/2XezFyTATQUbvV5WaChoUGhojv/jZAtdp5Zkm0A= -k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= -k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= -k8s.io/component-base v0.30.1 h1:bvAtlPh1UrdaZL20D9+sWxsJljMi0QZ3Lmw+kmZAaxQ= -k8s.io/component-base v0.30.1/go.mod h1:e/X9kDiOebwlI41AvBHuWdqFriSRrX50CdwA9TFaHLI= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/apiextensions-apiserver v0.30.3 h1:oChu5li2vsZHx2IvnGP3ah8Nj3KyqG3kRSaKmijhB9U= +k8s.io/apiextensions-apiserver v0.30.3/go.mod h1:uhXxYDkMAvl6CJw4lrDN4CPbONkF3+XL9cacCT44kV4= +k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= +k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.30.3 h1:QZJndA9k2MjFqpnyYv/PH+9PE0SHhx3hBho4X0vE65g= +k8s.io/apiserver v0.30.3/go.mod h1:6Oa88y1CZqnzetd2JdepO0UXzQX4ZnOekx2/PtEjrOg= +k8s.io/cli-runtime v0.30.3 h1:aG69oRzJuP2Q4o8dm+f5WJIX4ZBEwrvdID0+MXyUY6k= +k8s.io/cli-runtime v0.30.3/go.mod h1:hwrrRdd9P84CXSKzhHxrOivAR9BRnkMt0OeP5mj7X30= +k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= +k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= +k8s.io/component-base v0.30.3 h1:Ci0UqKWf4oiwy8hr1+E3dsnliKnkMLZMVbWzeorlk7s= +k8s.io/component-base v0.30.3/go.mod h1:C1SshT3rGPCuNtBs14RmVD2xW0EhRSeLvBh7AGk1quA= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/kubectl v0.30.1 h1:sHFIRI3oP0FFZmBAVEE8ErjnTyXDPkBcvO88mH9RjuY= -k8s.io/kubectl v0.30.1/go.mod h1:7j+L0Cc38RYEcx+WH3y44jRBe1Q1jxdGPKkX0h4iDq0= -k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= -k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kubectl v0.30.3 h1:YIBBvMdTW0xcDpmrOBzcpUVsn+zOgjMYIu7kAq+yqiI= +k8s.io/kubectl v0.30.3/go.mod h1:IcR0I9RN2+zzTRUa1BzZCm4oM0NLOawE6RzlDvd1Fpo= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= From 3642fe16c923b7042b4fe1e6e4c3bf93e50b1255 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:59:34 +0400 Subject: [PATCH 337/352] chore(deps): bump the aws group with 6 updates (#7468) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 26 +++++++++++++------------- go.sum | 52 ++++++++++++++++++++++++++-------------------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index e2191c3c2800..23f7771452a8 100644 --- a/go.mod +++ b/go.mod @@ -29,13 +29,13 @@ require ( github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b - github.com/aws/aws-sdk-go-v2 v1.30.4 - github.com/aws/aws-sdk-go-v2/config v1.27.31 - github.com/aws/aws-sdk-go-v2/credentials v1.17.30 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.0 - github.com/aws/aws-sdk-go-v2/service/ecr v1.32.2 - github.com/aws/aws-sdk-go-v2/service/s3 v1.61.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 // indirect + github.com/aws/aws-sdk-go-v2 v1.30.5 + github.com/aws/aws-sdk-go-v2/config v1.27.33 + github.com/aws/aws-sdk-go-v2/credentials v1.17.32 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.2 + github.com/aws/aws-sdk-go-v2/service/ecr v1.32.4 + github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 + github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect github.com/aws/smithy-go v1.20.4 github.com/bitnami/go-version v0.0.0-20231130084017-bb00604d650c github.com/bmatcuk/doublestar/v4 v4.6.1 @@ -171,15 +171,15 @@ require ( github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.54.6 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index d51f8099be1c..80827c8b4ffb 100644 --- a/go.sum +++ b/go.sum @@ -364,42 +364,42 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.54.6 h1:HEYUib3yTt8E6vxjMWM3yAq5b+qjj/6aKA62mkgux9g= github.com/aws/aws-sdk-go v1.54.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8= -github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= -github.com/aws/aws-sdk-go-v2/config v1.27.31 h1:kxBoRsjhT3pq0cKthgj6RU6bXTm/2SgdoUMyrVw0rAI= -github.com/aws/aws-sdk-go-v2/config v1.27.31/go.mod h1:z04nZdSWFPaDwK3DdJOG2r+scLQzMYuJeW0CujEm9FM= -github.com/aws/aws-sdk-go-v2/credentials v1.17.30 h1:aau/oYFtibVovr2rDt8FHlU17BTicFEMAi29V1U+L5Q= -github.com/aws/aws-sdk-go-v2/credentials v1.17.30/go.mod h1:BPJ/yXV92ZVq6G8uYvbU0gSl8q94UB63nMT5ctNO38g= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16/go.mod h1:2DwJF39FlNAUiX5pAc0UNeiz16lK2t7IaFcm0LFHEgc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 h1:jYfy8UPmd+6kJW5YhY0L1/KftReOGxI/4NtVSTh9O/I= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16/go.mod h1:7ZfEPZxkW42Afq4uQB8H2E2e6ebh6mXTueEpYzjCzcs= +github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g= +github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2/config v1.27.33 h1:Nof9o/MsmH4oa0s2q9a0k7tMz5x/Yj5k06lDODWz3BU= +github.com/aws/aws-sdk-go-v2/config v1.27.33/go.mod h1:kEqdYzRb8dd8Sy2pOdEbExTTF5v7ozEXX0McgPE7xks= +github.com/aws/aws-sdk-go-v2/credentials v1.17.32 h1:7Cxhp/BnT2RcGy4VisJ9miUPecY+lyE9I8JvcZofn9I= +github.com/aws/aws-sdk-go-v2/credentials v1.17.32/go.mod h1:P5/QMF3/DCHbXGEGkdbilXHsyTBX5D3HSwcrSc9p20I= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13/go.mod h1:NG7RXPUlqfsCLLFfi0+IpKN4sCB9D9fw/qTaSB+xRoU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7 h1:CRzzXjmgx9p362yO39D6hbZULdMI23gaKqSxijJCXHM= github.com/aws/aws-sdk-go-v2/service/ebs v1.21.7/go.mod h1:wnsHqpi3RgDwklS5SPHUgjcUUpontGPKJ+GJYOdV7pY= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.0 h1:LAdDRIj5BEZM9fLDTUWUyPzWvv5A++nCEps/RGmZNOo= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.0/go.mod h1:ISODge3zgdwOEa4Ou6WM9PKbxJWJ15DYKnr2bfmCAIA= -github.com/aws/aws-sdk-go-v2/service/ecr v1.32.2 h1:2RjzMZp/8PXJUMqiKkDSp7RVj6inF5DpVel35THjV+I= -github.com/aws/aws-sdk-go-v2/service/ecr v1.32.2/go.mod h1:kdk+WJbHcGVbIlRQfSrKyuKkbWDdD8I9NScyS5vZ8eQ= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.2 h1:QUUvxEs9q1DsYCaWaRrV8i7n82Adm34jrHb6OPjXPqc= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.177.2/go.mod h1:TFSALWR7Xs7+KyMM87ZAYxncKFBvzEt2rpK/BJCH2ps= +github.com/aws/aws-sdk-go-v2/service/ecr v1.32.4 h1:nQAU2Yr+afkAvIV39mg7LrNYFNQP7ShwbmiJqx2fUKA= +github.com/aws/aws-sdk-go-v2/service/ecr v1.32.4/go.mod h1:keOS9j4fv5ASh7dV29lIpGw2QgoJwGFAyMU0uPvfax4= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 h1:PpbXaecV3sLAS6rjQiaKw4/jyq3Z8gNzmoJupHAoBp0= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2/go.mod h1:fUHpGXr4DrXkEDpGAjClPsviWf+Bszeb0daKE0blxv8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE= github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 h1:yS0JkEdV6h9JOo8sy2JSpjX+i7vsKifU8SIeHrqiDhU= github.com/aws/aws-sdk-go-v2/service/kms v1.30.0/go.mod h1:+I8VUUSVD4p5ISQtzpgSva4I8cJ4SQ4b1dcBcof7O+g= -github.com/aws/aws-sdk-go-v2/service/s3 v1.61.0 h1:Wb544Wh+xfSXqJ/j3R4aX9wrKUoZsJNmilBYZb3mKQ4= -github.com/aws/aws-sdk-go-v2/service/s3 v1.61.0/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 h1:OMsEmCyz2i89XwRwPouAJvhj81wINh+4UK+k/0Yo/q8= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.5/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 h1:pIaGg+08llrP7Q5aiz9ICWbY8cqhTkyy+0SHvfzQpTc= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.7/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 h1:/Cfdu0XV3mONYKaOt1Gr0k1KvQzkzPyiKUdlWJqy+J4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7/go.mod h1:bCbAxKDqNvkHxRaIMnyVPXPo+OaPRwvmgzMxbz1VKSA= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 h1:NKTa1eqZYw8tiHSRGpP0VtTdub/8KNk8sDkNPFaOKDE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o= github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M= From dd0a64a1cf0cd76e6f81e3ff55fa6ccb95ce3c3d Mon Sep 17 00:00:00 2001 From: s-reddy1498 <41355782+s-reddy1498@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:32:43 +0530 Subject: [PATCH 338/352] fix(oracle): Update EOL date for Oracle 7 (#7480) --- pkg/detector/ospkg/oracle/oracle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/detector/ospkg/oracle/oracle.go b/pkg/detector/ospkg/oracle/oracle.go index 2b234698e38a..dd66c11a3cf1 100644 --- a/pkg/detector/ospkg/oracle/oracle.go +++ b/pkg/detector/ospkg/oracle/oracle.go @@ -25,7 +25,7 @@ var ( "4": time.Date(2013, 12, 31, 23, 59, 59, 0, time.UTC), "5": time.Date(2017, 12, 31, 23, 59, 59, 0, time.UTC), "6": time.Date(2021, 3, 21, 23, 59, 59, 0, time.UTC), - "7": time.Date(2024, 7, 23, 23, 59, 59, 0, time.UTC), + "7": time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC), "8": time.Date(2029, 7, 18, 23, 59, 59, 0, time.UTC), "9": time.Date(2032, 7, 18, 23, 59, 59, 0, time.UTC), } From 927c6e0c9d4d4a3f1be00f0f661c1d18325d9440 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 11 Sep 2024 08:39:09 +0400 Subject: [PATCH 339/352] fix(report): change a receiver of MarshalJSON (#7483) Signed-off-by: knqyf263 --- go.mod | 4 ++-- go.sum | 8 ++++---- pkg/fanal/types/package.go | 4 ++-- pkg/report/template_test.go | 29 +++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 23f7771452a8..2ef18dc1cc7a 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 github.com/aquasecurity/tml v0.6.1 github.com/aquasecurity/trivy-checks v0.13.1-0.20240830230553-53ddbbade784 - github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 + github.com/aquasecurity/trivy-db v0.0.0-20240910133327-7e0f4d2ed4c1 github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b github.com/aws/aws-sdk-go-v2 v1.30.5 @@ -384,7 +384,7 @@ require ( golang.org/x/sys v0.23.0 // indirect golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/tools v0.24.0 // indirect google.golang.org/api v0.172.0 // indirect google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect diff --git a/go.sum b/go.sum index 80827c8b4ffb..daaa23dc6559 100644 --- a/go.sum +++ b/go.sum @@ -349,8 +349,8 @@ github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gw github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= github.com/aquasecurity/trivy-checks v0.13.1-0.20240830230553-53ddbbade784 h1:1rvPiCK8uQd3sarOuZ60nwksHpxsNdrvptz4eDW/V14= github.com/aquasecurity/trivy-checks v0.13.1-0.20240830230553-53ddbbade784/go.mod h1:Ralz7PWmR3LirHlXxVtUXc+7CFmWE82jbLk7+TPvV/0= -github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04 h1:6/T8sFdNVG/AwOGoK6X55h7hF7LYqK8bsuPz8iEz8jM= -github.com/aquasecurity/trivy-db v0.0.0-20240718084044-d23a6ca8ba04/go.mod h1:0T6oy2t1Iedt+yi3Ml5cpOYp5FZT4MI1/mx+3p+PIs8= +github.com/aquasecurity/trivy-db v0.0.0-20240910133327-7e0f4d2ed4c1 h1:G0gnacAORRUqz2Tm5MqivSpldY2GZ74ijhJcMsae+sA= +github.com/aquasecurity/trivy-db v0.0.0-20240910133327-7e0f4d2ed4c1/go.mod h1:PYkSRx4dlgFATEt+okGwibvbxVEtqsOdH+vX/saACYE= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48 h1:JVgBIuIYbwG+ekC5lUHUpGJboPYiCcxiz06RCtz8neI= github.com/aquasecurity/trivy-java-db v0.0.0-20240109071736-184bd7481d48/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= github.com/aquasecurity/trivy-kubernetes v0.6.7-0.20240707095038-0300bc49b68b h1:h7gsIzHyrxpQnayOuQI0kX7+8rVcqhV6G5bM3KVFyJU= @@ -1806,8 +1806,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/vuln v1.1.3 h1:NPGnvPOTgnjBc9HTaUx+nj+EaUYxl5SJOWqaDYGaFYw= golang.org/x/vuln v1.1.3/go.mod h1:7Le6Fadm5FOqE9C926BCD0g12NWyhg7cxV4BwcPFuNY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/fanal/types/package.go b/pkg/fanal/types/package.go index 822291e61c29..2726aa8ad8c8 100644 --- a/pkg/fanal/types/package.go +++ b/pkg/fanal/types/package.go @@ -78,7 +78,7 @@ type PkgIdentifier struct { } // MarshalJSON customizes the JSON encoding of PkgIdentifier. -func (id *PkgIdentifier) MarshalJSON() ([]byte, error) { +func (id PkgIdentifier) MarshalJSON() ([]byte, error) { var p string if id.PURL != nil { p = id.PURL.String() @@ -90,7 +90,7 @@ func (id *PkgIdentifier) MarshalJSON() ([]byte, error) { *Alias }{ PURL: p, - Alias: (*Alias)(id), + Alias: (*Alias)(&id), }) } diff --git a/pkg/report/template_test.go b/pkg/report/template_test.go index 56a2d6df7610..bf2e04b7473e 100644 --- a/pkg/report/template_test.go +++ b/pkg/report/template_test.go @@ -6,11 +6,13 @@ import ( "testing" "time" + "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/clock" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/report" "github.com/aquasecurity/trivy/pkg/types" ) @@ -156,6 +158,33 @@ func TestReportWriter_Template(t *testing.T) { template: `{{ $high := 0 }}{{ $critical := 0 }}{{ range . }}{{ range .Vulnerabilities}}{{ if eq .Severity "HIGH" }}{{ $high = add $high 1 }}{{ end }}{{ if eq .Severity "CRITICAL" }}{{ $critical = add $critical 1 }}{{ end }}{{ end }}Critical: {{ $critical }}, High: {{ $high }}{{ end }}`, expected: `Critical: 2, High: 1`, }, + { + name: "custom JSON marshaler", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "foo", + Status: dbTypes.StatusAffected, + PkgIdentifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeNPM, + Name: "foobar", + Version: "1.2.3", + }, + }, + }, + }, + template: `{{ range . }}{{ range .Vulnerabilities}}{{ toPrettyJson . }}{{ end }}{{ end }}`, + expected: `{ + "VulnerabilityID": "CVE-2019-0000", + "PkgName": "foo", + "PkgIdentifier": { + "PURL": "pkg:npm/foobar@1.2.3" + }, + "Status": "affected", + "Layer": {} +}`, + }, { name: "happy path: env var parsing", detectedVulns: []types.DetectedVulnerability{}, From 7ff9aff2739b2eee4a98175b98914795e4077060 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Wed, 11 Sep 2024 12:16:51 +0600 Subject: [PATCH 340/352] fix(report): fix error with unmarshal of `ExperimentalModifiedFindings` (#7463) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- integration/convert_test.go | 26 ++- .../convert/npm-with-suppressed.json.golden | 195 ++++++++++++++++++ pkg/types/finding.go | 47 +++++ 3 files changed, 265 insertions(+), 3 deletions(-) create mode 100644 integration/testdata/fixtures/convert/npm-with-suppressed.json.golden diff --git a/integration/convert_test.go b/integration/convert_test.go index 803ba538dcba..e7d2a5f5e6fe 100644 --- a/integration/convert_test.go +++ b/integration/convert_test.go @@ -11,9 +11,11 @@ import ( func TestConvert(t *testing.T) { type args struct { - input string - format string - scanners string + input string + format string + scanners string + showSuppressed bool + listAllPkgs bool } tests := []struct { name string @@ -37,6 +39,16 @@ func TestConvert(t *testing.T) { }, golden: "testdata/npm-cyclonedx.json.golden", }, + { + name: "npm with suppressed vulnerability", + args: args{ + input: "testdata/fixtures/convert/npm-with-suppressed.json.golden", + format: "json", + showSuppressed: true, + listAllPkgs: true, + }, + golden: "testdata/fixtures/convert/npm-with-suppressed.json.golden", + }, } for _, tt := range tests { @@ -50,6 +62,14 @@ func TestConvert(t *testing.T) { tt.args.format, } + if tt.args.showSuppressed { + osArgs = append(osArgs, "--show-suppressed") + } + + if tt.args.listAllPkgs { + osArgs = append(osArgs, "--list-all-pkgs") + } + // Set up the output file outputFile := filepath.Join(t.TempDir(), "output.json") if *update { diff --git a/integration/testdata/fixtures/convert/npm-with-suppressed.json.golden b/integration/testdata/fixtures/convert/npm-with-suppressed.json.golden new file mode 100644 index 000000000000..0ce14e33545b --- /dev/null +++ b/integration/testdata/fixtures/convert/npm-with-suppressed.json.golden @@ -0,0 +1,195 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2024-09-09T13:21:09.230231+06:00", + "ArtifactName": "package-lock.json", + "ArtifactType": "filesystem", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "package-lock.json", + "Class": "lang-pkgs", + "Type": "npm", + "Packages": [ + { + "ID": "debug@3.0.1", + "Name": "debug", + "Identifier": { + "PURL": "pkg:npm/debug@3.0.1", + "UID": "45acc377fa09cc3" + }, + "Version": "3.0.1", + "Relationship": "direct", + "DependsOn": [ + "ms@2.0.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 11, + "EndLine": 19 + } + ] + }, + { + "ID": "ms@2.0.0", + "Name": "ms", + "Identifier": { + "PURL": "pkg:npm/ms@2.0.0", + "UID": "f51af0181daf2ced" + }, + "Version": "2.0.0", + "Indirect": true, + "Relationship": "indirect", + "Layer": {}, + "Locations": [ + { + "StartLine": 20, + "EndLine": 25 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2017-20165", + "PkgID": "debug@3.0.1", + "PkgName": "debug", + "PkgIdentifier": { + "PURL": "pkg:npm/debug@3.0.1", + "UID": "45acc377fa09cc3" + }, + "InstalledVersion": "3.0.1", + "FixedVersion": "3.1.0, 2.6.9", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2017-20165", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory npm", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "Title": "A vulnerability classified as problematic has been found in debug-js d ...", + "Description": "A vulnerability classified as problematic has been found in debug-js debug up to 3.0.x. This affects the function useColors of the file src/node.js. The manipulation of the argument str leads to inefficient regular expression complexity. Upgrading to version 3.1.0 is able to address this issue. The identifier of the patch is c38a0166c266a679c8de012d4eaccec3f944e685. It is recommended to upgrade the affected component. The identifier VDB-217665 was assigned to this vulnerability.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-1333" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 3 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + }, + "nvd": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + } + }, + "References": [ + "https://github.com/debug-js/debug", + "https://github.com/debug-js/debug/commit/c38a0166c266a679c8de012d4eaccec3f944e685", + "https://github.com/debug-js/debug/commit/f53962e944a87e6ca9bb622a2a12dffc22a9bb5a", + "https://github.com/debug-js/debug/pull/504", + "https://github.com/debug-js/debug/releases/tag/2.6.9", + "https://github.com/debug-js/debug/releases/tag/3.1.0", + "https://nvd.nist.gov/vuln/detail/CVE-2017-20165", + "https://vuldb.com/?ctiid.217665", + "https://vuldb.com/?id.217665" + ], + "PublishedDate": "2023-01-09T10:15:10.447Z", + "LastModifiedDate": "2024-05-17T01:17:24.28Z" + } + ], + "ExperimentalModifiedFindings": [ + { + "Type": "vulnerability", + "Status": "not_affected", + "Statement": "vulnerable_code_not_in_execute_path", + "Source": "./vex.json", + "Finding": { + "VulnerabilityID": "CVE-2017-16137", + "PkgID": "debug@3.0.1", + "PkgName": "debug", + "PkgIdentifier": { + "PURL": "pkg:npm/debug@3.0.1", + "UID": "45acc377fa09cc3" + }, + "InstalledVersion": "3.0.1", + "FixedVersion": "2.6.9, 3.1.0, 3.2.7, 4.3.1", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2017-16137", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory npm", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "Title": "nodejs-debug: Regular expression Denial of Service", + "Description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the o formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.", + "Severity": "LOW", + "CweIDs": [ + "CWE-400" + ], + "VendorSeverity": { + "ghsa": 1, + "nvd": 2, + "redhat": 2 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V3Score": 3.7 + }, + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V3Score": 5.3 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2017-16137", + "https://github.com/debug-js/debug/commit/4e2150207c568adb9ead8f4c4528016081c88020", + "https://github.com/debug-js/debug/commit/71169065b5262f9858ac78cc0b688c84a438f290", + "https://github.com/debug-js/debug/commit/b6d12fdbc63b483e5c969da33ea6adc09946b5ac", + "https://github.com/debug-js/debug/commit/f53962e944a87e6ca9bb622a2a12dffc22a9bb5a", + "https://github.com/debug-js/debug/issues/797", + "https://github.com/visionmedia/debug", + "https://github.com/visionmedia/debug/issues/501", + "https://github.com/visionmedia/debug/pull/504", + "https://lists.apache.org/thread.html/r8ba4c628fba7181af58817d452119481adce4ba92e889c643e4c7dd3%40%3Ccommits.netbeans.apache.org%3E", + "https://lists.apache.org/thread.html/r8ba4c628fba7181af58817d452119481adce4ba92e889c643e4c7dd3@%3Ccommits.netbeans.apache.org%3E", + "https://lists.apache.org/thread.html/rb5ac16fad337d1f3bb7079549f97d8166d0ef3082629417c39f12d63%40%3Cnotifications.netbeans.apache.org%3E", + "https://lists.apache.org/thread.html/rb5ac16fad337d1f3bb7079549f97d8166d0ef3082629417c39f12d63@%3Cnotifications.netbeans.apache.org%3E", + "https://nodesecurity.io/advisories/534", + "https://nvd.nist.gov/vuln/detail/CVE-2017-16137", + "https://www.cve.org/CVERecord?id=CVE-2017-16137" + ], + "PublishedDate": "2018-06-07T02:29:03.817Z", + "LastModifiedDate": "2023-11-07T02:40:28.13Z" + } + } + ] + } + ] +} diff --git a/pkg/types/finding.go b/pkg/types/finding.go index 9e194b8e6b74..65bc4b930fc9 100644 --- a/pkg/types/finding.go +++ b/pkg/types/finding.go @@ -1,5 +1,11 @@ package types +import ( + "encoding/json" + + "golang.org/x/xerrors" +) + type FindingType string type FindingStatus string @@ -45,3 +51,44 @@ func NewModifiedFinding(f finding, status FindingStatus, statement, source strin Finding: f, } } + +// UnmarshalJSON unmarshals ModifiedFinding given the type and `UnmarshalJSON` functions of struct fields +func (m *ModifiedFinding) UnmarshalJSON(data []byte) error { + type Alias ModifiedFinding + aux := &struct { + Finding json.RawMessage `json:"Finding"` + *Alias + }{ + Alias: (*Alias)(m), + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + // Select struct by m.Type to avoid errors with Unmarshal + var err error + switch m.Type { + case FindingTypeVulnerability: + m.Finding, err = unmarshalFinding[DetectedVulnerability](aux.Finding) + case FindingTypeMisconfiguration: + m.Finding, err = unmarshalFinding[DetectedMisconfiguration](aux.Finding) + case FindingTypeSecret: + m.Finding, err = unmarshalFinding[DetectedSecret](aux.Finding) + case FindingTypeLicense: + m.Finding, err = unmarshalFinding[DetectedLicense](aux.Finding) + default: + return xerrors.Errorf("invalid Finding type: %s", m.Type) + } + + if err != nil { + return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) + } + return nil +} + +func unmarshalFinding[T finding](data []byte) (T, error) { + var f T + err := json.Unmarshal(data, &f) + return f, err +} From d589856fdd38f2e9dfb4bbde3759cc3b57202146 Mon Sep 17 00:00:00 2001 From: Squiddim <82903357+Squiddim@users.noreply.github.com> Date: Wed, 11 Sep 2024 08:32:57 +0200 Subject: [PATCH 341/352] docs(oci): Add a note About the expected Media Type for the Trivy-DB OCI Artifact (#7449) --- docs/docs/configuration/db.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/db.md b/docs/docs/configuration/db.md index f6525fb61568..5fa4046668a9 100644 --- a/docs/docs/configuration/db.md +++ b/docs/docs/configuration/db.md @@ -53,12 +53,16 @@ $ trivy image --download-db-only ``` $ trivy image --db-repository registry.gitlab.com/gitlab-org/security-products/dependencies/trivy-db ``` - !!!note Trivy automatically adds the `trivy-db` schema version as a tag if the tag is not used: `trivy-db-registry:latest` => `trivy-db-registry:latest`, but `trivy-db-registry` => `trivy-db-registry:2`. +!!!note + Trivy expects the OCI Artifacts to have a Specific media type: + - Vulnerability DB `application/vnd.aquasec.trivy.db.layer.v1.tar+gzip` + - Java DB `application/vnd.aquasec.trivy.javadb.layer.v1.tar+gzip` + ## Java Index Database The same options are also available for the Java index DB, which is used for scanning Java applications. Skipping an update can be done by using the `--skip-java-db-update` option, while `--download-java-db-only` can be used to only download the Java index DB. @@ -84,4 +88,4 @@ $ trivy image --java-db-repository registry.gitlab.com/gitlab-org/security-produ $ trivy clean --vuln-db --java-db 2024-06-24T11:42:31+06:00 INFO Removing vulnerability database... 2024-06-24T11:42:31+06:00 INFO Removing Java database... -``` \ No newline at end of file +``` From 6472e3c9da2a8e7ba41598a45c80df8f18e57d4c Mon Sep 17 00:00:00 2001 From: Pierre Baumard Date: Wed, 11 Sep 2024 08:47:50 +0200 Subject: [PATCH 342/352] feat(license): improve license normalization (#7131) Signed-off-by: knqyf263 Co-authored-by: DmitriyLewen Co-authored-by: knqyf263 --- .../alpine-39-high-critical.json.golden | 2 +- integration/testdata/alpine-39.json.golden | 2 +- .../testdata/alpine-distroless.json.golden | 2 +- ...fluentd-multiple-lockfiles.cdx.json.golden | 836 +++++++++++++----- .../testdata/license-cyclonedx.json.golden | 4 +- .../testdata/ubi-7-comprehensive.json.golden | 2 +- .../python/packaging/packaging_test.go | 10 +- pkg/fanal/analyzer/pkg/apk/apk.go | 22 +- pkg/fanal/analyzer/pkg/apk/apk_test.go | 21 +- pkg/fanal/analyzer/pkg/dpkg/copyright_test.go | 9 +- pkg/fanal/artifact/image/image_test.go | 25 +- .../goldens/packages/alpine-310.json.golden | 28 +- .../goldens/packages/vulnimage.json.golden | 76 +- pkg/flag/license_flags.go | 14 +- pkg/licensing/{ => expression}/category.go | 24 +- pkg/licensing/expression/expression.go | 12 +- pkg/licensing/expression/expression_test.go | 2 +- pkg/licensing/expression/parser.go.y | 6 +- pkg/licensing/expression/parser_gen.go | 6 +- pkg/licensing/expression/parser_test.go | 34 +- pkg/licensing/expression/types.go | 40 +- pkg/licensing/normalize.go | 786 ++++++++++++---- pkg/licensing/normalize_private_test.go | 18 + pkg/licensing/normalize_test.go | 239 +++++ pkg/licensing/scanner.go | 3 +- pkg/licensing/scanner_test.go | 17 +- pkg/sbom/spdx/marshal.go | 2 +- pkg/sbom/spdx/marshal_test.go | 6 +- 28 files changed, 1702 insertions(+), 546 deletions(-) rename pkg/licensing/{ => expression}/category.go (96%) create mode 100644 pkg/licensing/normalize_private_test.go diff --git a/integration/testdata/alpine-39-high-critical.json.golden b/integration/testdata/alpine-39-high-critical.json.golden index 408cd11d4988..26931273fd01 100644 --- a/integration/testdata/alpine-39-high-critical.json.golden +++ b/integration/testdata/alpine-39-high-critical.json.golden @@ -106,7 +106,7 @@ "PkgName": "musl-utils", "PkgIdentifier": { "PURL": "pkg:apk/alpine/musl-utils@1.1.20-r4?arch=x86_64\u0026distro=3.9.4", - "UID": "8c341199f4077fc8" + "UID": "a35dd6cab4aabdf1" }, "InstalledVersion": "1.1.20-r4", "FixedVersion": "1.1.20-r5", diff --git a/integration/testdata/alpine-39.json.golden b/integration/testdata/alpine-39.json.golden index 3e1089f3e7cb..35d3e2a5c1b7 100644 --- a/integration/testdata/alpine-39.json.golden +++ b/integration/testdata/alpine-39.json.golden @@ -418,7 +418,7 @@ "PkgName": "musl-utils", "PkgIdentifier": { "PURL": "pkg:apk/alpine/musl-utils@1.1.20-r4?arch=x86_64\u0026distro=3.9.4", - "UID": "8c341199f4077fc8" + "UID": "a35dd6cab4aabdf1" }, "InstalledVersion": "1.1.20-r4", "FixedVersion": "1.1.20-r5", diff --git a/integration/testdata/alpine-distroless.json.golden b/integration/testdata/alpine-distroless.json.golden index 4ba010f0eea0..da03075d0cd5 100644 --- a/integration/testdata/alpine-distroless.json.golden +++ b/integration/testdata/alpine-distroless.json.golden @@ -55,7 +55,7 @@ "PkgName": "git", "PkgIdentifier": { "PURL": "pkg:apk/alpine/git@2.35.1-r2?arch=x86_64\u0026distro=3.16", - "UID": "d44ac4666246b919" + "UID": "2999d822f6cae40c" }, "InstalledVersion": "2.35.1-r2", "FixedVersion": "2.35.2-r0", diff --git a/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden b/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden index cc442e7d881d..65fc78e6c66f 100644 --- a/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden +++ b/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden @@ -81,7 +81,7 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -121,7 +121,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } } ], @@ -161,7 +166,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -201,7 +206,7 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { @@ -246,7 +251,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } } ], @@ -290,7 +295,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -320,22 +330,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "LGPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.0-only" } }, { "license": { - "name": "GPL-3.0" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-only" } } ], @@ -379,7 +409,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -424,7 +459,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } } ], @@ -468,7 +503,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -552,7 +587,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -592,7 +627,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -632,12 +667,12 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "GFDL" + "name": "GFDL-1.3-or-later" } } ], @@ -685,7 +720,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -740,12 +780,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-only" } } ], @@ -789,7 +829,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -819,22 +864,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" } } ], @@ -878,12 +943,12 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GFDL-1.3" + "name": "GFDL-1.3-only" } } ], @@ -927,27 +992,32 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" } }, { "license": { - "name": "GFDL-1.2" + "name": "GFDL-1.2-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "Artistic" + "name": "Artistic-2.0" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-or-later" } } ], @@ -991,7 +1061,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { @@ -1001,12 +1071,12 @@ }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { "license": { - "name": "Expat" + "name": "MIT" } }, { @@ -1016,7 +1086,7 @@ }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-or-later" } }, { @@ -1033,6 +1103,21 @@ "license": { "name": "CC0-1.0" } + }, + { + "license": { + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } } ], "purl": "pkg:deb/debian/gpgv@2.2.12-1%2Bdeb10u1?arch=amd64&distro=debian-10.2", @@ -1075,7 +1160,12 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" } } ], @@ -1119,7 +1209,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -1163,7 +1253,7 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -1208,7 +1298,12 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } } ], @@ -1248,17 +1343,22 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } } ], @@ -1302,7 +1402,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } } ], @@ -1342,17 +1447,22 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "LGPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-only" } } ], @@ -1400,17 +1510,17 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-1.0" + "name": "GPL-1.0-only" } } ], @@ -1458,17 +1568,17 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-1.0" + "name": "GPL-1.0-only" } } ], @@ -1516,7 +1626,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -1546,22 +1661,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" } } ], @@ -1605,12 +1740,12 @@ "licenses": [ { "license": { - "name": "BSD-variant" + "name": "BSD-3-Clause" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -1654,12 +1789,12 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -1703,12 +1838,12 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -1752,17 +1887,17 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } } ], @@ -1913,12 +2048,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-only" } } ], @@ -1962,7 +2097,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -1992,22 +2132,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-3.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" } } ], @@ -2051,7 +2211,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -2132,12 +2292,12 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -2181,17 +2341,27 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "GPL-2.0" + "name": "GFDL-1.3-no-invariants-or-later" } }, { "license": { - "name": "GFDL-NIV-1.3+" + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "GPL-2.0-only" } } ], @@ -2235,17 +2405,27 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GFDL-1.3-no-invariants-or-later" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GFDL-NIV-1.3+" + "name": "GPL-2.0-only" } } ], @@ -2289,17 +2469,22 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-only" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -2347,43 +2532,58 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-3.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "GFDL-1.3" + "name": "LGPL-3.0-only" } }, { "license": { - "name": "CC0" + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "The MIT License" + "name": "GPL-3.0-only" } }, { "license": { - "name": "LGPLv3+" + "name": "GFDL-1.3-only" } }, { "license": { - "name": "GPL-2.0" + "name": "CC0-1.0" + } + }, + { + "license": { + "name": "MIT" + } + }, + { + "license": { + "name": "LGPL-3.0-or-later" } }, { "license": { "name": "Apache-2.0" } + }, + { + "license": { + "name": "GPL-3.0-or-later" + } } ], "purl": "pkg:deb/debian/libgnutls30@3.6.7-4?arch=amd64&distro=debian-10.2", @@ -2426,7 +2626,7 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { @@ -2441,7 +2641,17 @@ }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "GPL-3.0-only" } } ], @@ -2522,23 +2732,38 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-or-later" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" } }, { "license": { "name": "Unicode" } + }, + { + "license": { + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "GPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" + } } ], "purl": "pkg:deb/debian/libidn2-0@2.0.5-1?arch=amd64&distro=debian-10.2", @@ -2596,7 +2821,7 @@ }, { "license": { - "name": "Expat" + "name": "MIT" } }, { @@ -2655,7 +2880,12 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } } ], @@ -2709,12 +2939,12 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { @@ -2732,6 +2962,11 @@ "name": "permissive-nowarranty" } }, + { + "license": { + "name": "GPL-2.0-only" + } + }, { "license": { "name": "none" @@ -2744,7 +2979,12 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" } }, { @@ -2759,7 +2999,7 @@ }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } } ], @@ -2803,7 +3043,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -2833,22 +3078,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-3.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" } } ], @@ -2966,47 +3231,47 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "other" + "name": "LGPL-2.0-only" } }, { "license": { - "name": "GPL-2.0" + "name": "other" } }, { "license": { - "name": "GPL-2.0-with-autoconf-exception" + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "public-domain" + "name": "GPL-2.0-with-autoconf-exception+" } }, { "license": { - "name": "GAP" + "name": "public-domain" } }, { "license": { - "name": "LGPL-3.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "GPL-3.0" + "name": "GAP" } } ], @@ -3114,7 +3379,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3158,7 +3423,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3202,7 +3467,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3246,7 +3511,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3331,12 +3596,12 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GFDL" + "name": "GFDL-1.3-or-later" } } ], @@ -3390,12 +3655,12 @@ }, { "license": { - "name": "Expat" + "name": "MIT" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { @@ -3440,27 +3705,27 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "Artistic" + "name": "Artistic-2.0" } }, { "license": { - "name": "zlib/libpng" + "name": "zlib-acknowledgement" } }, { "license": { - "name": "GPL-1.0" + "name": "GPL-1.0-or-later" } }, { "license": { - "name": "CC0" + "name": "CC0-1.0" } }, { @@ -3472,6 +3737,16 @@ "license": { "name": "Permissive" } + }, + { + "license": { + "name": "GPL-1.0-only" + } + }, + { + "license": { + "name": "GPL-3.0-only" + } } ], "purl": "pkg:deb/debian/libruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", @@ -3514,7 +3789,7 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } } ], @@ -3558,12 +3833,12 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -3607,12 +3882,12 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3656,12 +3931,12 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3705,12 +3980,12 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -3754,7 +4029,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -3784,22 +4064,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "LGPL-3.0-or-later" } }, { "license": { - "name": "LGPL-3.0" + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" } } ], @@ -3954,7 +4254,7 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { @@ -3964,18 +4264,28 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" + } + }, + { + "license": { + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "Expat" + "name": "MIT" } }, { "license": { "name": "public-domain" } + }, + { + "license": { + "name": "LGPL-2.1-only" + } } ], "purl": "pkg:deb/debian/libsystemd0@241-7~deb10u2?arch=amd64&distro=debian-10.2", @@ -4018,22 +4328,22 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-2.0-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GFDL-1.3" + "name": "GFDL-1.3-only" } } ], @@ -4114,7 +4424,7 @@ "licenses": [ { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-or-later" } }, { @@ -4124,18 +4434,28 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" + } + }, + { + "license": { + "name": "GPL-2.0-or-later" } }, { "license": { - "name": "Expat" + "name": "MIT" } }, { "license": { "name": "public-domain" } + }, + { + "license": { + "name": "LGPL-2.1-only" + } } ], "purl": "pkg:deb/debian/libudev1@241-7~deb10u2?arch=amd64&distro=debian-10.2", @@ -4178,12 +4498,12 @@ "licenses": [ { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-or-later" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" } }, { @@ -4198,12 +4518,12 @@ }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "GFDL-1.2+" + "name": "GFDL-1.2-or-later" } }, { @@ -4213,7 +4533,22 @@ }, { "license": { - "name": "GFDL-1.2" + "name": "LGPL-3.0-only" + } + }, + { + "license": { + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "GPL-2.0-only" + } + }, + { + "license": { + "name": "GFDL-1.2-only" } } ], @@ -4257,7 +4592,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -4287,22 +4627,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "LGPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.0-only" } }, { "license": { - "name": "GPL-3.0" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-only" } } ], @@ -4346,7 +4706,7 @@ "licenses": [ { "license": { - "name": "Expat" + "name": "MIT" } }, { @@ -4400,7 +4760,7 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { @@ -4410,7 +4770,12 @@ }, { "license": { - "name": "Expat" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "MIT" } } ], @@ -4454,7 +4819,7 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -4502,7 +4867,7 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -4546,7 +4911,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -4576,22 +4946,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-or-later" + } + }, + { + "license": { + "name": "LGPL-3.0-or-later" + } + }, + { + "license": { + "name": "GPL-3.0-only" } }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.0-only" } }, { "license": { - "name": "GPL-3.0" + "name": "LGPL-2.1-only" } }, { "license": { - "name": "LGPL-3.0" + "name": "LGPL-3.0-only" } } ], @@ -4746,7 +5136,7 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -4831,7 +5221,7 @@ "licenses": [ { "license": { - "name": "Expat" + "name": "MIT" } } ], @@ -4875,12 +5265,12 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GFDL" + "name": "GFDL-1.3-or-later" } } ], @@ -4924,7 +5314,7 @@ "licenses": [ { "license": { - "name": "Expat" + "name": "MIT" } } ], @@ -4968,7 +5358,7 @@ "licenses": [ { "license": { - "name": "Expat" + "name": "MIT" } } ], @@ -5120,7 +5510,7 @@ }, { "license": { - "name": "LGPL-2.1" + "name": "LGPL-2.1-only" } } ], @@ -5218,12 +5608,12 @@ }, { "license": { - "name": "Expat" + "name": "MIT" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-or-later" } }, { @@ -5268,27 +5658,27 @@ }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } }, { "license": { - "name": "Artistic" + "name": "Artistic-2.0" } }, { "license": { - "name": "zlib/libpng" + "name": "zlib-acknowledgement" } }, { "license": { - "name": "GPL-1.0" + "name": "GPL-1.0-or-later" } }, { "license": { - "name": "CC0" + "name": "CC0-1.0" } }, { @@ -5300,6 +5690,16 @@ "license": { "name": "Permissive" } + }, + { + "license": { + "name": "GPL-1.0-only" + } + }, + { + "license": { + "name": "GPL-3.0-only" + } } ], "purl": "pkg:deb/debian/ruby2.5@2.5.5-3%2Bdeb10u1?arch=amd64&distro=debian-10.2", @@ -5342,12 +5742,12 @@ "licenses": [ { "license": { - "name": "RubyLicense" + "name": "Ruby" } }, { "license": { - "name": "GPL-3.0" + "name": "GPL-2.0-or-later" } } ], @@ -5391,7 +5791,7 @@ "licenses": [ { "license": { - "name": "Expat" + "name": "MIT" } } ], @@ -5431,7 +5831,7 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } } ], @@ -5475,7 +5875,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } } ], @@ -5519,12 +5924,12 @@ "licenses": [ { "license": { - "name": "GPL-3.0" + "name": "GPL-3.0-only" } }, { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-only" } } ], @@ -5605,7 +6010,12 @@ "licenses": [ { "license": { - "name": "GPL-2.0" + "name": "GPL-2.0-or-later" + } + }, + { + "license": { + "name": "GPL-2.0-only" } }, { @@ -5635,22 +6045,42 @@ }, { "license": { - "name": "LGPL-2.0" + "name": "LGPL-2.0-or-later" + } + }, + { + "license": { + "name": "LGPL-2.1-or-later" } }, { "license": { - "name": "LGPL-2.1" + "name": "GPL-3.0-or-later" } }, { "license": { - "name": "GPL-3.0" + "name": "LGPL-3.0-or-later" } }, { "license": { - "name": "LGPL-3.0" + "name": "GPL-3.0-only" + } + }, + { + "license": { + "name": "LGPL-2.0-only" + } + }, + { + "license": { + "name": "LGPL-2.1-only" + } + }, + { + "license": { + "name": "LGPL-3.0-only" } } ], @@ -6289,7 +6719,7 @@ "licenses": [ { "license": { - "name": "Apache License (2.0)" + "name": "Apache-2.0" } } ], @@ -7370,7 +7800,7 @@ "licenses": [ { "license": { - "name": "2-clause BSDL" + "name": "BSD-2-Clause" } } ], diff --git a/integration/testdata/license-cyclonedx.json.golden b/integration/testdata/license-cyclonedx.json.golden index cf69da9756ed..95f8acd896b1 100644 --- a/integration/testdata/license-cyclonedx.json.golden +++ b/integration/testdata/license-cyclonedx.json.golden @@ -47,8 +47,8 @@ "Link": "" }, { - "Severity": "UNKNOWN", - "Category": "unknown", + "Severity": "LOW", + "Category": "notice", "PkgName": "org.slf4j:slf4j-api", "FilePath": "", "Name": "MIT License", diff --git a/integration/testdata/ubi-7-comprehensive.json.golden b/integration/testdata/ubi-7-comprehensive.json.golden index 6df4b8241456..b500dc5d4bc5 100644 --- a/integration/testdata/ubi-7-comprehensive.json.golden +++ b/integration/testdata/ubi-7-comprehensive.json.golden @@ -147,7 +147,7 @@ "PkgPath": "usr/lib/python2.7/site-packages/setuptools-0.9.8-py2.7.egg-info/PKG-INFO", "PkgIdentifier": { "PURL": "pkg:pypi/setuptools@0.9.8", - "UID": "3f4c89bf681c1d7a" + "UID": "13d32ebdc7bda1b4" }, "InstalledVersion": "0.9.8", "FixedVersion": "65.5.1", diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go index c3a89ad0cd19..960a92dfec2b 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go @@ -33,7 +33,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { Name: "kitchen", Version: "1.2.6", Licenses: []string{ - "GNU Library or Lesser General Public License (LGPL)", + "LGPL-2.1-only", }, FilePath: "kitchen-1.2.6-py2.7.egg", }, @@ -55,7 +55,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Name: "distlib", Version: "0.3.1", - Licenses: []string{"Python license"}, + Licenses: []string{"Python-2.0"}, FilePath: "distlib-0.3.1.egg-info/PKG-INFO", Digest: "sha1:d9d89d8ed3b2b683767c96814c9c5d3e57ef2e1b", }, @@ -76,7 +76,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Name: "setuptools", Version: "51.3.3", - Licenses: []string{"MIT License"}, + Licenses: []string{"MIT"}, FilePath: "setuptools-51.3.3.egg-info/PKG-INFO", }, }, @@ -96,7 +96,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Name: "setuptools", Version: "51.3.3", - Licenses: []string{"MIT License"}, + Licenses: []string{"MIT"}, FilePath: "setuptools-51.3.3.dist-info/METADATA", }, }, @@ -116,7 +116,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Name: "distlib", Version: "0.3.1", - Licenses: []string{"Python license"}, + Licenses: []string{"Python-2.0"}, FilePath: "distlib-0.3.1.dist-info/METADATA", }, }, diff --git a/pkg/fanal/analyzer/pkg/apk/apk.go b/pkg/fanal/analyzer/pkg/apk/apk.go index 9ce13e4b013d..962398600fc5 100644 --- a/pkg/fanal/analyzer/pkg/apk/apk.go +++ b/pkg/fanal/analyzer/pkg/apk/apk.go @@ -142,26 +142,8 @@ func (a alpinePkgAnalyzer) trimRequirement(s string) string { } func (a alpinePkgAnalyzer) parseLicense(line string) []string { - line = line[2:] // Remove "L:" - if line == "" { - return nil - } - var licenses []string - // e.g. MPL 2.0 GPL2+ => {"MPL2.0", "GPL2+"} - for i, s := range strings.Fields(line) { - s = strings.Trim(s, "()") - switch { - case s == "": - continue - case s == "AND" || s == "OR": - continue - case i > 0 && (s == "1.0" || s == "2.0" || s == "3.0"): - licenses[i-1] = licensing.Normalize(licenses[i-1] + s) - default: - licenses = append(licenses, licensing.Normalize(s)) - } - } - return licenses + // Remove "L:" before split + return licensing.LaxSplitLicenses(line[2:]) } func (a alpinePkgAnalyzer) parseProvides(line, pkgID string, provides map[string]string) { diff --git a/pkg/fanal/analyzer/pkg/apk/apk_test.go b/pkg/fanal/analyzer/pkg/apk/apk_test.go index 08a5d302d324..0491ed57c0d2 100644 --- a/pkg/fanal/analyzer/pkg/apk/apk_test.go +++ b/pkg/fanal/analyzer/pkg/apk/apk_test.go @@ -33,7 +33,7 @@ var pkgs = []types.Package{ Version: "1.24.2-r9", SrcName: "busybox", SrcVersion: "1.24.2-r9", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, DependsOn: []string{"musl@1.1.14-r10"}, Arch: "x86_64", Digest: "sha1:ca124719267cd0bedc2f4cb850a286ac13f0ad44", @@ -51,7 +51,7 @@ var pkgs = []types.Package{ Version: "3.0.3-r0", SrcName: "alpine-baselayout", SrcVersion: "3.0.3-r0", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, DependsOn: []string{ "busybox@1.24.2-r9", "musl@1.1.14-r10", @@ -92,7 +92,7 @@ var pkgs = []types.Package{ Version: "1.1-r0", SrcName: "alpine-keys", SrcVersion: "1.1-r0", - Licenses: []string{"GPL-3.0"}, + Licenses: []string{"GPL-2.0-or-later"}, Arch: "x86_64", Digest: "sha1:4def7ffaee6aeba700c1d62570326f75cbb8fa25", InstalledFiles: []string{ @@ -124,7 +124,7 @@ var pkgs = []types.Package{ Version: "1.0.2h-r1", SrcName: "openssl", SrcVersion: "1.0.2h-r1", - Licenses: []string{"openssl"}, + Licenses: []string{"OpenSSL"}, DependsOn: []string{ "musl@1.1.14-r10", "zlib@1.2.8-r2", @@ -155,7 +155,7 @@ var pkgs = []types.Package{ Version: "1.0.2h-r1", SrcName: "openssl", SrcVersion: "1.0.2h-r1", - Licenses: []string{"openssl"}, + Licenses: []string{"OpenSSL"}, Digest: "sha1:7120f337e93b2b4c44e0f5f31a15b60dc678ca14", DependsOn: []string{ "libcrypto1.0@1.0.2h-r1", @@ -173,7 +173,7 @@ var pkgs = []types.Package{ Version: "2.6.7-r0", SrcName: "apk-tools", SrcVersion: "2.6.7-r0", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:0990c0acd62b4175818c3a4cc60ed11f14e23bd8", DependsOn: []string{ "libcrypto1.0@1.0.2h-r1", @@ -192,7 +192,7 @@ var pkgs = []types.Package{ Version: "1.1.6-r0", SrcName: "pax-utils", SrcVersion: "1.1.6-r0", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:f9bab817c5ad93e92a6218bc0f7596b657c02d90", DependsOn: []string{"musl@1.1.14-r10"}, Arch: "x86_64", @@ -209,7 +209,7 @@ var pkgs = []types.Package{ Licenses: []string{ "MIT", "BSD-3-Clause", - "GPL-2.0", + "GPL-2.0-or-later", }, Digest: "sha1:608aa1dd39eff7bc6615d3e5e33383750f8f5ecc", DependsOn: []string{ @@ -231,7 +231,7 @@ var pkgs = []types.Package{ Version: "0.7-r0", SrcName: "libc-dev", SrcVersion: "0.7-r0", - Licenses: []string{"GPL-3.0"}, + Licenses: []string{"GPL-2.0-or-later"}, Digest: "sha1:9055bc7afd76cf2672198042f72fc4a5ed4fa961", DependsOn: []string{"musl-utils@1.1.14-r10"}, Arch: "x86_64", @@ -255,7 +255,6 @@ var pkgs = []types.Package{ "usr/share/aclocal/pkg.m4", }, }, - { ID: "sqlite-libs@3.26.0-r3", Name: "sqlite-libs", @@ -271,7 +270,6 @@ var pkgs = []types.Package{ "usr/lib/libsqlite3.so.0.8.6", }, }, - { ID: "test@2.9.11_pre20061021-r2", Name: "test", @@ -292,7 +290,6 @@ var pkgs = []types.Package{ "usr/include/sqlite3.h", }, }, - { ID: "ada-libs@2.7.4-r0", Name: "ada-libs", diff --git a/pkg/fanal/analyzer/pkg/dpkg/copyright_test.go b/pkg/fanal/analyzer/pkg/dpkg/copyright_test.go index d44899e4362e..2c6c1e2c50ee 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/copyright_test.go +++ b/pkg/fanal/analyzer/pkg/dpkg/copyright_test.go @@ -29,8 +29,8 @@ func Test_dpkgLicenseAnalyzer_Analyze(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/zlib1g/copyright", Findings: []types.LicenseFinding{ - {Name: "GPL-1.0"}, - {Name: "Artistic"}, + {Name: "GPL-1.0-or-later"}, + {Name: "Artistic-2.0"}, {Name: "BSD-4-clause-POWERDOG"}, {Name: "Zlib"}, }, @@ -49,7 +49,7 @@ func Test_dpkgLicenseAnalyzer_Analyze(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/adduser/copyright", Findings: []types.LicenseFinding{ - {Name: "GPL-2.0"}, + {Name: "GPL-2.0-only"}, }, PkgName: "adduser", }, @@ -66,7 +66,8 @@ func Test_dpkgLicenseAnalyzer_Analyze(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/apt/copyright", Findings: []types.LicenseFinding{ - {Name: "GPL-2.0"}, + {Name: "GPL-2.0-or-later"}, + {Name: "GPL-2.0-only"}, }, PkgName: "apt", }, diff --git a/pkg/fanal/artifact/image/image_test.go b/pkg/fanal/artifact/image/image_test.go index a69ab03708e2..f7e80e3cf578 100644 --- a/pkg/fanal/artifact/image/image_test.go +++ b/pkg/fanal/artifact/image/image_test.go @@ -40,7 +40,7 @@ func TestArtifact_Inspect(t *testing.T) { Version: "3.2.0-r3", SrcName: "alpine-baselayout", SrcVersion: "3.2.0-r3", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:8f373f5b329c3aaf136eb30c63a387661ee0f3d0", DependsOn: []string{ "busybox@1.31.1-r9", @@ -113,7 +113,7 @@ func TestArtifact_Inspect(t *testing.T) { Version: "2.10.4-r3", SrcName: "apk-tools", SrcVersion: "2.10.4-r3", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:b15ad0c90e4493dfdc948d6b90a8e020da8936ef", DependsOn: []string{ "libcrypto1.1@1.1.1d-r3", @@ -132,7 +132,7 @@ func TestArtifact_Inspect(t *testing.T) { Version: "1.31.1-r9", SrcName: "busybox", SrcVersion: "1.31.1-r9", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:a457703d71654811ea28d8d27a5cfc49ece27b34", DependsOn: []string{ "musl@1.1.24-r2", @@ -156,7 +156,7 @@ func TestArtifact_Inspect(t *testing.T) { SrcVersion: "20191127-r1", Licenses: []string{ "MPL-2.0", - "GPL-2.0", + "GPL-2.0-or-later", }, Arch: "x86_64", Digest: "sha1:3aeb8a90d7179d2a187782e980a964494e08c5fb", @@ -265,7 +265,7 @@ func TestArtifact_Inspect(t *testing.T) { Licenses: []string{ "MIT", "BSD-3-Clause", - "GPL-2.0", + "GPL-2.0-or-later", }, Digest: "sha1:6d3b45e79dbab444ca7cbfa59e2833203be6fb6a", DependsOn: []string{ @@ -287,7 +287,7 @@ func TestArtifact_Inspect(t *testing.T) { Version: "1.2.4-r0", SrcName: "pax-utils", SrcVersion: "1.2.4-r0", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:d6147beb32bff803b5d9f83a3bec7ab319087185", DependsOn: []string{ "musl@1.1.24-r2", @@ -303,7 +303,7 @@ func TestArtifact_Inspect(t *testing.T) { Version: "1.31.1-r9", SrcName: "busybox", SrcVersion: "1.31.1-r9", - Licenses: []string{"GPL-2.0"}, + Licenses: []string{"GPL-2.0-only"}, Digest: "sha1:3b685152af320120ae8941c740d3376b54e43c10", DependsOn: []string{ "libtls-standalone@2.9.1-r0", @@ -567,7 +567,7 @@ func TestArtifact_Inspect(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/base-files/copyright", Findings: []types.LicenseFinding{ - {Name: "GPL-3.0"}, + {Name: "GPL-2.0-or-later"}, }, PkgName: "base-files", }, @@ -575,7 +575,8 @@ func TestArtifact_Inspect(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/ca-certificates/copyright", Findings: []types.LicenseFinding{ - {Name: "GPL-2.0"}, + {Name: "GPL-2.0-or-later"}, + {Name: "GPL-2.0-only"}, {Name: "MPL-2.0"}, }, PkgName: "ca-certificates", @@ -584,7 +585,7 @@ func TestArtifact_Inspect(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/netbase/copyright", Findings: []types.LicenseFinding{ - {Name: "GPL-2.0"}, + {Name: "GPL-2.0-only"}, }, PkgName: "netbase", }, @@ -655,8 +656,8 @@ func TestArtifact_Inspect(t *testing.T) { Type: types.LicenseTypeDpkg, FilePath: "usr/share/doc/libc6/copyright", Findings: []types.LicenseFinding{ - {Name: "LGPL-2.1"}, - {Name: "GPL-2.0"}, + {Name: "LGPL-2.1-only"}, + {Name: "GPL-2.0-only"}, }, PkgName: "libc6", }, diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden index 32513f398b9f..17d3941a620e 100644 --- a/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden +++ b/pkg/fanal/test/integration/testdata/goldens/packages/alpine-310.json.golden @@ -4,14 +4,14 @@ "Name": "alpine-baselayout", "Identifier": { "PURL": "pkg:apk/alpine/alpine-baselayout@3.1.2-r0?arch=x86_64\u0026distro=3.10.2", - "UID": "3ca42aecf84bfa9" + "UID": "2d19d30821e01d2c" }, "Version": "3.1.2-r0", "Arch": "x86_64", "SrcName": "alpine-baselayout", "SrcVersion": "3.1.2-r0", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "busybox@1.30.1-r2", @@ -95,14 +95,14 @@ "Name": "apk-tools", "Identifier": { "PURL": "pkg:apk/alpine/apk-tools@2.10.4-r2?arch=x86_64\u0026distro=3.10.2", - "UID": "fa7ca40ce236844e" + "UID": "e967fd57e4033819" }, "Version": "2.10.4-r2", "Arch": "x86_64", "SrcName": "apk-tools", "SrcVersion": "2.10.4-r2", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "libcrypto1.1@1.1.1c-r0", @@ -124,14 +124,14 @@ "Name": "busybox", "Identifier": { "PURL": "pkg:apk/alpine/busybox@1.30.1-r2?arch=x86_64\u0026distro=3.10.2", - "UID": "1941d9acbebe7f44" + "UID": "f3002aff2b6b251d" }, "Version": "1.30.1-r2", "Arch": "x86_64", "SrcName": "busybox", "SrcVersion": "1.30.1-r2", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "musl@1.1.22-r3" @@ -155,7 +155,7 @@ "Name": "ca-certificates-cacert", "Identifier": { "PURL": "pkg:apk/alpine/ca-certificates-cacert@20190108-r0?arch=x86_64\u0026distro=3.10.2", - "UID": "9b1db91ad655d76c" + "UID": "1d3125ae903daa3c" }, "Version": "20190108-r0", "Arch": "x86_64", @@ -163,7 +163,7 @@ "SrcVersion": "20190108-r0", "Licenses": [ "MPL-2.0", - "GPL-2.0" + "GPL-2.0-or-later" ], "Layer": { "Digest": "sha256:9d48c3bd43c520dc2784e868a780e976b207cbf493eaff8c6596eb871cbd9609", @@ -321,7 +321,7 @@ "Name": "musl-utils", "Identifier": { "PURL": "pkg:apk/alpine/musl-utils@1.1.22-r3?arch=x86_64\u0026distro=3.10.2", - "UID": "40edadcb74964baa" + "UID": "112ed00987ba9c7d" }, "Version": "1.1.22-r3", "Arch": "x86_64", @@ -330,7 +330,7 @@ "Licenses": [ "MIT", "BSD-3-Clause", - "GPL-2.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.22-r3", @@ -354,14 +354,14 @@ "Name": "scanelf", "Identifier": { "PURL": "pkg:apk/alpine/scanelf@1.2.3-r0?arch=x86_64\u0026distro=3.10.2", - "UID": "6e5cf642f47e44d0" + "UID": "3ae1856359a8e719" }, "Version": "1.2.3-r0", "Arch": "x86_64", "SrcName": "pax-utils", "SrcVersion": "1.2.3-r0", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "musl@1.1.22-r3" @@ -380,14 +380,14 @@ "Name": "ssl_client", "Identifier": { "PURL": "pkg:apk/alpine/ssl_client@1.30.1-r2?arch=x86_64\u0026distro=3.10.2", - "UID": "3338164dd993d2c8" + "UID": "92ce33a8acb582f6" }, "Version": "1.30.1-r2", "Arch": "x86_64", "SrcName": "busybox", "SrcVersion": "1.30.1-r2", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "libtls-standalone@2.9.1-r0", diff --git a/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden b/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden index 4bfbb16a3dff..b4a8d982d9f7 100644 --- a/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden +++ b/pkg/fanal/test/integration/testdata/goldens/packages/vulnimage.json.golden @@ -71,14 +71,14 @@ "Name": "alpine-baselayout", "Identifier": { "PURL": "pkg:apk/alpine/alpine-baselayout@3.0.5-r2?arch=x86_64\u0026distro=3.7.1", - "UID": "fb3e0109c30ad75d" + "UID": "1fd865bbd91dfad4" }, "Version": "3.0.5-r2", "Arch": "x86_64", "SrcName": "alpine-baselayout", "SrcVersion": "3.0.5-r2", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "busybox@1.27.2-r11", @@ -163,14 +163,14 @@ "Name": "apk-tools", "Identifier": { "PURL": "pkg:apk/alpine/apk-tools@2.10.1-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "44e4204bb4dc2b2a" + "UID": "78262f2dc70b3ede" }, "Version": "2.10.1-r0", "Arch": "x86_64", "SrcName": "apk-tools", "SrcVersion": "2.10.1-r0", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "libressl2.6-libcrypto@2.6.5-r0", @@ -192,14 +192,14 @@ "Name": "apr", "Identifier": { "PURL": "pkg:apk/alpine/apr@1.6.3-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "715dd499dcec48f9" + "UID": "64f7de29e73c4636" }, "Version": "1.6.3-r0", "Arch": "x86_64", "SrcName": "apr", "SrcVersion": "1.6.3-r0", "Licenses": [ - "ASL2.0" + "Apache-2.0" ], "DependsOn": [ "libuuid@2.31-r0", @@ -221,14 +221,14 @@ "Name": "apr-util", "Identifier": { "PURL": "pkg:apk/alpine/apr-util@1.6.1-r1?arch=x86_64\u0026distro=3.7.1", - "UID": "2aa1dd25c68a60eb" + "UID": "2d3f23b8b61a097c" }, "Version": "1.6.1-r1", "Arch": "x86_64", "SrcName": "apr-util", "SrcVersion": "1.6.1-r1", "Licenses": [ - "ASL2.0" + "Apache-2.0" ], "DependsOn": [ "apr@1.6.3-r0", @@ -253,14 +253,14 @@ "Name": "bash", "Identifier": { "PURL": "pkg:apk/alpine/bash@4.4.19-r1?arch=x86_64\u0026distro=3.7.1", - "UID": "32e82b55d6afc293" + "UID": "c7841dcc19f73c7e" }, "Version": "4.4.19-r1", "Arch": "x86_64", "SrcName": "bash", "SrcVersion": "4.4.19-r1", "Licenses": [ - "GPL-3.0" + "GPL-3.0-or-later" ], "DependsOn": [ "busybox@1.27.2-r11", @@ -368,14 +368,14 @@ "Name": "busybox", "Identifier": { "PURL": "pkg:apk/alpine/busybox@1.27.2-r11?arch=x86_64\u0026distro=3.7.1", - "UID": "e32c8aeae1daa385" + "UID": "d936440f5fd65b0a" }, "Version": "1.27.2-r11", "Arch": "x86_64", "SrcName": "busybox", "SrcVersion": "1.27.2-r11", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "musl@1.1.18-r3" @@ -399,7 +399,7 @@ "Name": "ca-certificates", "Identifier": { "PURL": "pkg:apk/alpine/ca-certificates@20171114-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "fc49474e56f2cdca" + "UID": "88fab2304b2bb95" }, "Version": "20171114-r0", "Arch": "x86_64", @@ -407,7 +407,7 @@ "SrcVersion": "20171114-r0", "Licenses": [ "MPL-2.0", - "GPL-2.0" + "GPL-2.0-or-later" ], "DependsOn": [ "busybox@1.27.2-r11", @@ -666,14 +666,14 @@ "Name": "gdbm", "Identifier": { "PURL": "pkg:apk/alpine/gdbm@1.13-r1?arch=x86_64\u0026distro=3.7.1", - "UID": "eb4b3efac8d4d73f" + "UID": "1bbe6ee4fe37c0c5" }, "Version": "1.13-r1", "Arch": "x86_64", "SrcName": "gdbm", "SrcVersion": "1.13-r1", "Licenses": [ - "GPL-3.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.18-r3" @@ -698,14 +698,14 @@ "Name": "git", "Identifier": { "PURL": "pkg:apk/alpine/git@2.15.2-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "845b6214d48ea80e" + "UID": "8f0a9684f6888b5b" }, "Version": "2.15.2-r0", "Arch": "x86_64", "SrcName": "git", "SrcVersion": "2.15.2-r0", "Licenses": [ - "GPL-2.0" + "GPL-2.0-or-later" ], "DependsOn": [ "expat@2.2.5-r0", @@ -1271,16 +1271,16 @@ "Name": "libuuid", "Identifier": { "PURL": "pkg:apk/alpine/libuuid@2.31-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "ec8a9d1580eb93d7" + "UID": "39de02f4a8b5e0c" }, "Version": "2.31-r0", "Arch": "x86_64", "SrcName": "util-linux", "SrcVersion": "2.31-r0", "Licenses": [ - "GPL-2.0", - "GPL-2.0", - "LGPL-2.0", + "GPL-2.0-only", + "GPL-2.0-or-later", + "LGPL-2.0-or-later", "BSD-3-Clause", "Public", "Domain" @@ -1331,14 +1331,14 @@ "Name": "mercurial", "Identifier": { "PURL": "pkg:apk/alpine/mercurial@4.5.2-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "28d12c554475942a" + "UID": "6148e64298851c1b" }, "Version": "4.5.2-r0", "Arch": "x86_64", "SrcName": "mercurial", "SrcVersion": "4.5.2-r0", "Licenses": [ - "GPL-2.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.18-r3", @@ -2100,7 +2100,7 @@ "Name": "musl-utils", "Identifier": { "PURL": "pkg:apk/alpine/musl-utils@1.1.18-r3?arch=x86_64\u0026distro=3.7.1", - "UID": "c2765ad0d8dd83f9" + "UID": "dc61f5453717063a" }, "Version": "1.1.18-r3", "Arch": "x86_64", @@ -2109,7 +2109,7 @@ "Licenses": [ "MIT", "BSD-3-Clause", - "GPL-2.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.18-r3", @@ -5136,14 +5136,14 @@ "Name": "patch", "Identifier": { "PURL": "pkg:apk/alpine/patch@2.7.5-r2?arch=x86_64\u0026distro=3.7.1", - "UID": "c3945b28493e2842" + "UID": "be58b1df7c42f71e" }, "Version": "2.7.5-r2", "Arch": "x86_64", "SrcName": "patch", "SrcVersion": "2.7.5-r2", "Licenses": [ - "GPL-3.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.18-r3" @@ -7667,14 +7667,14 @@ "Name": "readline", "Identifier": { "PURL": "pkg:apk/alpine/readline@7.0.003-r0?arch=x86_64\u0026distro=3.7.1", - "UID": "2cb2c7047f5911f5" + "UID": "f3dc1d91dea8744d" }, "Version": "7.0.003-r0", "Arch": "x86_64", "SrcName": "readline", "SrcVersion": "7.0.003-r0", "Licenses": [ - "GPL-3.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.18-r3", @@ -7695,14 +7695,14 @@ "Name": "scanelf", "Identifier": { "PURL": "pkg:apk/alpine/scanelf@1.2.2-r1?arch=x86_64\u0026distro=3.7.1", - "UID": "88086fdb5a997cfd" + "UID": "8520c35436819" }, "Version": "1.2.2-r1", "Arch": "x86_64", "SrcName": "pax-utils", "SrcVersion": "1.2.2-r1", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "musl@1.1.18-r3" @@ -7721,14 +7721,14 @@ "Name": "serf", "Identifier": { "PURL": "pkg:apk/alpine/serf@1.3.9-r3?arch=x86_64\u0026distro=3.7.1", - "UID": "34d5052ef0071707" + "UID": "e052e8031c85839d" }, "Version": "1.3.9-r3", "Arch": "x86_64", "SrcName": "serf", "SrcVersion": "1.3.9-r3", "Licenses": [ - "ASL2.0" + "Apache-2.0" ], "DependsOn": [ "apr-util@1.6.1-r1", @@ -7780,14 +7780,14 @@ "Name": "ssl_client", "Identifier": { "PURL": "pkg:apk/alpine/ssl_client@1.27.2-r11?arch=x86_64\u0026distro=3.7.1", - "UID": "eab2a79208d922b" + "UID": "740ce998f526d2c2" }, "Version": "1.27.2-r11", "Arch": "x86_64", "SrcName": "busybox", "SrcVersion": "1.27.2-r11", "Licenses": [ - "GPL-2.0" + "GPL-2.0-only" ], "DependsOn": [ "libressl2.6-libtls@2.6.5-r0", @@ -7930,14 +7930,14 @@ "Name": "tar", "Identifier": { "PURL": "pkg:apk/alpine/tar@1.29-r1?arch=x86_64\u0026distro=3.7.1", - "UID": "636f4bd512a19da9" + "UID": "35fcd0737165df45" }, "Version": "1.29-r1", "Arch": "x86_64", "SrcName": "tar", "SrcVersion": "1.29-r1", "Licenses": [ - "GPL-3.0" + "GPL-2.0-or-later" ], "DependsOn": [ "musl@1.1.18-r3" diff --git a/pkg/flag/license_flags.go b/pkg/flag/license_flags.go index 5f4e148af5b2..5ff4c912e2a9 100644 --- a/pkg/flag/license_flags.go +++ b/pkg/flag/license_flags.go @@ -2,7 +2,7 @@ package flag import ( "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/licensing" + "github.com/aquasecurity/trivy/pkg/licensing/expression" ) var ( @@ -26,37 +26,37 @@ var ( // LicenseForbidden is an option only in a config file LicenseForbidden = Flag[[]string]{ ConfigName: "license.forbidden", - Default: licensing.ForbiddenLicenses, + Default: expression.ForbiddenLicenses, Usage: "forbidden licenses", } // LicenseRestricted is an option only in a config file LicenseRestricted = Flag[[]string]{ ConfigName: "license.restricted", - Default: licensing.RestrictedLicenses, + Default: expression.RestrictedLicenses, Usage: "restricted licenses", } // LicenseReciprocal is an option only in a config file LicenseReciprocal = Flag[[]string]{ ConfigName: "license.reciprocal", - Default: licensing.ReciprocalLicenses, + Default: expression.ReciprocalLicenses, Usage: "reciprocal licenses", } // LicenseNotice is an option only in a config file LicenseNotice = Flag[[]string]{ ConfigName: "license.notice", - Default: licensing.NoticeLicenses, + Default: expression.NoticeLicenses, Usage: "notice licenses", } // LicensePermissive is an option only in a config file LicensePermissive = Flag[[]string]{ ConfigName: "license.permissive", - Default: licensing.PermissiveLicenses, + Default: expression.PermissiveLicenses, Usage: "permissive licenses", } // LicenseUnencumbered is an option only in a config file LicenseUnencumbered = Flag[[]string]{ ConfigName: "license.unencumbered", - Default: licensing.UnencumberedLicenses, + Default: expression.UnencumberedLicenses, Usage: "unencumbered licenses", } ) diff --git a/pkg/licensing/category.go b/pkg/licensing/expression/category.go similarity index 96% rename from pkg/licensing/category.go rename to pkg/licensing/expression/category.go index f99ccdc52af4..c32f228c07d8 100644 --- a/pkg/licensing/category.go +++ b/pkg/licensing/expression/category.go @@ -1,4 +1,4 @@ -package licensing +package expression // Canonical names of the licenses. // ported from https://github.com/google/licenseclassifier/blob/7c62d6fe8d3aa2f39c4affb58c9781d9dc951a2d/license_type.go#L24-L177 @@ -165,6 +165,28 @@ const ( ZPL21 = "ZPL-2.1" ) +// GNU licenses always use either the suffix “-only“ or the suffix “-or-later“ +// https://spdx.dev/learn/handling-license-info/ +var GnuLicenses = []string{ + AGPL10, + AGPL30, + GFDL11WithInvariants, + GFDL11NoInvariants, + GFDL11, + GFDL12WithInvariants, + GFDL12NoInvariants, + GFDL12, + GFDL13WithInvariants, + GFDL13NoInvariants, + GFDL13, + GPL10, + GPL20, + GPL30, + LGPL20, + LGPL21, + LGPL30, +} + var ( // ForbiddenLicenses - Licenses that are forbidden to be used. // ported from https://github.com/google/licenseclassifier/blob/7c62d6fe8d3aa2f39c4affb58c9781d9dc951a2d/license_type.go#L340-L364 diff --git a/pkg/licensing/expression/expression.go b/pkg/licensing/expression/expression.go index 59848e57eef7..6f3c0054f929 100644 --- a/pkg/licensing/expression/expression.go +++ b/pkg/licensing/expression/expression.go @@ -11,7 +11,7 @@ var ( ErrInvalidExpression = xerrors.New("invalid expression error") ) -type NormalizeFunc func(license string) string +type NormalizeFunc func(license string) SimpleExpr func parse(license string) (Expression, error) { l := NewLexer(strings.NewReader(license)) @@ -38,7 +38,9 @@ func normalize(expr Expression, fn ...NormalizeFunc) Expression { switch e := expr.(type) { case SimpleExpr: for _, f := range fn { - e.license = f(e.license) + normalized := f(e.License) + e.License = normalized.License + e.HasPlus = e.HasPlus || normalized.HasPlus } return e case CompoundExpr: @@ -52,10 +54,10 @@ func normalize(expr Expression, fn ...NormalizeFunc) Expression { } // NormalizeForSPDX replaces ' ' to '-' in license-id. -// SPDX license MUST NOT be white space between a license-id. +// SPDX license MUST NOT have white space between a license-id. // There MUST be white space on either side of the operator "WITH". // ref: https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions -func NormalizeForSPDX(s string) string { +func NormalizeForSPDX(s string) SimpleExpr { var b strings.Builder for _, c := range s { switch { @@ -70,7 +72,7 @@ func NormalizeForSPDX(s string) string { _, _ = b.WriteRune('-') } } - return b.String() + return SimpleExpr{License: b.String(), HasPlus: false} } func isAlphabet(r rune) bool { diff --git a/pkg/licensing/expression/expression_test.go b/pkg/licensing/expression/expression_test.go index 0e3eaa7ae7cc..12b715e7ad30 100644 --- a/pkg/licensing/expression/expression_test.go +++ b/pkg/licensing/expression/expression_test.go @@ -37,7 +37,7 @@ func TestNormalize(t *testing.T) { { name: "upper", license: "LGPL-2.1-only OR MIT", - fn: strings.ToUpper, + fn: func(license string) SimpleExpr { return SimpleExpr{strings.ToUpper(license), false} }, want: "LGPL-2.1-ONLY OR MIT", }, } diff --git a/pkg/licensing/expression/parser.go.y b/pkg/licensing/expression/parser.go.y index 82bd11028619..85a90a5a73ce 100644 --- a/pkg/licensing/expression/parser.go.y +++ b/pkg/licensing/expression/parser.go.y @@ -32,17 +32,17 @@ license simple : IDENT { - $$ = SimpleExpr{license: $1.literal} + $$ = SimpleExpr{License: $1.literal} } | simple IDENT /* e.g. Public Domain */ { - $$ = SimpleExpr{license: $1.String() + " " + $2.literal} + $$ = SimpleExpr{License: $1.String() + " " + $2.literal} } plus : simple '+' { - $$ = SimpleExpr{license: $1.String(), hasPlus: true} + $$ = SimpleExpr{License: $1.String(), HasPlus: true} } compound diff --git a/pkg/licensing/expression/parser_gen.go b/pkg/licensing/expression/parser_gen.go index 32f65276374e..57c51b320e5d 100644 --- a/pkg/licensing/expression/parser_gen.go +++ b/pkg/licensing/expression/parser_gen.go @@ -452,19 +452,19 @@ yydefault: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:34 { - yyVAL.expr = SimpleExpr{license: yyDollar[1].token.literal} + yyVAL.expr = SimpleExpr{License: yyDollar[1].token.literal} } case 3: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:38 { - yyVAL.expr = SimpleExpr{license: yyDollar[1].expr.String() + " " + yyDollar[2].token.literal} + yyVAL.expr = SimpleExpr{License: yyDollar[1].expr.String() + " " + yyDollar[2].token.literal} } case 4: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:44 { - yyVAL.expr = SimpleExpr{license: yyDollar[1].expr.String(), hasPlus: true} + yyVAL.expr = SimpleExpr{License: yyDollar[1].expr.String(), HasPlus: true} } case 5: yyDollar = yyS[yypt-1 : yypt+1] diff --git a/pkg/licensing/expression/parser_test.go b/pkg/licensing/expression/parser_test.go index 522777085b5b..acbc09a53545 100644 --- a/pkg/licensing/expression/parser_test.go +++ b/pkg/licensing/expression/parser_test.go @@ -20,7 +20,7 @@ func TestParse(t *testing.T) { name: "single license", input: "Public Domain", want: SimpleExpr{ - license: "Public Domain", + License: "Public Domain", }, wantStr: "Public Domain", }, @@ -28,7 +28,7 @@ func TestParse(t *testing.T) { name: "tag:value license", input: "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", want: SimpleExpr{ - license: "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", + License: "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", }, wantStr: "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", }, @@ -36,8 +36,8 @@ func TestParse(t *testing.T) { name: "symbols", input: "Public ._-+", want: SimpleExpr{ - license: "Public ._-", - hasPlus: true, + License: "Public ._-", + HasPlus: true, }, wantStr: "Public ._-+", }, @@ -47,7 +47,7 @@ func TestParse(t *testing.T) { want: CompoundExpr{ left: CompoundExpr{ left: SimpleExpr{ - license: "Public Domain", + License: "Public Domain", }, conjunction: Token{ token: AND, @@ -55,15 +55,15 @@ func TestParse(t *testing.T) { }, right: CompoundExpr{ left: SimpleExpr{ - license: "GPLv2", - hasPlus: true, + License: "GPLv2", + HasPlus: true, }, conjunction: Token{ token: OR, literal: "or", }, right: SimpleExpr{ - license: "AFL", + License: "AFL", }, }, }, @@ -73,15 +73,15 @@ func TestParse(t *testing.T) { }, right: CompoundExpr{ left: SimpleExpr{ - license: "LGPLv2", - hasPlus: true, + License: "LGPLv2", + HasPlus: true, }, conjunction: Token{ token: WITH, literal: "with", }, right: SimpleExpr{ - license: "distribution exceptions", + License: "distribution exceptions", }, }, }, @@ -92,7 +92,7 @@ func TestParse(t *testing.T) { input: "Public Domain AND ( GPLv2+ or AFL AND ( CC0 or LGPL1.0) )", want: CompoundExpr{ left: SimpleExpr{ - license: "Public Domain", + License: "Public Domain", }, conjunction: Token{ token: AND, @@ -100,8 +100,8 @@ func TestParse(t *testing.T) { }, right: CompoundExpr{ left: SimpleExpr{ - license: "GPLv2", - hasPlus: true, + License: "GPLv2", + HasPlus: true, }, conjunction: Token{ token: OR, @@ -109,7 +109,7 @@ func TestParse(t *testing.T) { }, right: CompoundExpr{ left: SimpleExpr{ - license: "AFL", + License: "AFL", }, conjunction: Token{ token: AND, @@ -117,14 +117,14 @@ func TestParse(t *testing.T) { }, right: CompoundExpr{ left: SimpleExpr{ - license: "CC0", + License: "CC0", }, conjunction: Token{ token: OR, literal: "or", }, right: SimpleExpr{ - license: "LGPL1.0", + License: "LGPL1.0", }, }, }, diff --git a/pkg/licensing/expression/types.go b/pkg/licensing/expression/types.go index f344c610ab4d..acf2473ed4e8 100644 --- a/pkg/licensing/expression/types.go +++ b/pkg/licensing/expression/types.go @@ -3,30 +3,8 @@ package expression import ( "fmt" "slices" - - "github.com/aquasecurity/trivy/pkg/licensing" ) -var versioned = []string{ - licensing.AGPL10, - licensing.AGPL30, - licensing.GFDL11WithInvariants, - licensing.GFDL11NoInvariants, - licensing.GFDL11, - licensing.GFDL12WithInvariants, - licensing.GFDL12NoInvariants, - licensing.GFDL12, - licensing.GFDL13WithInvariants, - licensing.GFDL13NoInvariants, - licensing.GFDL13, - licensing.GPL10, - licensing.GPL20, - licensing.GPL30, - licensing.LGPL20, - licensing.LGPL21, - licensing.LGPL30, -} - type Expression interface { String() string } @@ -37,24 +15,24 @@ type Token struct { } type SimpleExpr struct { - license string - hasPlus bool + License string + HasPlus bool } func (s SimpleExpr) String() string { - if slices.Contains(versioned, s.license) { - if s.hasPlus { + if slices.Contains(GnuLicenses, s.License) { + if s.HasPlus { // e.g. AGPL-1.0-or-later - return s.license + "-or-later" + return s.License + "-or-later" } // e.g. GPL-1.0-only - return s.license + "-only" + return s.License + "-only" } - if s.hasPlus { - return s.license + "+" + if s.HasPlus { + return s.License + "+" } - return s.license + return s.License } type CompoundExpr struct { diff --git a/pkg/licensing/normalize.go b/pkg/licensing/normalize.go index 4ca8eab00030..4ce294275214 100644 --- a/pkg/licensing/normalize.go +++ b/pkg/licensing/normalize.go @@ -3,160 +3,569 @@ package licensing import ( "regexp" "strings" -) - -var mapping = map[string]string{ - // GPL - "GPL-1": GPL10, - "GPL-1+": GPL10, - "GPL 1.0": GPL10, - "GPL 1": GPL10, - "GPL2": GPL20, - "GPL 2.0": GPL20, - "GPL 2": GPL20, - "GPL-2": GPL20, - "GPL-2.0-ONLY": GPL20, - "GPL2+": GPL20, - "GPLV2": GPL20, - "GPLV2+": GPL20, - "GPL-2+": GPL20, - "GPL-2.0+": GPL20, - "GPL-2.0-OR-LATER": GPL20, - "GPL-2+ WITH AUTOCONF EXCEPTION": GPL20withautoconfexception, - "GPL-2+-with-bison-exception": GPL20withbisonexception, - "GPL3": GPL30, - "GPL 3.0": GPL30, - "GPL 3": GPL30, - "GPLV3": GPL30, - "GPLV3+": GPL30, - "GPL-3": GPL30, - "GPL-3.0-ONLY": GPL30, - "GPL3+": GPL30, - "GPL-3+": GPL30, - "GPL-3.0-OR-LATER": GPL30, - "GPL-3+ WITH AUTOCONF EXCEPTION": GPL30withautoconfexception, - "GPL-3+-WITH-BISON-EXCEPTION": GPL20withbisonexception, - "GPL": GPL30, // 2? 3? - - // LGPL - "LGPL2": LGPL20, - "LGPL 2": LGPL20, - "LGPL 2.0": LGPL20, - "LGPL-2": LGPL20, - "LGPL2+": LGPL20, - "LGPL-2+": LGPL20, - "LGPL-2.0+": LGPL20, - "LGPL-2.1": LGPL21, - "LGPL 2.1": LGPL21, - "LGPL-2.1+": LGPL21, - "LGPLV2.1+": LGPL21, - "LGPL-3": LGPL30, - "LGPL 3": LGPL30, - "LGPL-3+": LGPL30, - "LGPL": LGPL30, // 2? 3? - "GNU LESSER": LGPL30, // 2? 3? - - // MPL - "MPL1.0": MPL10, - "MPL1": MPL10, - "MPL 1.0": MPL10, - "MPL 1": MPL10, - "MPL2.0": MPL20, - "MPL 2.0": MPL20, - "MPL2": MPL20, - "MPL 2": MPL20, - - // BSD - "BSD": BSD3Clause, // 2? 3? - "BSD-2-CLAUSE": BSD2Clause, - "BSD-3-CLAUSE": BSD3Clause, - "BSD-4-CLAUSE": BSD4Clause, - "BSD 2 CLAUSE": BSD2Clause, - "BSD 2-CLAUSE": BSD2Clause, - "BSD 2-CLAUSE LICENSE": BSD2Clause, - "THE BSD 2-CLAUSE LICENSE": BSD2Clause, - "THE 2-CLAUSE BSD LICENSE": BSD2Clause, - "TWO-CLAUSE BSD-STYLE LICENSE": BSD2Clause, - "BSD 3 CLAUSE": BSD3Clause, - "BSD 3-CLAUSE": BSD3Clause, - "BSD 3-CLAUSE LICENSE": BSD3Clause, - "THE BSD 3-CLAUSE LICENSE": BSD3Clause, - "BSD 3-CLAUSE \"NEW\" OR \"REVISED\" LICENSE (BSD-3-CLAUSE)": BSD3Clause, - "ECLIPSE DISTRIBUTION LICENSE (NEW BSD LICENSE)": BSD3Clause, - "NEW BSD LICENSE": BSD3Clause, - "MODIFIED BSD LICENSE": BSD3Clause, - "REVISED BSD": BSD3Clause, - "REVISED BSD LICENSE": BSD3Clause, - "THE NEW BSD LICENSE": BSD3Clause, - "3-CLAUSE BSD LICENSE": BSD3Clause, - "BSD 3-CLAUSE NEW LICENSE": BSD3Clause, - "BSD LICENSE": BSD3Clause, - "EDL 1.0": BSD3Clause, - "ECLIPSE DISTRIBUTION LICENSE - V 1.0": BSD3Clause, - "ECLIPSE DISTRIBUTION LICENSE V. 1.0": BSD3Clause, - "ECLIPSE DISTRIBUTION LICENSE V1.0": BSD3Clause, - "THE BSD LICENSE": BSD4Clause, - // APACHE - "APACHE LICENSE": Apache10, - "APACHE SOFTWARE LICENSES": Apache10, - "APACHE": Apache20, // 1? 2? - "APACHE 2.0": Apache20, - "APACHE 2": Apache20, - "APACHE V2": Apache20, - "APACHE 2.0 LICENSE": Apache20, - "APACHE SOFTWARE LICENSE, VERSION 2.0": Apache20, - "THE APACHE SOFTWARE LICENSE, VERSION 2.0": Apache20, - "APACHE LICENSE (V2.0)": Apache20, - "APACHE LICENSE 2.0": Apache20, - "APACHE LICENSE V2.0": Apache20, - "APACHE LICENSE VERSION 2.0": Apache20, - "APACHE LICENSE, VERSION 2.0": Apache20, - "APACHE PUBLIC LICENSE 2.0": Apache20, - "APACHE SOFTWARE LICENSE - VERSION 2.0": Apache20, - "THE APACHE LICENSE, VERSION 2.0": Apache20, - "APACHE-2.0 LICENSE": Apache20, - "APACHE 2 STYLE LICENSE": Apache20, - "ASF 2.0": Apache20, + expr "github.com/aquasecurity/trivy/pkg/licensing/expression" +) - // CC0-1.0 - "CC0 1.0 UNIVERSAL": CC010, - "PUBLIC DOMAIN, PER CREATIVE COMMONS CC0": CC010, +func licence(name string, hasPlus bool) expr.SimpleExpr { + return expr.SimpleExpr{License: name, HasPlus: hasPlus} +} - // CDDL 1.0 - "CDDL 1.0": CDDL10, - "CDDL LICENSE": CDDL10, - "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) VERSION 1.0": CDDL10, - "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) V1.0": CDDL10, +var mapping = map[string]expr.SimpleExpr{ + // Simple mappings (i.e. that could be parsed by SpdxExpression.parse, at least without space) + // modified from https://github.com/oss-review-toolkit/ort/blob/fc5389c2cfd9c8b009794c8a11f5c91321b7a730/utils/spdx/src/main/resources/simple-license-mapping.yml - // CDDL 1.1 - "CDDL 1.1": CDDL11, - "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) VERSION 1.1": CDDL11, - "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) V1.1": CDDL11, + // Ambiguous simple mappings (mapping reason not obvious without additional information) + "AFL": licence(expr.AFL30, false), + "AGPL": licence(expr.AGPL30, false), + "AL-2": licence(expr.Apache20, false), + "AL-2.0": licence(expr.Apache20, false), + "APACHE": licence(expr.Apache20, false), + "APACHE-STYLE": licence(expr.Apache20, false), + "ARTISTIC": licence(expr.Artistic20, false), + "ASL": licence(expr.Apache20, false), + "BSD": licence(expr.BSD3Clause, false), + "BSD*": licence(expr.BSD3Clause, false), + "BSD-LIKE": licence(expr.BSD3Clause, false), + "BSD-STYLE": licence(expr.BSD3Clause, false), + "BSD-VARIANT": licence(expr.BSD3Clause, false), + "CDDL": licence(expr.CDDL10, false), + "ECLIPSE": licence(expr.EPL10, false), + "EPL": licence(expr.EPL10, false), + "EUPL": licence(expr.EUPL10, false), + "FDL": licence(expr.GFDL13, true), + "GFDL": licence(expr.GFDL13, true), + "GPL": licence(expr.GPL20, true), + "LGPL": licence(expr.LGPL20, true), + "MPL": licence(expr.MPL20, false), + "NETSCAPE": licence(expr.NPL11, false), + "PYTHON": licence(expr.Python20, false), + "ZOPE": licence(expr.ZPL21, false), - // EPL 1.0 - "ECLIPSE PUBLIC LICENSE - VERSION 1.0": EPL10, - "ECLIPSE PUBLIC LICENSE (EPL) 1.0": EPL10, - "ECLIPSE PUBLIC LICENSE V1.0": EPL10, - "ECLIPSE PUBLIC LICENSE, VERSION 1.0": EPL10, - "ECLIPSE PUBLIC LICENSE - V 1.0": EPL10, - "ECLIPSE PUBLIC LICENSE - V1.0": EPL10, - "ECLIPSE PUBLIC LICENSE (EPL), VERSION 1.0": EPL10, + // Non-ambiguous simple mappings + "0BSD": licence(expr.ZeroBSD, false), + "AFL-1.1": licence(expr.AFL11, false), + "AFL-1.2": licence(expr.AFL12, false), + "AFL-2": licence(expr.AFL20, false), + "AFL-2.0": licence(expr.AFL20, false), + "AFL-2.1": licence(expr.AFL21, false), + "AFL-3.0": licence(expr.AFL30, false), + "AGPL-1.0": licence(expr.AGPL10, false), + "AGPL-3.0": licence(expr.AGPL30, false), + "APACHE-1": licence(expr.Apache10, false), + "APACHE-1.0": licence(expr.Apache10, false), + "APACHE-1.1": licence(expr.Apache11, false), + "APACHE-2": licence(expr.Apache20, false), + "APACHE-2.0": licence(expr.Apache20, false), + "APL-2": licence(expr.Apache20, false), + "APL-2.0": licence(expr.Apache20, false), + "APSL-1.0": licence(expr.APSL10, false), + "APSL-1.1": licence(expr.APSL11, false), + "APSL-1.2": licence(expr.APSL12, false), + "APSL-2.0": licence(expr.APSL20, false), + "ARTISTIC-1.0": licence(expr.Artistic10, false), + "ARTISTIC-1.0-CL-8": licence(expr.Artistic10cl8, false), + "ARTISTIC-1.0-PERL": licence(expr.Artistic10Perl, false), + "ARTISTIC-2.0": licence(expr.Artistic20, false), + "ASF-1": licence(expr.Apache10, false), + "ASF-1.0": licence(expr.Apache10, false), + "ASF-1.1": licence(expr.Apache11, false), + "ASF-2": licence(expr.Apache20, false), + "ASF-2.0": licence(expr.Apache20, false), + "ASL-1": licence(expr.Apache10, false), + "ASL-1.0": licence(expr.Apache10, false), + "ASL-1.1": licence(expr.Apache11, false), + "ASL-2": licence(expr.Apache20, false), + "ASL-2.0": licence(expr.Apache20, false), + "BCL": licence(expr.BCL, false), + "BEERWARE": licence(expr.Beerware, false), + "BOOST": licence(expr.BSL10, false), + "BOOST-1.0": licence(expr.BSL10, false), + "BOUNCY": licence(expr.MIT, false), + "BSD-2": licence(expr.BSD2Clause, false), + "BSD-2-CLAUSE": licence(expr.BSD2Clause, false), + "BSD-2-CLAUSE-FREEBSD": licence(expr.BSD2ClauseFreeBSD, false), + "BSD-2-CLAUSE-NETBSD": licence(expr.BSD2ClauseNetBSD, false), + "BSD-3": licence(expr.BSD3Clause, false), + "BSD-3-CLAUSE": licence(expr.BSD3Clause, false), + "BSD-3-CLAUSE-ATTRIBUTION": licence(expr.BSD3ClauseAttribution, false), + "BSD-3-CLAUSE-CLEAR": licence(expr.BSD3ClauseClear, false), + "BSD-3-CLAUSE-LBNL": licence(expr.BSD3ClauseLBNL, false), + "BSD-4": licence(expr.BSD4Clause, false), + "BSD-4-CLAUSE": licence(expr.BSD4Clause, false), + "BSD-4-CLAUSE-UC": licence(expr.BSD4ClauseUC, false), + "BSD-PROTECTION": licence(expr.BSDProtection, false), + "BSL": licence(expr.BSL10, false), + "BSL-1.0": licence(expr.BSL10, false), + "CC-BY-1.0": licence(expr.CCBY10, false), + "CC-BY-2.0": licence(expr.CCBY20, false), + "CC-BY-2.5": licence(expr.CCBY25, false), + "CC-BY-3.0": licence(expr.CCBY30, false), + "CC-BY-4.0": licence(expr.CCBY40, false), + "CC-BY-NC-1.0": licence(expr.CCBYNC10, false), + "CC-BY-NC-2.0": licence(expr.CCBYNC20, false), + "CC-BY-NC-2.5": licence(expr.CCBYNC25, false), + "CC-BY-NC-3.0": licence(expr.CCBYNC30, false), + "CC-BY-NC-4.0": licence(expr.CCBYNC40, false), + "CC-BY-NC-ND-1.0": licence(expr.CCBYNCND10, false), + "CC-BY-NC-ND-2.0": licence(expr.CCBYNCND20, false), + "CC-BY-NC-ND-2.5": licence(expr.CCBYNCND25, false), + "CC-BY-NC-ND-3.0": licence(expr.CCBYNCND30, false), + "CC-BY-NC-ND-4.0": licence(expr.CCBYNCND40, false), + "CC-BY-NC-SA-1.0": licence(expr.CCBYNCSA10, false), + "CC-BY-NC-SA-2.0": licence(expr.CCBYNCSA20, false), + "CC-BY-NC-SA-2.5": licence(expr.CCBYNCSA25, false), + "CC-BY-NC-SA-3.0": licence(expr.CCBYNCSA30, false), + "CC-BY-NC-SA-4.0": licence(expr.CCBYNCSA40, false), + "CC-BY-ND-1.0": licence(expr.CCBYND10, false), + "CC-BY-ND-2.0": licence(expr.CCBYND20, false), + "CC-BY-ND-2.5": licence(expr.CCBYND25, false), + "CC-BY-ND-3.0": licence(expr.CCBYND30, false), + "CC-BY-ND-4.0": licence(expr.CCBYND40, false), + "CC-BY-SA-1.0": licence(expr.CCBYSA10, false), + "CC-BY-SA-2.0": licence(expr.CCBYSA20, false), + "CC-BY-SA-2.5": licence(expr.CCBYSA25, false), + "CC-BY-SA-3.0": licence(expr.CCBYSA30, false), + "CC-BY-SA-4.0": licence(expr.CCBYSA40, false), + "CC0": licence(expr.CC010, false), + "CC0-1.0": licence(expr.CC010, false), + "CDDL-1": licence(expr.CDDL10, false), + "CDDL-1.0": licence(expr.CDDL10, false), + "CDDL-1.1": licence(expr.CDDL11, false), + "COMMONS-CLAUSE": licence(expr.CommonsClause, false), + "CPAL": licence(expr.CPAL10, false), + "CPAL-1.0": licence(expr.CPAL10, false), + "CPL": licence(expr.CPL10, false), + "CPL-1.0": licence(expr.CPL10, false), + "ECLIPSE-1.0": licence(expr.EPL10, false), + "ECLIPSE-2.0": licence(expr.EPL20, false), + "EDL-1.0": licence(expr.BSD3Clause, false), + "EGENIX": licence(expr.EGenix, false), + "EPL-1.0": licence(expr.EPL10, false), + "EPL-2.0": licence(expr.EPL20, false), + "EUPL-1.0": licence(expr.EUPL10, false), + "EUPL-1.1": licence(expr.EUPL11, false), + "EXPAT": licence(expr.MIT, false), + "FACEBOOK-2-CLAUSE": licence(expr.Facebook2Clause, false), + "FACEBOOK-3-CLAUSE": licence(expr.Facebook3Clause, false), + "FACEBOOK-EXAMPLES": licence(expr.FacebookExamples, false), + "FREEIMAGE": licence(expr.FreeImage, false), + "FTL": licence(expr.FTL, false), + "GFDL-1.1": licence(expr.GFDL11, false), + "GFDL-1.1-INVARIANTS": licence(expr.GFDL11WithInvariants, false), + "GFDL-1.1-NO-INVARIANTS": licence(expr.GFDL11NoInvariants, false), + "GFDL-1.2": licence(expr.GFDL12, false), + "GFDL-1.2-INVARIANTS": licence(expr.GFDL12WithInvariants, false), + "GFDL-1.2-NO-INVARIANTS": licence(expr.GFDL12NoInvariants, false), + "GFDL-1.3": licence(expr.GFDL13, false), + "GFDL-1.3-INVARIANTS": licence(expr.GFDL13WithInvariants, false), + "GFDL-1.3-NO-INVARIANTS": licence(expr.GFDL13NoInvariants, false), + "GFDL-NIV-1.3": licence(expr.GFDL13NoInvariants, false), + "GO": licence(expr.BSD3Clause, false), + "GPL-1": licence(expr.GPL10, false), + "GPL-1.0": licence(expr.GPL10, false), + "GPL-2": licence(expr.GPL20, false), + "GPL-2+-WITH-BISON-EXCEPTION": licence(expr.GPL20withbisonexception, true), + "GPL-2.0": licence(expr.GPL20, false), + "GPL-2.0-WITH-AUTOCONF-EXCEPTION": licence(expr.GPL20withautoconfexception, false), + "GPL-2.0-WITH-BISON-EXCEPTION": licence(expr.GPL20withbisonexception, false), + "GPL-2.0-WITH-CLASSPATH-EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GPL-2.0-WITH-FONT-EXCEPTION": licence(expr.GPL20withfontexception, false), + "GPL-2.0-WITH-GCC-EXCEPTION": licence(expr.GPL20withGCCexception, false), + "GPL-3": licence(expr.GPL30, false), + "GPL-3+-WITH-BISON-EXCEPTION": licence(expr.GPL20withbisonexception, true), + "GPL-3.0": licence(expr.GPL30, false), + "GPL-3.0-WITH-AUTOCONF-EXCEPTION": licence(expr.GPL30withautoconfexception, false), + "GPL-3.0-WITH-GCC-EXCEPTION": licence(expr.GPL30withGCCexception, false), + "GPLV2+CE": licence(expr.GPL20withclasspathexception, true), + "GUST-FONT": licence(expr.GUSTFont, false), + "HSQLDB": licence(expr.BSD3Clause, false), + "IMAGEMAGICK": licence(expr.ImageMagick, false), + "IPL-1.0": licence(expr.IPL10, false), + "ISC": licence(expr.ISC, false), + "ISCL": licence(expr.ISC, false), + "JQUERY": licence(expr.MIT, false), + "LGPL-2": licence(expr.LGPL20, false), + "LGPL-2.0": licence(expr.LGPL20, false), + "LGPL-2.1": licence(expr.LGPL21, false), + "LGPL-3": licence(expr.LGPL30, false), + "LGPL-3.0": licence(expr.LGPL30, false), + "LGPLLR": licence(expr.LGPLLR, false), + "LIBPNG": licence(expr.Libpng, false), + "LIL-1.0": licence(expr.Lil10, false), + "LINUX-OPENIB": licence(expr.LinuxOpenIB, false), + "LPL-1.0": licence(expr.LPL10, false), + "LPL-1.02": licence(expr.LPL102, false), + "LPPL-1.3C": licence(expr.LPPL13c, false), + "MIT": licence(expr.MIT, false), + // MIT No Attribution (MIT-0) is not yet supported by google/licenseclassifier + "MIT-0": licence(expr.MIT, false), + "MIT-LIKE": licence(expr.MIT, false), + "MIT-STYLE": licence(expr.MIT, false), + "MPL-1": licence(expr.MPL10, false), + "MPL-1.0": licence(expr.MPL10, false), + "MPL-1.1": licence(expr.MPL11, false), + "MPL-2": licence(expr.MPL20, false), + "MPL-2.0": licence(expr.MPL20, false), + "MS-PL": licence(expr.MSPL, false), + "NCSA": licence(expr.NCSA, false), + "NPL-1.0": licence(expr.NPL10, false), + "NPL-1.1": licence(expr.NPL11, false), + "OFL-1.1": licence(expr.OFL11, false), + "OPENSSL": licence(expr.OpenSSL, false), + "OPENVISION": licence(expr.OpenVision, false), + "OSL-1": licence(expr.OSL10, false), + "OSL-1.0": licence(expr.OSL10, false), + "OSL-1.1": licence(expr.OSL11, false), + "OSL-2": licence(expr.OSL20, false), + "OSL-2.0": licence(expr.OSL20, false), + "OSL-2.1": licence(expr.OSL21, false), + "OSL-3": licence(expr.OSL30, false), + "OSL-3.0": licence(expr.OSL30, false), + "PHP-3.0": licence(expr.PHP30, false), + "PHP-3.01": licence(expr.PHP301, false), + "PIL": licence(expr.PIL, false), + "POSTGRESQL": licence(expr.PostgreSQL, false), + "PYTHON-2": licence(expr.Python20, false), + "PYTHON-2.0": licence(expr.Python20, false), + "PYTHON-2.0-COMPLETE": licence(expr.Python20complete, false), + "QPL-1": licence(expr.QPL10, false), + "QPL-1.0": licence(expr.QPL10, false), + "RUBY": licence(expr.Ruby, false), + "SGI-B-1.0": licence(expr.SGIB10, false), + "SGI-B-1.1": licence(expr.SGIB11, false), + "SGI-B-2.0": licence(expr.SGIB20, false), + "SISSL": licence(expr.SISSL, false), + "SISSL-1.2": licence(expr.SISSL12, false), + "SLEEPYCAT": licence(expr.Sleepycat, false), + "UNICODE-DFS-2015": licence(expr.UnicodeDFS2015, false), + "UNICODE-DFS-2016": licence(expr.UnicodeDFS2016, false), + "UNICODE-TOU": licence(expr.UnicodeTOU, false), + "UNLICENSE": licence(expr.Unlicense, false), + "UNLICENSED": licence(expr.Unlicense, false), + "UPL-1": licence(expr.UPL10, false), + "UPL-1.0": licence(expr.UPL10, false), + "W3C": licence(expr.W3C, false), + "W3C-19980720": licence(expr.W3C19980720, false), + "W3C-20150513": licence(expr.W3C20150513, false), + "W3CL": licence(expr.W3C, false), + "WTF": licence(expr.WTFPL, false), + "WTFPL": licence(expr.WTFPL, false), + "X11": licence(expr.X11, false), + "XNET": licence(expr.Xnet, false), + "ZEND-2": licence(expr.Zend20, false), + "ZEND-2.0": licence(expr.Zend20, false), + "ZLIB": licence(expr.Zlib, false), + "ZLIB-ACKNOWLEDGEMENT": licence(expr.ZlibAcknowledgement, false), + "ZOPE-1.1": licence(expr.ZPL11, false), + "ZOPE-2.0": licence(expr.ZPL20, false), + "ZOPE-2.1": licence(expr.ZPL21, false), + "ZPL-1.1": licence(expr.ZPL11, false), + "ZPL-2.0": licence(expr.ZPL20, false), + "ZPL-2.1": licence(expr.ZPL21, false), - // EPL 2.0 - "ECLIPSE PUBLIC LICENSE - VERSION 2.0": EPL20, - "EPL 2.0": EPL20, - "ECLIPSE PUBLIC LICENSE - V 2.0": EPL20, - "ECLIPSE PUBLIC LICENSE V2.0": EPL20, - "ECLIPSE PUBLIC LICENSE, VERSION 2.0": EPL20, - "THE ECLIPSE PUBLIC LICENSE VERSION 2.0": EPL20, - "ECLIPSE PUBLIC LICENSE V. 2.0": EPL20, + // Non simple declared mappings + // modified from https://github.com/oss-review-toolkit/ort/blob/fc5389c2cfd9c8b009794c8a11f5c91321b7a730/utils/spdx/src/main/resources/declared-license-mapping.yml - "RUBY": Ruby, - "ZLIB": Zlib, + // Ambiguous declared mappings (mapping reason not obvious without additional information) + "ACADEMIC FREE LICENSE (AFL)": licence(expr.AFL21, false), + "APACHE SOFTWARE LICENSES": licence(expr.Apache20, false), + "APACHE SOFTWARE": licence(expr.Apache20, false), + "APPLE PUBLIC SOURCE": licence(expr.APSL10, false), + "BSD SOFTWARE": licence(expr.BSD2Clause, false), + "BSD STYLE": licence(expr.BSD3Clause, false), + "COMMON DEVELOPMENT AND DISTRIBUTION": licence(expr.CDDL10, false), + "CREATIVE COMMONS - BY": licence(expr.CCBY30, false), + "CREATIVE COMMONS ATTRIBUTION": licence(expr.CCBY30, false), + "CREATIVE COMMONS": licence(expr.CCBY30, false), + "ECLIPSE PUBLIC LICENSE (EPL)": licence(expr.EPL10, false), + "GENERAL PUBLIC LICENSE (GPL)": licence(expr.GPL20, true), + "GNU FREE DOCUMENTATION LICENSE (FDL)": licence(expr.GFDL13, true), + "GNU GENERAL PUBLIC LIBRARY": licence(expr.GPL30, true), + "GNU GENERAL PUBLIC LICENSE (GPL)": licence(expr.GPL30, true), + "GNU GPL": licence(expr.GPL20, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL)": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC": licence(expr.LGPL21, false), + "GNU LESSER PUBLIC": licence(expr.LGPL21, false), + "GNU LESSER": licence(expr.LGPL21, false), + "GNU LGPL": licence(expr.LGPL21, false), + "GNU LIBRARY OR LESSER GENERAL PUBLIC LICENSE (LGPL)": licence(expr.LGPL21, false), + "GNU PUBLIC": licence(expr.GPL20, true), + "GPL (WITH DUAL LICENSING OPTION)": licence(expr.GPL20, false), + "GPLV2 WITH EXCEPTIONS": licence(expr.GPL20withclasspathexception, false), + "INDIVIDUAL BSD": licence(expr.BSD3Clause, false), + "LESSER GENERAL PUBLIC LICENSE (LGPL)": licence(expr.LGPL21, true), + "LGPL WITH EXCEPTIONS": licence(expr.LGPL30, false), + "LPGL, SEE LICENSE FILE.": licence(expr.LGPL30, true), + "MOZILLA PUBLIC": licence(expr.MPL20, false), + "ZOPE PUBLIC": licence(expr.ZPL21, false), - // Public Domain - "PUBLIC DOMAIN": Unlicense, + // Non-ambiguous declared mappings + "(NEW) BSD": licence(expr.BSD3Clause, false), + "2-CLAUSE BSD": licence(expr.BSD2Clause, false), + "2-CLAUSE BSDL": licence(expr.BSD2Clause, false), + "3-CLAUSE BDSL": licence(expr.BSD3Clause, false), + "3-CLAUSE BSD": licence(expr.BSD3Clause, false), + "ACADEMIC FREE LICENSE (AFL-2.1": licence(expr.AFL21, false), + "AFFERO GENERAL PUBLIC LICENSE (AGPL-3": licence(expr.AGPL30, false), + "APACHE 2 STYLE": licence(expr.Apache20, false), + "APACHE LICENSE, ASL-2.0": licence(expr.Apache20, false), + "APACHE LICENSE, VERSION 2.0 (HTTP://WWW.APACHE.ORG/LICENSES/LICENSE-2.0": licence(expr.Apache20, false), + "APACHE PUBLIC-1.1": licence(expr.Apache11, false), + "APACHE PUBLIC-2": licence(expr.Apache20, false), + "APACHE PUBLIC-2.0": licence(expr.Apache20, false), + "APACHE SOFTWARE LICENSE (APACHE-2": licence(expr.Apache20, false), + "APACHE SOFTWARE LICENSE (APACHE-2.0": licence(expr.Apache20, false), + "APACHE SOFTWARE-1.1": licence(expr.Apache11, false), + "APACHE SOFTWARE-2": licence(expr.Apache20, false), + "APACHE SOFTWARE-2.0": licence(expr.Apache20, false), + "APACHE VERSION 2.0, JANUARY 2004": licence(expr.Apache20, false), + "APACHE-2.0 */ ' " =END --": licence(expr.Apache20, false), + "BERKELEY SOFTWARE DISTRIBUTION (BSD)": licence(expr.BSD2Clause, false), + "BOOST SOFTWARE LICENSE 1.0 (BSL-1.0": licence(expr.BSL10, false), + "BOOST SOFTWARE": licence(expr.BSL10, false), + "BOUNCY CASTLE": licence(expr.MIT, false), + "BSD (3-CLAUSE)": licence(expr.BSD3Clause, false), + "BSD - SEE NDG/HTTPSCLIENT/LICENSE FILE FOR DETAILS": licence(expr.BSD3Clause, false), + "BSD 2 CLAUSE": licence(expr.BSD2Clause, false), + "BSD 2-CLAUSE": licence(expr.BSD2Clause, false), + "BSD 3 CLAUSE": licence(expr.BSD3Clause, false), + "BSD 3-CLAUSE NEW": licence(expr.BSD3Clause, false), + "BSD 3-CLAUSE \"NEW\" OR \"REVISED\" LICENSE (BSD-3-CLAUSE)": licence(expr.BSD3Clause, false), + "BSD 3-CLAUSE": licence(expr.BSD3Clause, false), + "BSD 4 CLAUSE": licence(expr.BSD4Clause, false), + "BSD 4-CLAUSE": licence(expr.BSD4Clause, false), + "BSD FOUR CLAUSE": licence(expr.BSD4Clause, false), + "BSD LICENSE FOR HSQL": licence(expr.BSD3Clause, false), + "BSD NEW": licence(expr.BSD3Clause, false), + "BSD THREE CLAUSE": licence(expr.BSD3Clause, false), + "BSD TWO CLAUSE": licence(expr.BSD2Clause, false), + "BSD-3 CLAUSE": licence(expr.BSD3Clause, false), + "BSD-STYLE + ATTRIBUTION": licence(expr.BSD3ClauseAttribution, false), + "CC BY-NC-SA-2.0": licence(expr.CCBYNCSA20, false), + "CC BY-NC-SA-2.5": licence(expr.CCBYNCSA25, false), + "CC BY-NC-SA-3.0": licence(expr.CCBYNCSA30, false), + "CC BY-NC-SA-4.0": licence(expr.CCBYNCSA40, false), + "CC BY-SA-2.0": licence(expr.CCBYSA20, false), + "CC BY-SA-2.5": licence(expr.CCBYSA25, false), + "CC BY-SA-3.0": licence(expr.CCBYSA30, false), + "CC BY-SA-4.0": licence(expr.CCBYSA40, false), + "CC0 1.0 UNIVERSAL (CC0 1.0) PUBLIC DOMAIN DEDICATION": licence(expr.CC010, false), + "CC0 1.0 UNIVERSAL": licence(expr.CC010, false), + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL)-1.0": licence(expr.CDDL10, false), + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL)-1.1": licence(expr.CDDL11, false), + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE 1.0 (CDDL-1.0": licence(expr.CDDL10, false), + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE 1.1 (CDDL-1.1": licence(expr.CDDL11, false), + "COMMON PUBLIC": licence(expr.CPL10, false), + "COMMON PUBLIC-1.0": licence(expr.CPL10, false), + "CREATIVE COMMONS - ATTRIBUTION 4.0 INTERNATIONAL": licence(expr.CCBY40, false), + "CREATIVE COMMONS 3.0 BY-SA": licence(expr.CCBYSA30, false), + "CREATIVE COMMONS ATTRIBUTION 3.0 UNPORTED (CC BY-3.0": licence(expr.CCBY30, false), + "CREATIVE COMMONS ATTRIBUTION 4.0 INTERNATIONAL (CC BY-4.0": licence(expr.CCBY40, false), + "CREATIVE COMMONS ATTRIBUTION 4.0 INTERNATIONAL PUBLIC": licence(expr.CCBY40, false), + "CREATIVE COMMONS ATTRIBUTION-1.0": licence(expr.CCBY10, false), + "CREATIVE COMMONS ATTRIBUTION-2.5": licence(expr.CCBY25, false), + "CREATIVE COMMONS ATTRIBUTION-3.0": licence(expr.CCBY30, false), + "CREATIVE COMMONS ATTRIBUTION-4.0": licence(expr.CCBY40, false), + "CREATIVE COMMONS ATTRIBUTION-NONCOMMERCIAL 4.0 INTERNATIONAL": licence(expr.CCBYNC40, false), + "CREATIVE COMMONS ATTRIBUTION-NONCOMMERCIAL-NODERIVATIVES 4.0 INTERNATIONAL": licence(expr.CCBYNCND40, false), + "CREATIVE COMMONS ATTRIBUTION-NONCOMMERCIAL-SHAREALIKE 3.0 UNPORTED (CC BY-NC-SA-3.0": licence(expr.CCBYNCSA30, false), + "CREATIVE COMMONS ATTRIBUTION-NONCOMMERCIAL-SHAREALIKE 4.0 INTERNATIONAL PUBLIC": licence(expr.CCBYNCSA40, false), + "CREATIVE COMMONS CC0": licence(expr.CC010, false), + "CREATIVE COMMONS GNU LGPL-2.1": licence(expr.LGPL21, false), + "CREATIVE COMMONS LICENSE ATTRIBUTION-NODERIVS 3.0 UNPORTED": licence(expr.CCBYNCND30, false), + "CREATIVE COMMONS LICENSE ATTRIBUTION-NONCOMMERCIAL-SHAREALIKE 3.0 UNPORTED": licence(expr.CCBYNCSA30, false), + "CREATIVE COMMONS ZERO": licence(expr.CC010, false), + "CREATIVE COMMONS-3.0": licence(expr.CCBY30, false), + "ECLIPSE DISTRIBUTION LICENSE (EDL)-1.0": licence(expr.BSD3Clause, false), + "ECLIPSE DISTRIBUTION LICENSE (NEW BSD LICENSE)": licence(expr.BSD3Clause, false), + "ECLIPSE DISTRIBUTION-1.0": licence(expr.BSD3Clause, false), + "ECLIPSE PUBLIC LICENSE (EPL)-1.0": licence(expr.EPL10, false), + "ECLIPSE PUBLIC LICENSE (EPL)-2.0": licence(expr.EPL20, false), + "ECLIPSE PUBLIC LICENSE 1.0 (EPL-1.0": licence(expr.EPL10, false), + "ECLIPSE PUBLIC LICENSE 2.0 (EPL-2.0": licence(expr.EPL20, false), + "ECLIPSE PUBLIC": licence(expr.EPL10, false), + "ECLIPSE PUBLIC-1.0": licence(expr.EPL10, false), + "ECLIPSE PUBLIC-2.0": licence(expr.EPL20, false), + "ECLIPSE PUBLISH-1.0": licence(expr.EPL10, false), + "EPL (ECLIPSE PUBLIC LICENSE)-1.0": licence(expr.EPL10, false), + "EU PUBLIC LICENSE 1.0 (EUPL-1.0": licence(expr.EUPL10, false), + "EU PUBLIC LICENSE 1.1 (EUPL-1.1": licence(expr.EUPL11, false), + "EUROPEAN UNION PUBLIC LICENSE (EUPL-1.0": licence(expr.EUPL10, false), + "EUROPEAN UNION PUBLIC LICENSE (EUPL-1.1": licence(expr.EUPL11, false), + "EUROPEAN UNION PUBLIC LICENSE 1.0 (EUPL-1.0": licence(expr.EUPL10, false), + "EUROPEAN UNION PUBLIC LICENSE 1.1 (EUPL-1.1": licence(expr.EUPL11, false), + "EUROPEAN UNION PUBLIC-1.0": licence(expr.EUPL10, false), + "EUROPEAN UNION PUBLIC-1.1": licence(expr.EUPL11, false), + "EXPAT (MIT/X11)": licence(expr.MIT, false), + "GENERAL PUBLIC LICENSE 2.0 (GPL)": licence(expr.GPL20, false), + "GNU AFFERO GENERAL PUBLIC LICENSE V3 (AGPL-3": licence(expr.AGPL30, false), + "GNU AFFERO GENERAL PUBLIC LICENSE V3 (AGPL-3.0": licence(expr.AGPL30, false), + "GNU AFFERO GENERAL PUBLIC LICENSE V3 OR LATER (AGPL3+)": licence(expr.AGPL30, true), + "GNU AFFERO GENERAL PUBLIC LICENSE V3 OR LATER (AGPLV3+)": licence(expr.AGPL30, true), + "GNU AFFERO GENERAL PUBLIC-3": licence(expr.AGPL30, false), + "GNU FREE DOCUMENTATION LICENSE (GFDL-1.3": licence(expr.GFDL13, false), + "GNU GENERAL LESSER PUBLIC LICENSE (LGPL)-2.1": licence(expr.LGPL21, false), + "GNU GENERAL LESSER PUBLIC LICENSE (LGPL)-3.0": licence(expr.LGPL30, false), + "GNU GENERAL PUBLIC LICENSE (GPL), VERSION 2, WITH CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC LICENSE (GPL), VERSION 2, WITH THE CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC LICENSE (GPL)-2": licence(expr.GPL20, false), + "GNU GENERAL PUBLIC LICENSE (GPL)-3": licence(expr.GPL30, false), + "GNU GENERAL PUBLIC LICENSE V2 (GPL-2": licence(expr.GPL20, false), + "GNU GENERAL PUBLIC LICENSE V2 OR LATER (GPLV2+)": licence(expr.GPL20, true), + "GNU GENERAL PUBLIC LICENSE V2.0 ONLY, WITH CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC LICENSE V3 (GPL-3": licence(expr.GPL30, false), + "GNU GENERAL PUBLIC LICENSE V3 OR LATER (GPLV3+)": licence(expr.GPL30, true), + "GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL-2": licence(expr.GPL20, false), + "GNU GENERAL PUBLIC LICENSE VERSION 2, JUNE 1991": licence(expr.GPL20, false), + "GNU GENERAL PUBLIC LICENSE VERSION 3 (GPL-3": licence(expr.GPL30, false), + "GNU GENERAL PUBLIC LICENSE, VERSION 2 (GPL2), WITH THE CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC LICENSE, VERSION 2 WITH THE CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC LICENSE, VERSION 2 WITH THE GNU CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC LICENSE, VERSION 2, WITH THE CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GNU GENERAL PUBLIC-2": licence(expr.GPL20, false), + "GNU GENERAL PUBLIC-3": licence(expr.GPL30, false), + "GNU GPL-2": licence(expr.GPL20, false), + "GNU GPL-3": licence(expr.GPL30, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL)-2": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL)-2.0": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL)-2.1": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL)-3": licence(expr.LGPL30, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL)-3.0": licence(expr.LGPL30, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL-2": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL-2.0": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL-2.1": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL-3": licence(expr.LGPL30, false), + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL-3.0": licence(expr.LGPL30, false), + "GNU LESSER GENERAL PUBLIC LICENSE V2 (LGPL-2": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC LICENSE V2 OR LATER (LGPLV2+)": licence(expr.LGPL20, true), + "GNU LESSER GENERAL PUBLIC LICENSE V3 (LGPL-3": licence(expr.LGPL30, false), + "GNU LESSER GENERAL PUBLIC LICENSE V3 OR LATER (LGPLV3+)": licence(expr.LGPL30, true), + "GNU LESSER GENERAL PUBLIC LICENSE VERSION 2.1 (LGPL-2.1": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC LICENSE VERSION 2.1, FEBRUARY 1999": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC LICENSE, VERSION 2.1, FEBRUARY 1999": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC-2": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC-2.0": licence(expr.LGPL20, false), + "GNU LESSER GENERAL PUBLIC-2.1": licence(expr.LGPL21, false), + "GNU LESSER GENERAL PUBLIC-3": licence(expr.LGPL30, false), + "GNU LESSER GENERAL PUBLIC-3.0": licence(expr.LGPL30, false), + "GNU LGP (GNU GENERAL PUBLIC LICENSE)-2": licence(expr.LGPL20, false), + "GNU LGPL (GNU LESSER GENERAL PUBLIC LICENSE)-2.1": licence(expr.LGPL21, false), + "GNU LGPL-2": licence(expr.LGPL20, false), + "GNU LGPL-2.0": licence(expr.LGPL20, false), + "GNU LGPL-2.1": licence(expr.LGPL21, false), + "GNU LGPL-3": licence(expr.LGPL30, false), + "GNU LGPL-3.0": licence(expr.LGPL30, false), + "GNU LIBRARY GENERAL PUBLIC-2.0": licence(expr.LGPL20, false), + "GNU LIBRARY GENERAL PUBLIC-2.1": licence(expr.LGPL21, false), + "GNU LIBRARY OR LESSER GENERAL PUBLIC LICENSE VERSION 2.0 (LGPL-2": licence(expr.LGPL20, false), + "GNU LIBRARY OR LESSER GENERAL PUBLIC LICENSE VERSION 3.0 (LGPL-3": licence(expr.LGPL30, false), + "GPL (≥ 3)": licence(expr.GPL30, true), + "GPL 2 WITH CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GPL V2 WITH CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GPL-2+ WITH AUTOCONF EXCEPTION": licence(expr.GPL20withautoconfexception, true), + "GPL-3+ WITH AUTOCONF EXCEPTION": licence(expr.GPL30withautoconfexception, true), + "GPL2 W/ CPE": licence(expr.GPL20withclasspathexception, false), + "GPLV2 LICENSE, INCLUDES THE CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "GPLV2 WITH CLASSPATH EXCEPTION": licence(expr.GPL20withclasspathexception, false), + "HSQLDB LICENSE, A BSD OPEN SOURCE": licence(expr.BSD3Clause, false), + "HTTP://ANT-CONTRIB.SOURCEFORGE.NET/TASKS/LICENSE.TXT": licence(expr.Apache11, false), + "HTTP://ASM.OW2.ORG/LICENSE.HTML": licence(expr.BSD3Clause, false), + "HTTP://CREATIVECOMMONS.ORG/PUBLICDOMAIN/ZERO/1.0/LEGALCODE": licence(expr.CC010, false), + "HTTP://EN.WIKIPEDIA.ORG/WIKI/ZLIB_LICENSE": licence(expr.Zlib, false), + "HTTP://JSON.CODEPLEX.COM/LICENSE": licence(expr.MIT, false), + "HTTP://POLYMER.GITHUB.IO/LICENSE.TXT": licence(expr.BSD3Clause, false), + "HTTP://WWW.APACHE.ORG/LICENSES/LICENSE-2.0": licence(expr.Apache20, false), + "HTTP://WWW.APACHE.ORG/LICENSES/LICENSE-2.0.HTML": licence(expr.Apache20, false), + "HTTP://WWW.APACHE.ORG/LICENSES/LICENSE-2.0.TXT": licence(expr.Apache20, false), + "HTTP://WWW.GNU.ORG/COPYLEFT/LESSER.HTML": licence(expr.LGPL30, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-ND/1.0": licence(expr.CCBYNCND10, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-ND/2.0": licence(expr.CCBYNCND20, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-ND/2.5": licence(expr.CCBYNCND25, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-ND/3.0": licence(expr.CCBYNCND30, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-ND/4.0": licence(expr.CCBYNCND40, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-SA/1.0": licence(expr.CCBYNCSA10, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-SA/2.0": licence(expr.CCBYNCSA20, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-SA/2.5": licence(expr.CCBYNCSA25, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-SA/3.0": licence(expr.CCBYNCSA30, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-NC-SA/4.0": licence(expr.CCBYNCSA40, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-ND/1.0": licence(expr.CCBYND10, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-ND/2.0": licence(expr.CCBYND20, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-ND/2.5": licence(expr.CCBYND25, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-ND/3.0": licence(expr.CCBYND30, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-ND/4.0": licence(expr.CCBYND40, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-SA/1.0": licence(expr.CCBYSA10, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-SA/2.0": licence(expr.CCBYSA20, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-SA/2.5": licence(expr.CCBYSA25, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-SA/3.0": licence(expr.CCBYSA30, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY-SA/4.0": licence(expr.CCBYSA40, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY/1.0": licence(expr.CCBY10, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY/2.0": licence(expr.CCBY20, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY/2.5": licence(expr.CCBY25, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY/3.0": licence(expr.CCBY30, false), + "HTTPS://CREATIVECOMMONS.ORG/LICENSES/BY/4.0": licence(expr.CCBY40, false), + "HTTPS://CREATIVECOMMONS.ORG/PUBLICDOMAIN/ZERO/1.0/": licence(expr.CC010, false), + "HTTPS://GITHUB.COM/DOTNET/CORE-SETUP/BLOB/MASTER/LICENSE.TXT": licence(expr.MIT, false), + "HTTPS://GITHUB.COM/DOTNET/COREFX/BLOB/MASTER/LICENSE.TXT": licence(expr.MIT, false), + "HTTPS://RAW.GITHUB.COM/RDFLIB/RDFLIB/MASTER/LICENSE": licence(expr.BSD3Clause, false), + "HTTPS://RAW.GITHUBUSERCONTENT.COM/ASPNET/ASPNETCORE/2.0.0/LICENSE.TXT": licence(expr.Apache20, false), + "HTTPS://RAW.GITHUBUSERCONTENT.COM/ASPNET/HOME/2.0.0/LICENSE.TXT": licence(expr.Apache20, false), + "HTTPS://RAW.GITHUBUSERCONTENT.COM/NUGET/NUGET.CLIENT/DEV/LICENSE.TXT": licence(expr.Apache20, false), + "HTTPS://WWW.APACHE.ORG/LICENSES/LICENSE-2.0": licence(expr.Apache20, false), + "HTTPS://WWW.ECLIPSE.ORG/LEGAL/EPL-V10.HTML": licence(expr.EPL10, false), + "HTTPS://WWW.ECLIPSE.ORG/LEGAL/EPL-V20.HTML": licence(expr.EPL20, false), + "IBM PUBLIC": licence(expr.IPL10, false), + "ISC LICENSE (ISCL)": licence(expr.ISC, false), + "JYTHON SOFTWARE": licence(expr.Python20, false), + "KIRKK.COM BSD": licence(expr.BSD3Clause, false), + "LESSER GENERAL PUBLIC LICENSE, VERSION 3 OR GREATER": licence(expr.LGPL30, true), + "LICENSE AGREEMENT FOR OPEN SOURCE COMPUTER VISION LIBRARY (3-CLAUSE BSD LICENSE)": licence(expr.BSD3Clause, false), + "MIT (HTTP://MOOTOOLS.NET/LICENSE.TXT)": licence(expr.MIT, false), + "MIT / HTTP://REM.MIT-LICENSE.ORG": licence(expr.MIT, false), + "MIT LICENSE (HTTP://OPENSOURCE.ORG/LICENSES/MIT)": licence(expr.MIT, false), + "MIT LICENSE (MIT)": licence(expr.MIT, false), + "MIT LICENSE(MIT)": licence(expr.MIT, false), + "MIT LICENSED. HTTP://WWW.OPENSOURCE.ORG/LICENSES/MIT-LICENSE.PHP": licence(expr.MIT, false), + "MIT/EXPAT": licence(expr.MIT, false), + "MOCKRUNNER LICENSE, BASED ON APACHE SOFTWARE-1.1": licence(expr.Apache11, false), + "MODIFIED BSD": licence(expr.BSD3Clause, false), + "MOZILLA PUBLIC LICENSE 1.0 (MPL)": licence(expr.MPL10, false), + "MOZILLA PUBLIC LICENSE 1.1 (MPL-1.1": licence(expr.MPL11, false), + "MOZILLA PUBLIC LICENSE 2.0 (MPL-2.0": licence(expr.MPL20, false), + "MOZILLA PUBLIC-1.0": licence(expr.MPL10, false), + "MOZILLA PUBLIC-1.1": licence(expr.MPL11, false), + "MOZILLA PUBLIC-2.0": licence(expr.MPL20, false), + "NCSA OPEN SOURCE": licence(expr.NCSA, false), + "NETSCAPE PUBLIC LICENSE (NPL)": licence(expr.NPL10, false), + "NETSCAPE PUBLIC": licence(expr.NPL10, false), + "NEW BSD": licence(expr.BSD3Clause, false), + "OPEN SOFTWARE LICENSE 3.0 (OSL-3.0": licence(expr.OSL30, false), + "OPEN SOFTWARE-3.0": licence(expr.OSL30, false), + "PERL ARTISTIC-2": licence(expr.Artistic10Perl, false), + // Note: public domain without a specific license should not be mapped + // see https://wiki.spdx.org/view/Legal_Team/Decisions/Dealing_with_Public_Domain_within_SPDX_Files + // and https://opensource.google/documentation/reference/thirdparty/licenses#unencumbered + "PUBLIC DOMAIN (CC0-1.0)": licence(expr.CC010, false), + "PUBLIC DOMAIN, PER CREATIVE COMMONS CC0": licence(expr.CC010, false), + "QT PUBLIC LICENSE (QPL)": licence(expr.QPL10, false), + "QT PUBLIC": licence(expr.QPL10, false), + "REVISED BSD": licence(expr.BSD3Clause, false), + "RUBY'S": licence(expr.Ruby, false), + "SEQUENCE LIBRARY LICENSE (BSD-LIKE)": licence(expr.BSD3Clause, false), + "SIL OPEN FONT LICENSE 1.1 (OFL-1.1": licence(expr.OFL11, false), + "SIL OPEN FONT-1.1": licence(expr.OFL11, false), + "SIMPLIFIED BSD LISCENCE": licence(expr.BSD2Clause, false), + "SIMPLIFIED BSD": licence(expr.BSD2Clause, false), + "SUN INDUSTRY STANDARDS SOURCE LICENSE (SISSL)": licence(expr.SISSL, false), + "THREE-CLAUSE BSD-STYLE": licence(expr.BSD3Clause, false), + "TWO-CLAUSE BSD-STYLE": licence(expr.BSD2Clause, false), + "UNIVERSAL PERMISSIVE LICENSE (UPL)": licence(expr.UPL10, false), + "UNIVERSAL PERMISSIVE-1.0": licence(expr.UPL10, false), + "UNLICENSE (UNLICENSE)": licence(expr.Unlicense, false), + "W3C SOFTWARE": licence(expr.W3C, false), + "ZLIB / LIBPNG": licence(expr.ZlibAcknowledgement, false), + "ZLIB/LIBPNG": licence(expr.ZlibAcknowledgement, false), + "['MIT']": licence(expr.MIT, false), } const ( @@ -181,8 +590,6 @@ var pythonLicenseExceptions = map[string]string{ // 'BSD-3-CLAUSE and GPL-2' => {"BSD-3-CLAUSE", "GPL-2"} // 'GPL-1+ or Artistic, and BSD-4-clause-POWERDOG' => {"GPL-1+", "Artistic", "BSD-4-clause-POWERDOG"} // 'BSD 3-Clause License or Apache License, Version 2.0' => {"BSD 3-Clause License", "Apache License, Version 2.0"} -// var LicenseSplitRegexp = regexp.MustCompile("(,?[_ ]+or[_ ]+)|(,?[_ ]+and[_ ])|(,[ ]*)") - var licenseSplitRegexp = regexp.MustCompile("(,?[_ ]+(?:or|and)[_ ]+)|(,[ ]*)") // Typical keywords for license texts @@ -218,12 +625,65 @@ func TrimLicenseText(text string) string { return strings.Join(s[:n], " ") + "..." } -func Normalize(name string) string { +// version number match +var versionRegexpString = "([A-UW-Z)]{2,})( LICENSE)?\\s*[,(-]?\\s*(V|V\\.|VER|VER\\.|VERSION|VERSION-|-)?\\s*([1-9](\\.\\d)*)[)]?" + +// case insensitive version match anywhere in string +var versionRegexp = regexp.MustCompile("(?i)" + versionRegexpString) + +// version suffix match +var versionSuffixRegexp = regexp.MustCompile(versionRegexpString + "$") + +// suffixes from https://spdx.dev/learn/handling-license-info/ +var onlySuffixes = [2]string{"-ONLY", " ONLY"} +var plusSuffixes = [3]string{"+", "-OR-LATER", " OR LATER"} + +func standardizeKeyAndSuffix(name string) expr.SimpleExpr { + // Standardize space, including newline + name = strings.Join(strings.Fields(name), " ") name = strings.TrimSpace(name) - if l, ok := mapping[strings.ToUpper(name)]; ok { - return l + name = strings.ToUpper(name) + // Do not perform any further normalization for URLs + if strings.HasPrefix(name, "HTTP") { + return expr.SimpleExpr{License: name, HasPlus: false} + } + name = strings.ReplaceAll(name, "LICENCE", "LICENSE") + name = strings.TrimPrefix(name, "THE ") + name = strings.TrimSuffix(name, " LICENSE") + name = strings.TrimSuffix(name, " LICENSED") + name = strings.TrimSuffix(name, "-LICENSE") + name = strings.TrimSuffix(name, "-LICENSED") + // Remove License and Licensed suffixes except for licenses already containing those suffixes such as Unlicense + if name != "UNLICENSE" { + name = strings.TrimSuffix(name, "LICENSE") + } + if name != "UNLICENSED" { + name = strings.TrimSuffix(name, "LICENSED") + } + hasPlus := false + for _, s := range plusSuffixes { + if strings.HasSuffix(name, s) { + name = strings.TrimSuffix(name, s) + hasPlus = true + } } - return name + for _, s := range onlySuffixes { + name = strings.TrimSuffix(name, s) + } + name = versionSuffixRegexp.ReplaceAllString(name, "$1-$4") + return expr.SimpleExpr{License: name, HasPlus: hasPlus} +} + +func Normalize(name string) string { + return NormalizeLicense(name).String() +} + +func NormalizeLicense(name string) expr.SimpleExpr { + normalized := standardizeKeyAndSuffix(name) + if found, ok := mapping[normalized.License]; ok { + return expr.SimpleExpr{License: found.License, HasPlus: found.HasPlus || normalized.HasPlus} + } + return expr.SimpleExpr{License: name, HasPlus: false} } func SplitLicenses(str string) []string { @@ -261,3 +721,25 @@ func SplitLicenses(str string) []string { } return licenses } + +// Split license string considering spaces as separator +// e.g. MPL 2.0 GPL2+ => {"MPL2.0", "GPL2+"} +func LaxSplitLicenses(str string) []string { + if str == "" { + return nil + } + var licenses []string + str = versionRegexp.ReplaceAllString(str, "$1-$4") + for _, s := range strings.Fields(str) { + s = strings.Trim(s, "()") + switch { + case s == "": + continue + case s == "AND" || s == "OR": + continue + default: + licenses = append(licenses, Normalize(s)) + } + } + return licenses +} diff --git a/pkg/licensing/normalize_private_test.go b/pkg/licensing/normalize_private_test.go new file mode 100644 index 000000000000..68d62f241944 --- /dev/null +++ b/pkg/licensing/normalize_private_test.go @@ -0,0 +1,18 @@ +package licensing + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// All map keys must be standardized to be matched +// (uppercase, no common suffixes, standardized version, etc.) +func TestMap(t *testing.T) { + for key := range mapping { + t.Run(key, func(t *testing.T) { + standardized := standardizeKeyAndSuffix(key) + assert.Equal(t, standardized.License, key) + }) + } +} diff --git a/pkg/licensing/normalize_test.go b/pkg/licensing/normalize_test.go index 16fa9eac6250..9b47fd923f29 100644 --- a/pkg/licensing/normalize_test.go +++ b/pkg/licensing/normalize_test.go @@ -8,6 +8,220 @@ import ( "github.com/aquasecurity/trivy/pkg/licensing" ) +func TestNormalize(t *testing.T) { + tests := []struct { + licenses []string + normalized string + normalizedKey string + }{ + { + licenses: []string{ + " the apache license ", + " the\tapache \r\nlicense \r\n ", + " apache ", + "ApacheLicence", + "ApacheLicense", + "al-2", + "al-v2", + "al2", + "alv2", + "apache - v 2.0", + "apache - v. 2.0", + "apache - ver 2.0", + "apache - version 2.0", + "apache 2", + "apache 2.0", + "apache license (2.0)", + "apache license (v. 2)", + "apache license (v. 2.0)", + "apache license (v2)", + "apache license (v2.0)", + "apache license (version 2.0)", + "apache license 2", + "apache license 2.0", + "apache license v2", + "apache license v2.0", + "apache license version 2", + "apache license version 2.0", + "apache license", + "apache license, 2.0", + "apache license, asl version 2.0", + "apache license, version 2", + "apache license, version 2.0 (http://www.apache.org/licenses/license-2.0)", + "apache license, version 2.0", + "apache license,version 2.0", + "apache license,version-2.0", + "apache license-2.0", + "apache public 2.0", + "apache public license 2.0", + "apache public license-2.0", + "apache public-2", + "apache public-2.0", + "apache software license (apache-2.0)", + "apache software license - version 2.0", + "apache software license 2.0", + "apache software license, version 2", + "apache software license, version 2.0", + "apache software-2.0", + "apache v 2.0", + "apache v. 2.0", + "apache v2", + "apache v2.0", + "apache ver 2.0", + "apache ver. 2.0", + "apache version 2.0", + "apache version 2.0, january 2004", + "apache version-2", + "apache version-2.0", + "apache", + "apache, 2", + "apache, v2.0", + "apache, version 2", + "apache, version 2.0", + "apache-2", + "apache-2.0", + "apache-licence", + "apache-license", + "apache-licensed", + "apache-licensed", + "asf 2.0", + "asl 2", + "asl, version 2", + "asl2.0", + "the apache license", + "the apache license", + }, + normalized: "Apache-2.0", + normalizedKey: "Apache-2.0", + }, + { + licenses: []string{ + "Apache+", + }, + normalized: "Apache-2.0+", + normalizedKey: "Apache-2.0", + }, + { + licenses: []string{ + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) V1.1", + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) VERSION 1.1", + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL), VERSION 1.1", + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE 1.1 (CDDL-1.1)", + }, + normalized: "CDDL-1.1", + normalizedKey: "CDDL-1.1", + }, + { + licenses: []string{ + "ECLIPSE PUBLIC LICENSE (EPL) 1.0", + "ECLIPSE PUBLIC LICENSE (EPL), VERSION 1.0", + "ECLIPSE PUBLIC LICENSE - V 1.0", + "ECLIPSE PUBLIC LICENSE - V1.0", + "ECLIPSE PUBLIC LICENSE - VERSION 1.0", + "ECLIPSE PUBLIC LICENSE 1.0 (EPL-1.0)", + "ECLIPSE PUBLIC LICENSE 1.0", + "ECLIPSE PUBLIC LICENSE V. 1.0", + "ECLIPSE PUBLIC LICENSE V1.0", + "ECLIPSE PUBLIC LICENSE VERSION 1.0", + "ECLIPSE PUBLIC LICENSE, VERSION 1.0", + "ECLIPSE PUBLIC", + }, + normalized: "EPL-1.0", + normalizedKey: "EPL-1.0", + }, + { + licenses: []string{ + "EUROPEAN UNION PUBLIC LICENSE (EUPL V.1.1)", + "EUROPEAN UNION PUBLIC LICENSE 1.1 (EUPL 1.1)", + "EUROPEAN UNION PUBLIC LICENSE 1.1", + "EUROPEAN UNION PUBLIC LICENSE, VERSION 1.1", + }, + normalized: "EUPL-1.1", + normalizedKey: "EUPL-1.1", + }, + { + licenses: []string{ + "GPL-or-later", + "GPL+", + "GPL-2.0-only+", + }, + normalized: "GPL-2.0-or-later", + normalizedKey: "GPL-2.0", + }, + { + licenses: []string{ + "GPL (≥ 3)", + "GPL3+", + "GPL3-or-later", + "GPL3 or later licence", + }, + normalized: "GPL-3.0-or-later", + normalizedKey: "GPL-3.0", + }, + { + licenses: []string{ + "GNU GENERAL PUBLIC LICENSE 3", + "GNU GENERAL PUBLIC LICENSE (GPL) V. 3", + "GNU GENERAL PUBLIC LICENSE VERSION 3 (GPL V3)", + }, + normalized: "GPL-3.0-only", + normalizedKey: "GPL-3.0", + }, + + { + licenses: []string{ + "LGPL LICENSE-3", + "GNU LESSER GENERAL PUBLIC LICENSE V3", + "GNU LESSER GENERAL PUBLIC LICENSE V3.0", + "GNU LESSER GENERAL PUBLIC LICENSE VERSION 3", + "GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0", + "GNU LESSER GENERAL PUBLIC LICENSE, VERSION 3.0", + "GNU LIBRARY OR LESSER GENERAL PUBLIC LICENSE VERSION 3.0 (LGPLV3)", + "GNU GENERAL LESSER PUBLIC LICENSE (LGPL) VERSION 3.0", + "GNU LESSER GENERAL PUBLIC LICENSE (LGPL), VERSION 3", + }, + normalized: "LGPL-3.0-only", + normalizedKey: "LGPL-3.0", + }, + { + licenses: []string{ + "The Unlicense", + "Unlicense", + "Unlicensed", + "UNLICENSE", + "UNLICENSED", + }, + normalized: "Unlicense", + normalizedKey: "Unlicense", + }, + { + licenses: []string{ + "MIT License", + "http://json.codeplex.com/license", + }, + normalized: "MIT", + normalizedKey: "MIT", + }, + { + licenses: []string{ + " The unmapped license ", + }, + normalized: " The unmapped license ", + normalizedKey: " The unmapped license ", + }, + } + for _, tt := range tests { + t.Run(tt.normalized, func(t *testing.T) { + for _, ll := range tt.licenses { + normalized := licensing.Normalize(ll) + normalizedKey := licensing.NormalizeLicense(ll).License + assert.Equal(t, tt.normalized, normalized) + assert.Equal(t, tt.normalizedKey, normalizedKey) + } + }) + } +} + func TestSplitLicenses(t *testing.T) { tests := []struct { name string @@ -113,3 +327,28 @@ func TestSplitLicenses(t *testing.T) { }) } } + +func TestLaxSplitLicense(t *testing.T) { + var tests = []struct { + license string + wantLicenses []string + }{ + { + license: "ASL 2.0", + wantLicenses: []string{"Apache-2.0"}, + }, + { + license: "MPL 2.0 GPL2+", + wantLicenses: []string{ + "MPL-2.0", + "GPL-2.0-or-later", + }, + }, + } + for _, tt := range tests { + t.Run(tt.license, func(t *testing.T) { + parsed := licensing.LaxSplitLicenses(tt.license) + assert.Equal(t, tt.wantLicenses, parsed) + }) + } +} diff --git a/pkg/licensing/scanner.go b/pkg/licensing/scanner.go index 100358a5af6c..1a8ed8e1d9f0 100644 --- a/pkg/licensing/scanner.go +++ b/pkg/licensing/scanner.go @@ -21,8 +21,9 @@ func NewScanner(categories map[types.LicenseCategory][]string) Scanner { } func (s *Scanner) Scan(licenseName string) (types.LicenseCategory, string) { + license := NormalizeLicense(licenseName) for category, names := range s.categories { - if slices.Contains(names, licenseName) { + if slices.Contains(names, license.License) { return category, categoryToSeverity(category).String() } } diff --git a/pkg/licensing/scanner_test.go b/pkg/licensing/scanner_test.go index fce93f3c5337..992992e48f9a 100644 --- a/pkg/licensing/scanner_test.go +++ b/pkg/licensing/scanner_test.go @@ -7,6 +7,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/licensing" + "github.com/aquasecurity/trivy/pkg/licensing/expression" ) func TestScanner_Scan(t *testing.T) { @@ -21,11 +22,11 @@ func TestScanner_Scan(t *testing.T) { name: "forbidden", categories: map[types.LicenseCategory][]string{ types.CategoryForbidden: { - licensing.BSD3Clause, - licensing.Apache20, + expression.BSD3Clause, + expression.Apache20, }, }, - licenseName: licensing.Apache20, + licenseName: expression.Apache20, wantCategory: types.CategoryForbidden, wantSeverity: "CRITICAL", }, @@ -33,21 +34,21 @@ func TestScanner_Scan(t *testing.T) { name: "restricted", categories: map[types.LicenseCategory][]string{ types.CategoryForbidden: { - licensing.GPL30, + expression.GPL30, }, types.CategoryRestricted: { - licensing.BSD3Clause, - licensing.Apache20, + expression.BSD3Clause, + expression.Apache20, }, }, - licenseName: licensing.BSD3Clause, + licenseName: expression.BSD3Clause, wantCategory: types.CategoryRestricted, wantSeverity: "HIGH", }, { name: "unknown", categories: make(map[types.LicenseCategory][]string), - licenseName: licensing.BSD3Clause, + licenseName: expression.BSD3Clause, wantCategory: types.CategoryUnknown, wantSeverity: "UNKNOWN", }, diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index 33952ab41441..809ce95d7e4c 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -547,7 +547,7 @@ func NormalizeLicense(licenses []string) string { return fmt.Sprintf("(%s)", license) }), " AND ") - s, err := expression.Normalize(license, licensing.Normalize, expression.NormalizeForSPDX) + s, err := expression.Normalize(license, licensing.NormalizeLicense, expression.NormalizeForSPDX) if err != nil { // Not fail on the invalid license log.Warn("Unable to marshal SPDX licenses", log.String("license", license)) diff --git a/pkg/sbom/spdx/marshal_test.go b/pkg/sbom/spdx/marshal_test.go index ef64cf4651d5..74e9bee0a45d 100644 --- a/pkg/sbom/spdx/marshal_test.go +++ b/pkg/sbom/spdx/marshal_test.go @@ -11,6 +11,7 @@ import ( "github.com/package-url/packageurl-go" "github.com/spdx/tools-golang/spdx" "github.com/spdx/tools-golang/spdx/v2/common" + "github.com/spdx/tools-golang/spdxlib" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -1329,6 +1330,7 @@ func TestMarshaler_Marshal(t *testing.T) { spdxDoc, err := marshaler.MarshalReport(ctx, tc.inputReport) require.NoError(t, err) + assert.NoError(t, spdxlib.ValidateDocument(spdxDoc)) assert.Equal(t, tc.wantSBOM, spdxDoc) }) } @@ -1361,7 +1363,7 @@ func Test_GetLicense(t *testing.T) { "GPLv2+", "LGPL 2.0 or GNU LESSER", }, - want: "GPL-2.0-or-later AND (LGPL-2.0-only OR LGPL-3.0-only)", + want: "GPL-2.0-or-later AND (LGPL-2.0-only OR LGPL-2.1-only)", }, { name: "happy path with AND operator", @@ -1369,7 +1371,7 @@ func Test_GetLicense(t *testing.T) { "GPLv2+", "LGPL 2.0 and GNU LESSER", }, - want: "GPL-2.0-or-later AND LGPL-2.0-only AND LGPL-3.0-only", + want: "GPL-2.0-or-later AND LGPL-2.0-only AND LGPL-2.1-only", }, { name: "happy path with WITH operator", From 8876e7065554648a6a85def697559933d9a55706 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Wed, 11 Sep 2024 12:52:01 +0400 Subject: [PATCH 343/352] docs(db): add a manifest example (#7485) Signed-off-by: knqyf263 --- docs/docs/configuration/db.md | 43 +++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/docs/docs/configuration/db.md b/docs/docs/configuration/db.md index 5fa4046668a9..ccffae1e5302 100644 --- a/docs/docs/configuration/db.md +++ b/docs/docs/configuration/db.md @@ -53,15 +53,44 @@ $ trivy image --download-db-only ``` $ trivy image --db-repository registry.gitlab.com/gitlab-org/security-products/dependencies/trivy-db ``` + +The media type of the OCI layer must be `application/vnd.aquasec.trivy.db.layer.v1.tar+gzip`. +You can reference the OCI manifest of [trivy-db]. + +
+Manifest + +```shell +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.aquasec.trivy.config.v1+json", + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", + "size": 2 + }, + "layers": [ + { + "mediaType": "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip", + "digest": "sha256:29ad6505b8957c7cd4c367e7c705c641a9020d2be256812c5f4cc2fc099f4f02", + "size": 55474933, + "annotations": { + "org.opencontainers.image.title": "db.tar.gz" + } + } + ], + "annotations": { + "org.opencontainers.image.created": "2024-09-11T06:14:51Z" + } +} +``` +
+ !!!note Trivy automatically adds the `trivy-db` schema version as a tag if the tag is not used: `trivy-db-registry:latest` => `trivy-db-registry:latest`, but `trivy-db-registry` => `trivy-db-registry:2`. -!!!note - Trivy expects the OCI Artifacts to have a Specific media type: - - Vulnerability DB `application/vnd.aquasec.trivy.db.layer.v1.tar+gzip` - - Java DB `application/vnd.aquasec.trivy.javadb.layer.v1.tar+gzip` ## Java Index Database The same options are also available for the Java index DB, which is used for scanning Java applications. @@ -76,6 +105,9 @@ Downloading the Java index DB from an external OCI registry can be done by using $ trivy image --java-db-repository registry.gitlab.com/gitlab-org/security-products/dependencies/trivy-java-db --download-java-db-only ``` +The media type of the OCI layer must be `application/vnd.aquasec.trivy.javadb.layer.v1.tar+gzip`. +You can reference the OCI manifest of [trivy-java-db]. + !!!note Trivy automatically adds the `trivy-java-db` schema version as a tag if the tag is not used: @@ -89,3 +121,6 @@ $ trivy clean --vuln-db --java-db 2024-06-24T11:42:31+06:00 INFO Removing vulnerability database... 2024-06-24T11:42:31+06:00 INFO Removing Java database... ``` + +[trivy-db]: https://github.com/aquasecurity/trivy-db/pkgs/container/trivy-db +[trivy-java-db]: https://github.com/aquasecurity/trivy-java-db/pkgs/container/trivy-java-db \ No newline at end of file From b0222feeb586ec59904bb321fda8f3f22496d07b Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:10:13 +0600 Subject: [PATCH 344/352] revert(java): stop supporting of `test` scope for `pom.xml` files (#7488) --- docs/docs/coverage/language/java.md | 18 ++++-------- pkg/dependency/parser/java/pom/artifact.go | 1 - pkg/dependency/parser/java/pom/parse.go | 4 +-- pkg/dependency/parser/java/pom/parse_test.go | 28 ------------------- pkg/dependency/parser/java/pom/pom.go | 1 - .../parser/java/pom/testdata/happy/pom.xml | 6 ---- 6 files changed, 7 insertions(+), 51 deletions(-) diff --git a/docs/docs/coverage/language/java.md b/docs/docs/coverage/language/java.md index 26bad288e552..67cd8c135b9d 100644 --- a/docs/docs/coverage/language/java.md +++ b/docs/docs/coverage/language/java.md @@ -12,12 +12,12 @@ Each artifact supports the following scanners: The following table provides an outline of the features Trivy offers. -| Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | -|------------------|:---------------------:|:------------------:|:------------------------------------:|:--------:|:----------------------------------------:| -| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | Not needed | -| pom.xml | Maven repository [^1] | [Exclude](#scopes) | ✓ | ✓[^7] | - | -| *gradle.lockfile | - | Exclude | ✓ | ✓ | Not needed | -| *.sbt.lock | - | Exclude | - | ✓ | Not needed | +| Artifact | Internet access | Dev dependencies | [Dependency graph][dependency-graph] | Position | [Detection Priority][detection-priority] | +|------------------|:---------------------:|:----------------:|:------------------------------------:|:--------:|:----------------------------------------:| +| JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | Not needed | +| pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | - | +| *gradle.lockfile | - | Exclude | ✓ | ✓ | Not needed | +| *.sbt.lock | - | Exclude | - | ✓ | Not needed | These may be enabled or disabled depending on the target. See [here](./index.md) for the detail. @@ -69,11 +69,6 @@ The vulnerability database will be downloaded anyway. !!! Warning Trivy may skip some dependencies (that were not found on your local machine) when the `--offline-scan` flag is passed. -### scopes -Trivy supports `runtime`, `compile`, `test` and `import` (for `dependencyManagement`) [dependency scopes][dependency-scopes]. -Dependencies without scope are also detected. - -By default, Trivy doesn't report dependencies with `test` scope. Use the `--include-dev-deps` flag to include them. ### maven-invoker-plugin Typically, the integration tests directory (`**/[src|target]/it/*/pom.xml`) of [maven-invoker-plugin][maven-invoker-plugin] doesn't contain actual `pom.xml` files and should be skipped to avoid noise. @@ -125,4 +120,3 @@ Make sure that you have cache[^8] directory to find licenses from `*.pom` depend [maven-pom-repos]: https://maven.apache.org/settings.html#repositories [sbt-dependency-lock]: https://stringbean.github.io/sbt-dependency-lock [detection-priority]: ../../scanner/vulnerability.md#detection-priority -[dependency-scopes]: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope diff --git a/pkg/dependency/parser/java/pom/artifact.go b/pkg/dependency/parser/java/pom/artifact.go index f691afac5ebd..b2e97efb229b 100644 --- a/pkg/dependency/parser/java/pom/artifact.go +++ b/pkg/dependency/parser/java/pom/artifact.go @@ -27,7 +27,6 @@ type artifact struct { Module bool Relationship ftypes.Relationship - Test bool Locations ftypes.Locations } diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index 46c859538529..1add19a4b53b 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -214,7 +214,6 @@ func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ft Licenses: result.artifact.Licenses, Relationship: art.Relationship, Locations: art.Locations, - Test: art.Test, } // save only dependency names @@ -235,7 +234,6 @@ func (p *Parser) parseRoot(root artifact, uniqModules map[string]struct{}) ([]ft Licenses: art.Licenses, Relationship: art.Relationship, Locations: art.Locations, - Dev: art.Test, } pkgs = append(pkgs, pkg) @@ -402,7 +400,7 @@ func (p *Parser) parseDependencies(deps []pomDependency, props map[string]string // Resolve dependencies d = d.Resolve(props, depManagement, rootDepManagement) - if (d.Scope != "" && d.Scope != "compile" && d.Scope != "runtime" && d.Scope != "test") || d.Optional { + if (d.Scope != "" && d.Scope != "compile" && d.Scope != "runtime") || d.Optional { continue } diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go index 77a47b5ecdac..934085d5d536 100644 --- a/pkg/dependency/parser/java/pom/parse_test.go +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -61,19 +61,6 @@ func TestPom_Parse(t *testing.T) { }, }, }, - { - ID: "org.example:example-test:2.0.0", - Name: "org.example:example-test", - Version: "2.0.0", - Relationship: ftypes.RelationshipDirect, - Dev: true, - Locations: ftypes.Locations{ - { - StartLine: 49, - EndLine: 54, - }, - }, - }, }, wantDeps: []ftypes.Dependency{ { @@ -81,7 +68,6 @@ func TestPom_Parse(t *testing.T) { DependsOn: []string{ "org.example:example-api:1.7.30", "org.example:example-runtime:1.0.0", - "org.example:example-test:2.0.0", }, }, }, @@ -123,19 +109,6 @@ func TestPom_Parse(t *testing.T) { }, }, }, - { - ID: "org.example:example-test:2.0.0", - Name: "org.example:example-test", - Version: "2.0.0", - Relationship: ftypes.RelationshipDirect, - Dev: true, - Locations: ftypes.Locations{ - { - StartLine: 49, - EndLine: 54, - }, - }, - }, }, wantDeps: []ftypes.Dependency{ { @@ -143,7 +116,6 @@ func TestPom_Parse(t *testing.T) { DependsOn: []string{ "org.example:example-api:1.7.30", "org.example:example-runtime:1.0.0", - "org.example:example-test:2.0.0", }, }, }, diff --git a/pkg/dependency/parser/java/pom/pom.go b/pkg/dependency/parser/java/pom/pom.go index d27f995217d6..889d107c3c6c 100644 --- a/pkg/dependency/parser/java/pom/pom.go +++ b/pkg/dependency/parser/java/pom/pom.go @@ -303,7 +303,6 @@ func (d pomDependency) ToArtifact(opts analysisOptions) artifact { Exclusions: exclusions, Locations: locations, Relationship: ftypes.RelationshipIndirect, // default - Test: d.Scope == "test", } } diff --git a/pkg/dependency/parser/java/pom/testdata/happy/pom.xml b/pkg/dependency/parser/java/pom/testdata/happy/pom.xml index 9dfc1c75bd65..1f3c9697a17d 100644 --- a/pkg/dependency/parser/java/pom/testdata/happy/pom.xml +++ b/pkg/dependency/parser/java/pom/testdata/happy/pom.xml @@ -46,11 +46,5 @@ 999 provided - - org.example - example-test - 2.0.0 - test - From 04a854c3373952cc555bb4c2877d7cb0c4eb8335 Mon Sep 17 00:00:00 2001 From: Itay Shakury Date: Thu, 12 Sep 2024 10:10:23 +0300 Subject: [PATCH 345/352] docs: refine go docs (#7442) Signed-off-by: knqyf263 Co-authored-by: knqyf263 --- docs/docs/coverage/language/golang.md | 109 +++++++++++++++----------- 1 file changed, 62 insertions(+), 47 deletions(-) diff --git a/docs/docs/coverage/language/golang.md b/docs/docs/coverage/language/golang.md index f2cbff03255a..cd1a30c53e9c 100644 --- a/docs/docs/coverage/language/golang.md +++ b/docs/docs/coverage/language/golang.md @@ -1,32 +1,31 @@ # Go -## Data Sources -The data sources are listed [here](../../scanner/vulnerability.md#data-sources-1). -Trivy uses Go Vulnerability Database for standard packages, such as `net/http`, and uses GitHub Advisory Database for third-party packages. - -## Features +## Overview Trivy supports two types of Go scanning, Go Modules and binaries built by Go. The following scanners are supported. -| Artifact | SBOM | Vulnerability | License | -| -------- | :---: | :-----------: | :-----: | -| Modules | ✓ | ✓ | ✓[^2] | -| Binaries | ✓ | ✓ | - | +| Artifact | SBOM | Vulnerability | License | +|----------|:----:|:-------------:|:-------------:| +| Modules | ✓ | ✓ | [✓](#license) | +| Binaries | ✓ | ✓ | - | The table below provides an outline of the features Trivy offers. -| Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | Stdlib | [Detection Priority][detection-priority] | -|----------|:-----------:|:-----------------|:------------------------------------:|:------:|:----------------------------------------:| -| Modules | ✅ | Include | ✅[^2] | ✅[^6] | [✅](#stdlib) | -| Binaries | ✅ | Exclude | - | ✅[^4] | Not needed | +| Artifact | Offline[^1] | Dev dependencies | [Dependency graph][dependency-graph] | Stdlib | [Detection Priority][detection-priority] | +|----------|:-----------:|:-----------------|:------------------------------------:|:------------------------:|:----------------------------------------:| +| Modules | ✅ | Include | [✅](#dependency-graph) | [✅](#standard-library) | [✅](#standard-library) | +| Binaries | ✅ | Exclude | - | [✅](#standard-library-1) | Not needed | !!! note - Trivy scans only dependencies of the Go project. - Let's say you scan the Docker binary, Trivy doesn't detect vulnerabilities of Docker itself. - Also, when you scan go.mod in Kubernetes, the Kubernetes vulnerabilities will not be found. + When scanning Go projects (go.mod or binaries built with Go), Trivy scans only dependencies of the project, and does not detect vulnerabilities of application itself. + For example, when scanning the Docker project (Docker's source code with go.mod or the Docker binary), Trivy might find vulnerabilities in Go modules that Docker depends on, but won't find vulnerabilities of Docker itself. Moreover, when scanning the Trivy project, which happens to use Docker, Docker's vulnerabilities might be detected as dependencies of Trivy. -### Go Modules +## Data Sources +The data sources are listed [here](../../scanner/vulnerability.md#data-sources-1). +Trivy uses Go Vulnerability Database for [standard library](https://pkg.go.dev/std) and uses GitHub Advisory Database for other Go modules. + +## Go Module Depending on Go versions, the required files are different. | Version | Required files | Offline | @@ -42,7 +41,7 @@ Go 1.17+ holds actually needed indirect dependencies in `go.mod`, and it reduces If you want to have better detection, please consider updating the Go version in your project. !!! note - The Go version doesn't mean your CLI version, but the Go version in your go.mod. + The Go version doesn't mean your Go tool version, but the Go version in your go.mod. ``` module github.com/aquasecurity/trivy @@ -61,32 +60,37 @@ If you want to have better detection, please consider updating the Go version in $ go mod tidy -go=1.18 ``` -To identify licenses and dependency relationships, you need to download modules to local cache beforehand, -such as `go mod download`, `go mod tidy`, etc. -Trivy traverses `$GOPATH/pkg/mod` and collects those extra information. - -#### stdlib -If [--detection-priority comprehensive][detection-priority] is passed, Trivy determines the minimum version of `Go` and saves it as a `stdlib` dependency. - -By default, `Go` selects the higher version from of `toolchan` or local version of `Go`. -See [toolchain] for more details. +### Main Module +Trivy scans only dependencies of the project, and does not detect vulnerabilities of the main module. +For example, when scanning the Docker project (Docker's source code with go.mod), Trivy might find vulnerabilities in Go modules that Docker depends on, but won't find vulnerabilities of Docker itself. +Moreover, when scanning the Trivy project, which happens to use Docker, Docker's vulnerabilities might be detected as dependencies of Trivy. -To obtain reproducible scan results Trivy doesn't check the local version of `Go`. -Trivy shows the minimum required version for the `go.mod` file, obtained from `toolchain` line (or from the `go` line, if `toolchain` line is omitted). +### Standard Library +Detecting the version of Go used in the project can be tricky. +The go.mod file include hints that allows Trivy to guess the Go version but it eventually depends on the Go tool version in the build environment. +Since this strategy is not fully deterministic and accurate, it is enabled only in [--detection-priority comprehensive][detection-priority] mode. +When enabled, Trivy detects stdlib version as the minimum between the `go` and the `toolchain` directives in the `go.mod` file. +To obtain reproducible scan results Trivy doesn't check the locally installed version of `Go`. !!! note Trivy detects `stdlib` only for `Go` 1.21 or higher. The version from the `go` line (for `Go` 1.20 or early) is not a minimum required version. For details, see [this](https://go.googlesource.com/proposal/+/master/design/57001-gotoolchain.md). - - -### Go binaries -Trivy scans binaries built by Go, which include [module information](https://tip.golang.org/doc/go1.18#go-version). -If there is a Go binary in your container image, Trivy automatically finds and scans it. +It possibly produces false positives. +See [the caveat](#stdlib-vulnerabilities) for details. + +### License +To identify licenses, you need to download modules to local cache beforehand, such as `go mod download`, `go mod tidy`, etc. +Trivy traverses `$GOPATH/pkg/mod` and collects those extra information. + +### Dependency Graph +Same as licenses, you need to download modules to local cache beforehand. -Also, you can scan your local binaries. +## Go Binary +Trivy scans Go binaries when it encounters them during scans such as container images or file systems. +When scanning binaries built by Go, Trivy finds dependencies and Go version information as [embedded in the binary by Go tool at build time](https://tip.golang.org/doc/go1.18#go-version). ``` $ trivy rootfs ./your_binary @@ -95,22 +99,33 @@ $ trivy rootfs ./your_binary !!! note It doesn't work with UPX-compressed binaries. -#### Empty versions -There are times when Go uses the `(devel)` version for modules/dependencies. +### Main Module +Go binaries installed using the `go install` command contains correct (semver) version for the main module and therefor are detected by Trivy. +In other cases, Go uses the `(devel)` version[^2]. +In this case, Trivy will attempt to parse any `-ldflags` as it's a common practice to pass versions this way. +If unsuccessful, the version will be empty[^3]. + +### Standard Library +Trivy detects the Go version used to compile the binary and detects its vulnerabilities in the standard libraries. +It possibly produces false positives. +See [the caveat](#stdlib-vulnerabilities) for details. + +## Caveats + +### Stdlib Vulnerabilities +Trivy does not know if or how you use stdlib functions, therefore it is possible that stdlib vulnerabilities are not applicable to your use case. +There are a few ways to mitigate this: -- Only Go binaries installed using the `go install` command contain correct (semver) version for the main module. - In other cases, Go uses the `(devel)` version[^3]. -- Dependencies replaced with local ones use the `(devel)` versions. +1. Analyze vulnerability reachability using a tool such as [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck). This will ensure that reported vulnerabilities are applicable to your project. +2. Suppress non-applicable vulnerabilities using either [ignore file](../../configuration/filtering.md) for self-use or [VEX Hub](../../supply-chain/vex/repo.md) for public use. -In the first case, Trivy will attempt to parse any `-ldflags` as a secondary source, and will leave the version -empty if it cannot do so[^5]. For the second case, the version of such packages is empty. +### Empty Version +As described in the [Main Module](#main-module-1) section, the main module of Go binaries might have an empty version. +Also, dependencies replaced with local ones will have an empty version. [^1]: It doesn't require the Internet access. -[^2]: Need to download modules to local cache beforehand -[^3]: See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477 -[^4]: Identify the Go version used to compile the binary and detect its vulnerabilities -[^5]: See https://github.com/golang/go/issues/63432#issuecomment-1751610604 -[^6]: Only available if `toolchain` directive exists +[^2]: See https://github.com/aquasecurity/trivy/issues/1837#issuecomment-1832523477 +[^3]: See https://github.com/golang/go/issues/63432#issuecomment-1751610604 [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies [toolchain]: https://go.dev/doc/toolchain From 42748c40372863a7f233be75f7cd21d9947a52aa Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Mon, 16 Sep 2024 09:50:52 +0400 Subject: [PATCH 346/352] chore(vex): suppress openssl vulnerabilities (#7500) Signed-off-by: knqyf263 --- .vex/oci.openvex.json | 99 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/.vex/oci.openvex.json b/.vex/oci.openvex.json index b689d43afac1..f1ec8a32df48 100644 --- a/.vex/oci.openvex.json +++ b/.vex/oci.openvex.json @@ -140,6 +140,105 @@ "status": "not_affected", "justification": "vulnerable_code_cannot_be_controlled_by_adversary", "impact_statement": "awk is not used" + }, + { + "vulnerability": { + "name": "CVE-2024-4741" + }, + "products": [ + { + "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"}, + {"@id": "pkg:apk/alpine/ssl_client"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"}, + {"@id": "pkg:apk/alpine/ssl_client"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"}, + {"@id": "pkg:apk/alpine/ssl_client"} + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_cannot_be_controlled_by_adversary", + "impact_statement": "openssl is not used" + }, + { + "vulnerability": { + "name": "CVE-2024-5535" + }, + "products": [ + { + "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"}, + {"@id": "pkg:apk/alpine/ssl_client"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"}, + {"@id": "pkg:apk/alpine/ssl_client"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"}, + {"@id": "pkg:apk/alpine/ssl_client"} + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_cannot_be_controlled_by_adversary", + "impact_statement": "openssl is not used" + }, + { + "vulnerability": { + "name": "CVE-2024-6119" + }, + "products": [ + { + "@id": "pkg:oci/trivy?repository_url=index.docker.io%2Faquasec%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=public.ecr.aws%2Faquasecurity%2Ftrivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"} + ] + }, + { + "@id": "pkg:oci/trivy?repository_url=ghcr.io/aquasecurity/trivy", + "subcomponents": [ + {"@id": "pkg:apk/alpine/libcrypto3"}, + {"@id": "pkg:apk/alpine/libssl3"} + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_cannot_be_controlled_by_adversary", + "impact_statement": "openssl is not used" } ] } From 701dbdaa5d20dbd1911a4c62f7b2f41d218028fe Mon Sep 17 00:00:00 2001 From: Lior Kaplan Date: Mon, 16 Sep 2024 09:29:55 +0300 Subject: [PATCH 347/352] chore(deps): bump alpine from 3.20.0 to 3.20.3 (#7508) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9e4dfe3b1dbe..1113c80aa9d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20.0 +FROM alpine:3.20.3 RUN apk --no-cache add ca-certificates git COPY trivy /usr/local/bin/trivy COPY contrib/*.tpl contrib/ From 0efd2027244aacde4380c47f579bfb5b5cb6997f Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 16 Sep 2024 13:44:56 +0600 Subject: [PATCH 348/352] chore(vex): add `CVE-2024-34155`, `CVE-2024-34156` and `CVE-2024-34158` in `trivy.openvex.json` (#7510) --- .vex/trivy.openvex.json | 87 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/.vex/trivy.openvex.json b/.vex/trivy.openvex.json index 21af61db7d76..2dd1629ecc89 100644 --- a/.vex/trivy.openvex.json +++ b/.vex/trivy.openvex.json @@ -453,6 +453,93 @@ "status": "not_affected", "justification": "vulnerable_code_not_in_execute_path", "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-3105", + "name": "GO-2024-3105", + "description": "Stack exhaustion in all Parse functions in go/parser", + "aliases": [ + "CVE-2024-34155" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/stdlib", + "identifiers": { + "purl": "pkg:golang/stdlib" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-3106", + "name": "GO-2024-3106", + "description": "Stack exhaustion in Decoder.Decode in encoding/gob", + "aliases": [ + "CVE-2024-34156" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/stdlib", + "identifiers": { + "purl": "pkg:golang/stdlib" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "Govulncheck incorrectly marks this vulnerability as affected. The vulnerable code isn't called. See https://github.com/aquasecurity/trivy/issues/7478" + }, + { + "vulnerability": { + "@id": "https://pkg.go.dev/vuln/GO-2024-3107", + "name": "GO-2024-3107", + "description": "Stack exhaustion in Parse in go/build/constraint", + "aliases": [ + "CVE-2024-34158" + ] + }, + "products": [ + { + "@id": "pkg:golang/github.com/aquasecurity/trivy", + "identifiers": { + "purl": "pkg:golang/github.com/aquasecurity/trivy" + }, + "subcomponents": [ + { + "@id": "pkg:golang/stdlib", + "identifiers": { + "purl": "pkg:golang/stdlib" + } + } + ] + } + ], + "status": "not_affected", + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "Govulncheck determined that the vulnerable code isn't called" } ] } From 54429497e7d6a87eac236771d4efb8a5a7faaac5 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:14:28 +0600 Subject: [PATCH 349/352] fix(java): use `dependencyManagement` from root/child pom's for dependencies from parents (#7497) --- pkg/dependency/parser/java/pom/parse.go | 20 +++- pkg/dependency/parser/java/pom/parse_test.go | 96 +++++++++++++++++++ .../2.0.0/example-dependency-2.0.0.pom | 26 +++++ .../3.0.0/example-parent-3.0.0.pom | 32 +++++++ .../pom.xml | 21 ++++ .../use-root-dep-management-in-parent/pom.xml | 31 ++++++ 6 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.0.0/example-dependency-2.0.0.pom create mode 100644 pkg/dependency/parser/java/pom/testdata/repository/org/example/example-parent/3.0.0/example-parent-3.0.0.pom create mode 100644 pkg/dependency/parser/java/pom/testdata/use-dep-management-from-child-in-parent/pom.xml create mode 100644 pkg/dependency/parser/java/pom/testdata/use-root-dep-management-in-parent/pom.xml diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index 1add19a4b53b..76ca37f2dd05 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -335,8 +335,20 @@ func (p *Parser) analyze(pom *pom, opts analysisOptions) (analysisResult, error) p.releaseRemoteRepos = lo.Uniq(append(pomReleaseRemoteRepos, p.releaseRemoteRepos...)) p.snapshotRemoteRepos = lo.Uniq(append(pomSnapshotRemoteRepos, p.snapshotRemoteRepos...)) + // We need to forward dependencyManagements from current and root pom to Parent, + // to use them for dependencies in parent. + // For better understanding see the following tests: + // - `dependency from parent uses version from child pom depManagement` + // - `dependency from parent uses version from root pom depManagement` + // + // depManagements from root pom has higher priority than depManagements from current pom. + depManagementForParent := lo.UniqBy(append(opts.depManagement, pom.content.DependencyManagement.Dependencies.Dependency...), + func(dep pomDependency) string { + return dep.Name() + }) + // Parent - parent, err := p.parseParent(pom.filePath, pom.content.Parent) + parent, err := p.parseParent(pom.filePath, pom.content.Parent, depManagementForParent) if err != nil { return analysisResult{}, xerrors.Errorf("parent error: %w", err) } @@ -477,7 +489,7 @@ func excludeDep(exclusions map[string]struct{}, art artifact) bool { return false } -func (p *Parser) parseParent(currentPath string, parent pomParent) (analysisResult, error) { +func (p *Parser) parseParent(currentPath string, parent pomParent, rootDepManagement []pomDependency) (analysisResult, error) { // Pass nil properties so that variables in are not evaluated. target := newArtifact(parent.GroupId, parent.ArtifactId, parent.Version, nil, nil) // if version is property (e.g. ${revision}) - we still need to parse this pom @@ -499,7 +511,9 @@ func (p *Parser) parseParent(currentPath string, parent pomParent) (analysisResu logger.Debug("Parent POM not found", log.Err(err)) } - result, err := p.analyze(parentPOM, analysisOptions{}) + result, err := p.analyze(parentPOM, analysisOptions{ + depManagement: rootDepManagement, + }) if err != nil { return analysisResult{}, xerrors.Errorf("analyze error: %w", err) } diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go index 934085d5d536..9fa97ffd3672 100644 --- a/pkg/dependency/parser/java/pom/parse_test.go +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -1499,6 +1499,102 @@ func TestPom_Parse(t *testing.T) { }, }, }, + // [INFO] com.example:root-depManagement-in-parent:jar:1.0.0 + // [INFO] \- org.example:example-dependency:jar:2.0.0:compile + // [INFO] \- org.example:example-api:jar:1.0.1:compile + { + name: "dependency from parent uses version from root pom depManagement", + inputFile: filepath.Join("testdata", "use-root-dep-management-in-parent", "pom.xml"), + local: true, + want: []ftypes.Package{ + { + ID: "com.example:root-depManagement-in-parent:1.0.0", + Name: "com.example:root-depManagement-in-parent", + Version: "1.0.0", + Relationship: ftypes.RelationshipRoot, + }, + { + ID: "org.example:example-dependency:2.0.0", + Name: "org.example:example-dependency", + Version: "2.0.0", + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ + { + StartLine: 25, + EndLine: 29, + }, + }, + }, + { + ID: "org.example:example-api:1.0.1", + Name: "org.example:example-api", + Version: "1.0.1", + Relationship: ftypes.RelationshipIndirect, + }, + }, + wantDeps: []ftypes.Dependency{ + { + ID: "com.example:root-depManagement-in-parent:1.0.0", + DependsOn: []string{ + "org.example:example-dependency:2.0.0", + }, + }, + { + ID: "org.example:example-dependency:2.0.0", + DependsOn: []string{ + "org.example:example-api:1.0.1", + }, + }, + }, + }, + // [INFO] com.example:root-depManagement-in-parent:jar:1.0.0 + // [INFO] \- org.example:example-dependency:jar:2.0.0:compile + // [INFO] \- org.example:example-api:jar:2.0.1:compile + { + name: "dependency from parent uses version from child pom depManagement", + inputFile: filepath.Join("testdata", "use-dep-management-from-child-in-parent", "pom.xml"), + local: true, + want: []ftypes.Package{ + { + ID: "com.example:root-depManagement-in-parent:1.0.0", + Name: "com.example:root-depManagement-in-parent", + Version: "1.0.0", + Relationship: ftypes.RelationshipRoot, + }, + { + ID: "org.example:example-dependency:2.0.0", + Name: "org.example:example-dependency", + Version: "2.0.0", + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ + { + StartLine: 15, + EndLine: 19, + }, + }, + }, + { + ID: "org.example:example-api:2.0.1", + Name: "org.example:example-api", + Version: "2.0.1", + Relationship: ftypes.RelationshipIndirect, + }, + }, + wantDeps: []ftypes.Dependency{ + { + ID: "com.example:root-depManagement-in-parent:1.0.0", + DependsOn: []string{ + "org.example:example-dependency:2.0.0", + }, + }, + { + ID: "org.example:example-dependency:2.0.0", + DependsOn: []string{ + "org.example:example-api:2.0.1", + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.0.0/example-dependency-2.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.0.0/example-dependency-2.0.0.pom new file mode 100644 index 000000000000..3f236cdf314e --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-dependency/2.0.0/example-dependency-2.0.0.pom @@ -0,0 +1,26 @@ + + + 4.0.0 + + + org.example + example-parent + 3.0.0 + + + org.example + example-dependency + 2.0.0 + + + + + org.example + example-api + 2.0.1 + + + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-parent/3.0.0/example-parent-3.0.0.pom b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-parent/3.0.0/example-parent-3.0.0.pom new file mode 100644 index 000000000000..917908d53d7b --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/repository/org/example/example-parent/3.0.0/example-parent-3.0.0.pom @@ -0,0 +1,32 @@ + + + + 4.0.0 + + org.example + example-parent + 3.0.0 + pom + + + 3.0.1 + + + + + + org.example + example-api + ${api.version} + + + + + + + org.example + example-api + + + \ No newline at end of file diff --git a/pkg/dependency/parser/java/pom/testdata/use-dep-management-from-child-in-parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/use-dep-management-from-child-in-parent/pom.xml new file mode 100644 index 000000000000..5d062242161d --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/use-dep-management-from-child-in-parent/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + com.example + root-depManagement-in-parent + 1.0.0 + + + + 1.0.1 + + + + + org.example + example-dependency + 2.0.0 + + + diff --git a/pkg/dependency/parser/java/pom/testdata/use-root-dep-management-in-parent/pom.xml b/pkg/dependency/parser/java/pom/testdata/use-root-dep-management-in-parent/pom.xml new file mode 100644 index 000000000000..685a7fc7caf3 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/use-root-dep-management-in-parent/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + com.example + root-depManagement-in-parent + 1.0.0 + + + + 1.0.1 + + + + + + org.example + example-api + ${api.version} + + + + + + + org.example + example-dependency + 2.0.0 + + + From e6f45cd48f2c436336f3bc70c6b1b565ddff7707 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:23:41 +0600 Subject: [PATCH 350/352] refactor: split `.egg` and `packaging` analyzers (#7514) --- pkg/fanal/analyzer/const.go | 9 +- .../analyzer/language/python/packaging/egg.go | 126 +++++++++++++++ .../language/python/packaging/egg_test.go | 146 ++++++++++++++++++ .../language/python/packaging/packaging.go | 111 ++++--------- .../python/packaging/packaging_test.go | 27 ---- .../sample_package.egg | Bin 0 -> 1135 bytes 6 files changed, 311 insertions(+), 108 deletions(-) create mode 100644 pkg/fanal/analyzer/language/python/packaging/egg.go create mode 100644 pkg/fanal/analyzer/language/python/packaging/egg_test.go create mode 100644 pkg/fanal/analyzer/language/python/packaging/testdata/egg-zip-with-license-file/sample_package.egg diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index ea2108e89281..197c0033296e 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -75,10 +75,11 @@ const ( TypeCondaEnv Type = "conda-environment" // Python - TypePythonPkg Type = "python-pkg" - TypePip Type = "pip" - TypePipenv Type = "pipenv" - TypePoetry Type = "poetry" + TypePythonPkg Type = "python-pkg" + TypePythonPkgEgg Type = "python-egg" + TypePip Type = "pip" + TypePipenv Type = "pipenv" + TypePoetry Type = "poetry" // Go TypeGoBinary Type = "gobinary" diff --git a/pkg/fanal/analyzer/language/python/packaging/egg.go b/pkg/fanal/analyzer/language/python/packaging/egg.go new file mode 100644 index 000000000000..1de53980260e --- /dev/null +++ b/pkg/fanal/analyzer/language/python/packaging/egg.go @@ -0,0 +1,126 @@ +package packaging + +import ( + "archive/zip" + "context" + "io" + "os" + "path" + "path/filepath" + + "github.com/samber/lo" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/python/packaging" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" +) + +func init() { + analyzer.RegisterAnalyzer(&eggAnalyzer{}) +} + +const ( + eggAnalyzerVersion = 1 + eggExt = ".egg" +) + +type eggAnalyzer struct { + logger *log.Logger + licenseClassifierConfidenceLevel float64 +} + +func (a *eggAnalyzer) Init(opt analyzer.AnalyzerOptions) error { + a.logger = log.WithPrefix("python") + a.licenseClassifierConfidenceLevel = opt.LicenseScannerOption.ClassifierConfidenceLevel + return nil +} + +// Analyze analyzes egg archive files +func (a *eggAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + // .egg file is zip format and PKG-INFO needs to be extracted from the zip file. + pkginfoInZip, err := findFileInZip(input.Content, input.Info.Size(), isEggFile) + if err != nil { + return nil, xerrors.Errorf("unable to open `.egg` archive: %w", err) + } + + // Egg archive may not contain required files, then we will get nil. Skip this archives + if pkginfoInZip == nil { + return nil, nil + } + + rsa, err := xio.NewReadSeekerAt(pkginfoInZip) + if err != nil { + return nil, xerrors.Errorf("unable to convert PKG-INFO reader: %w", err) + } + + app, err := language.ParsePackage(types.PythonPkg, input.FilePath, rsa, packaging.NewParser(), input.Options.FileChecksum) + if err != nil { + return nil, xerrors.Errorf("parse error: %w", err) + } else if app == nil { + return nil, nil + } + + opener := func(licPath string) (io.ReadCloser, error) { + required := func(filePath string) bool { + return path.Base(filePath) == licPath + } + + f, err := findFileInZip(input.Content, input.Info.Size(), required) + if err != nil { + return nil, xerrors.Errorf("unable to find license file in `*.egg` file: %w", err) + } else if f == nil { // zip doesn't contain license file + return nil, nil + } + + return f, nil + } + + if err = fillAdditionalData(opener, app, a.licenseClassifierConfidenceLevel); err != nil { + a.logger.Warn("Unable to collect additional info", log.Err(err)) + } + + return &analyzer.AnalysisResult{ + Applications: []types.Application{*app}, + }, nil +} + +func findFileInZip(r xio.ReadSeekerAt, zipSize int64, required func(filePath string) bool) (io.ReadCloser, error) { + if _, err := r.Seek(0, io.SeekStart); err != nil { + return nil, xerrors.Errorf("file seek error: %w", err) + } + + zr, err := zip.NewReader(r, zipSize) + if err != nil { + return nil, xerrors.Errorf("zip reader error: %w", err) + } + + found, ok := lo.Find(zr.File, func(f *zip.File) bool { + return required(f.Name) + }) + if !ok { + return nil, nil + } + + f, err := found.Open() + if err != nil { + return nil, xerrors.Errorf("unable to open file in zip: %w", err) + } + + return f, nil +} + +func (a *eggAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return filepath.Ext(filePath) == eggExt +} + +func (a *eggAnalyzer) Type() analyzer.Type { + return analyzer.TypePythonPkgEgg +} + +func (a *eggAnalyzer) Version() int { + return eggAnalyzerVersion +} diff --git a/pkg/fanal/analyzer/language/python/packaging/egg_test.go b/pkg/fanal/analyzer/language/python/packaging/egg_test.go new file mode 100644 index 000000000000..615dffb4b0cc --- /dev/null +++ b/pkg/fanal/analyzer/language/python/packaging/egg_test.go @@ -0,0 +1,146 @@ +package packaging + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +func Test_eggAnalyzer_Analyze(t *testing.T) { + tests := []struct { + name string + inputFile string + includeChecksum bool + want *analyzer.AnalysisResult + wantErr string + }{ + { + name: "egg zip", + inputFile: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", + Packages: types.Packages{ + { + Name: "kitchen", + Version: "1.2.6", + Licenses: []string{ + "LGPL-2.1-only", + }, + FilePath: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", + }, + }, + }, + }, + }, + }, + { + name: "egg zip with checksum", + inputFile: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", + includeChecksum: true, + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", + Packages: types.Packages{ + { + Name: "kitchen", + Version: "1.2.6", + Licenses: []string{ + "LGPL-2.1-only", + }, + FilePath: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", + Digest: "sha1:4e13b6e379966771e896ee43cf8e240bf6083dca", + }, + }, + }, + }, + }, + }, + { + name: "egg zip with license file", + inputFile: "testdata/egg-zip-with-license-file/sample_package.egg", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.PythonPkg, + FilePath: "testdata/egg-zip-with-license-file/sample_package.egg", + Packages: types.Packages{ + { + Name: "sample_package", + Version: "0.1", + Licenses: []string{ + "MIT", + }, + FilePath: "testdata/egg-zip-with-license-file/sample_package.egg", + }, + }, + }, + }, + }, + }, + { + name: "egg zip doesn't contain required files", + inputFile: "testdata/no-req-files/no-required-files.egg", + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + defer f.Close() + fileInfo, err := os.Lstat(tt.inputFile) + require.NoError(t, err) + + a := &eggAnalyzer{} + got, err := a.Analyze(context.Background(), analyzer.AnalysisInput{ + Content: f, + FilePath: tt.inputFile, + Info: fileInfo, + Options: analyzer.AnalysisOptions{ + FileChecksum: tt.includeChecksum, + }, + }) + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } + +} + +func Test_eggAnalyzer_Required(t *testing.T) { + tests := []struct { + name string + filePath string + want bool + }{ + { + name: "egg zip", + filePath: "python2.7/site-packages/cssutils-1.0-py2.7.egg", + want: true, + }, + { + name: "egg-info PKG-INFO", + filePath: "python3.8/site-packages/wrapt-1.12.1.egg-info/PKG-INFO", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := eggAnalyzer{} + got := a.Required(tt.filePath, nil) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging.go b/pkg/fanal/analyzer/language/python/packaging/packaging.go index 73e5f446bc40..944a5abde331 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging.go @@ -1,8 +1,6 @@ package packaging import ( - "archive/zip" - "bytes" "context" "errors" "io" @@ -29,7 +27,7 @@ func init() { analyzer.RegisterPostAnalyzer(analyzer.TypePythonPkg, newPackagingAnalyzer) } -const version = 1 +const version = 2 func newPackagingAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { return &packagingAnalyzer{ @@ -43,7 +41,7 @@ var ( eggFiles = []string{ // .egg format // https://setuptools.readthedocs.io/en/latest/deprecated/python_eggs.html#eggs-and-their-formats - ".egg", // zip format + // ".egg" is zip format. We check it in `eggAnalyzer`. "EGG-INFO/PKG-INFO", // .egg-info format: .egg-info can be a file or directory @@ -68,38 +66,32 @@ func (a packagingAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAna return filepath.Base(path) == "METADATA" || isEggFile(path) } - err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error { + err := fsutils.WalkDir(input.FS, ".", required, func(filePath string, d fs.DirEntry, r io.Reader) error { rsa, ok := r.(xio.ReadSeekerAt) if !ok { return xerrors.New("invalid reader") } - // .egg file is zip format and PKG-INFO needs to be extracted from the zip file. - if strings.HasSuffix(path, ".egg") { - info, err := d.Info() - if err != nil { - return xerrors.Errorf("egg file error: %w", err) - } - pkginfoInZip, err := a.analyzeEggZip(rsa, info.Size()) - if err != nil { - return xerrors.Errorf("egg analysis error: %w", err) - } - - // Egg archive may not contain required files, then we will get nil. Skip this archives - if pkginfoInZip == nil { - return nil - } - rsa = pkginfoInZip - } - - app, err := a.parse(path, rsa, input.Options.FileChecksum) + app, err := a.parse(filePath, rsa, input.Options.FileChecksum) if err != nil { return xerrors.Errorf("parse error: %w", err) } else if app == nil { return nil } - if err := a.fillAdditionalData(input.FS, app); err != nil { + opener := func(licPath string) (io.ReadCloser, error) { + // Note that fs.FS is always slashed regardless of the platform, + // and path.Join should be used rather than filepath.Join. + f, err := input.FS.Open(path.Join(path.Dir(filePath), licPath)) + if errors.Is(err, fs.ErrNotExist) { + return nil, nil + } else if err != nil { + return nil, xerrors.Errorf("file open error: %w", err) + } + return f, nil + } + + if err = fillAdditionalData(opener, app, a.licenseClassifierConfidenceLevel); err != nil { a.logger.Warn("Unable to collect additional info", log.Err(err)) } @@ -115,7 +107,9 @@ func (a packagingAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAna }, nil } -func (a packagingAnalyzer) fillAdditionalData(fsys fs.FS, app *types.Application) error { +type fileOpener func(filePath string) (io.ReadCloser, error) + +func fillAdditionalData(opener fileOpener, app *types.Application, licenseClassifierConfidenceLevel float64) error { for i, pkg := range app.Packages { var licenses []string for _, lic := range pkg.Licenses { @@ -126,19 +120,12 @@ func (a packagingAnalyzer) fillAdditionalData(fsys fs.FS, app *types.Application licenses = append(licenses, lic) continue } - licenseFilePath := path.Base(strings.TrimPrefix(lic, licensing.LicenseFilePrefix)) + licensePath := path.Base(strings.TrimPrefix(lic, licensing.LicenseFilePrefix)) - findings, err := classifyLicense(app.FilePath, licenseFilePath, a.licenseClassifierConfidenceLevel, fsys) + foundLicenses, err := classifyLicenses(opener, licensePath, licenseClassifierConfidenceLevel) if err != nil { - return err - } else if len(findings) == 0 { - continue + return xerrors.Errorf("unable to classify licenses: %w", err) } - - // License found - foundLicenses := lo.Map(findings, func(finding types.LicenseFinding, _ int) string { - return finding.Name - }) licenses = append(licenses, foundLicenses...) } app.Packages[i].Licenses = licenses @@ -147,62 +134,32 @@ func (a packagingAnalyzer) fillAdditionalData(fsys fs.FS, app *types.Application return nil } -func classifyLicense(dir, licPath string, classifierConfidenceLevel float64, fsys fs.FS) (types.LicenseFindings, error) { - // Note that fs.FS is always slashed regardless of the platform, - // and path.Join should be used rather than filepath.Join. - f, err := fsys.Open(path.Join(path.Dir(dir), licPath)) - if errors.Is(err, fs.ErrNotExist) { +func classifyLicenses(opener fileOpener, licPath string, licenseClassifierConfidenceLevel float64) ([]string, error) { + f, err := opener(licPath) + if err != nil { + return nil, xerrors.Errorf("unable to open license file: %w", err) + } else if f == nil { // File doesn't exist return nil, nil - } else if err != nil { - return nil, xerrors.Errorf("file open error: %w", err) } defer f.Close() - l, err := licensing.Classify(licPath, f, classifierConfidenceLevel) + l, err := licensing.Classify("", f, licenseClassifierConfidenceLevel) if err != nil { return nil, xerrors.Errorf("license classify error: %w", err) - } else if l == nil { + } else if l == nil { // No licenses found return nil, nil } - return l.Findings, nil + // License found + return lo.Map(l.Findings, func(finding types.LicenseFinding, _ int) string { + return finding.Name + }), nil } func (a packagingAnalyzer) parse(filePath string, r xio.ReadSeekerAt, checksum bool) (*types.Application, error) { return language.ParsePackage(types.PythonPkg, filePath, r, a.pkgParser, checksum) } -func (a packagingAnalyzer) analyzeEggZip(r io.ReaderAt, size int64) (xio.ReadSeekerAt, error) { - zr, err := zip.NewReader(r, size) - if err != nil { - return nil, xerrors.Errorf("zip reader error: %w", err) - } - - found, ok := lo.Find(zr.File, func(f *zip.File) bool { - return isEggFile(f.Name) - }) - if !ok { - return nil, nil - } - return a.open(found) -} - -// open reads the file content in the zip archive to make it seekable. -func (a packagingAnalyzer) open(file *zip.File) (xio.ReadSeekerAt, error) { - f, err := file.Open() - if err != nil { - return nil, err - } - defer f.Close() - - b, err := io.ReadAll(f) - if err != nil { - return nil, xerrors.Errorf("file %s open error: %w", file.Name, err) - } - - return bytes.NewReader(b), nil -} - func (a packagingAnalyzer) Required(filePath string, _ os.FileInfo) bool { return strings.Contains(filePath, ".dist-info") || isEggFile(filePath) } diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go index 960a92dfec2b..47a9cca901b6 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go @@ -20,28 +20,6 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { want *analyzer.AnalysisResult wantErr string }{ - { - name: "egg zip", - dir: "testdata/egg-zip", - want: &analyzer.AnalysisResult{ - Applications: []types.Application{ - { - Type: types.PythonPkg, - FilePath: "kitchen-1.2.6-py2.7.egg", - Packages: types.Packages{ - { - Name: "kitchen", - Version: "1.2.6", - Licenses: []string{ - "LGPL-2.1-only", - }, - FilePath: "kitchen-1.2.6-py2.7.egg", - }, - }, - }, - }, - }, - }, { name: "egg-info", dir: "testdata/happy-egg", @@ -124,11 +102,6 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { }, }, }, - { - name: "egg zip doesn't contain required files", - dir: "testdata/no-req-files", - want: &analyzer.AnalysisResult{}, - }, { name: "license file in dist.info", dir: "testdata/license-file-dist", diff --git a/pkg/fanal/analyzer/language/python/packaging/testdata/egg-zip-with-license-file/sample_package.egg b/pkg/fanal/analyzer/language/python/packaging/testdata/egg-zip-with-license-file/sample_package.egg new file mode 100644 index 0000000000000000000000000000000000000000..91d67dc5947b71b0d541924d68430e56d2aaa360 GIT binary patch literal 1135 zcmWIWW@Zs#VBlb2C`?t3WIzI(K(?#9yRN67o4YrYdt0HtLZ85jgn zl|j_|_MOdRGURD_U&*!KVN=0bPM7JrS}ju-yxq8|X}7fLO_$Dz58g^IeB1w|+4gg# z3478Wg~c_CV`Ao*=v;M@;90UT!DF7C&@Fu#k3T}(TSb;XlTK#0Giy&o&U)_dL9( zC`)zA-tW&?(l>HRNTy$w;hgcH>jB4Xx8LurA1}Ro*K6X1J^Q8ybMWrWJ^SQrTJX%D zX?wTlF}+SS*t~Pkq0+o%3A-QsK<@5^-1(RlBnyJzJ1@2HxTZMrNfm2vm) zRI|ek2KT-`ym*Ffx^>2$^0m1F{8rcX%#B;s1mg4EA3yY#;K_OYHR-qMq>^Ql2mKs2 z?^mw4VX#B>Ku*n!TyF_Zy{(TgO>Eo$_~N}cryQPd-phAT_{F1(j4yp2JhQre`MOK= z_oqft_9_=5oA&82&Ym~RaA`|TO|fx>abHV9VELC{>-63UoSw_Xo9#03yWx)8>FN@J z`_Bjm&8oO;*`s6h&nR~R-`ekaGIyKTY|VS6WBKX6xpeR1XOHS;zfir^e3Es_FSSOw z2mk)9dVPYQH*5b4Q-dc*D`xYTr7il-D1R^MTe-RWzW=#)iuK19PGhf4>S|uGFzA!( z5}Q`t)f?}r6$n{PcIVx(FkFuPkc4s7mzf+tkvqN0yf-oV0m*<4$47u` Date: Mon, 16 Sep 2024 21:57:10 -0600 Subject: [PATCH 351/352] feat(misconf): Register checks only when needed (#7435) --- pkg/iac/rego/embed.go | 6 +++--- pkg/iac/rego/embed_test.go | 1 + pkg/iac/rego/scanner.go | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/iac/rego/embed.go b/pkg/iac/rego/embed.go index fddc069d9283..4679102033c2 100644 --- a/pkg/iac/rego/embed.go +++ b/pkg/iac/rego/embed.go @@ -6,6 +6,7 @@ import ( "io/fs" "path/filepath" "strings" + "sync" "github.com/open-policy-agent/opa/ast" @@ -14,8 +15,7 @@ import ( "github.com/aquasecurity/trivy/pkg/log" ) -func init() { - +var LoadAndRegister = sync.OnceFunc(func() { modules, err := LoadEmbeddedPolicies() if err != nil { // we should panic as the policies were not embedded properly @@ -30,7 +30,7 @@ func init() { } RegisterRegoRules(modules) -} +}) func RegisterRegoRules(modules map[string]*ast.Module) { ctx := context.TODO() diff --git a/pkg/iac/rego/embed_test.go b/pkg/iac/rego/embed_test.go index 5b6368dec2eb..9ed0b00747ed 100644 --- a/pkg/iac/rego/embed_test.go +++ b/pkg/iac/rego/embed_test.go @@ -15,6 +15,7 @@ import ( ) func Test_EmbeddedLoading(t *testing.T) { + LoadAndRegister() frameworkRules := rules.GetRegistered() var found bool diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go index 23a1e04bc2b8..f6c7cabcb369 100644 --- a/pkg/iac/rego/scanner.go +++ b/pkg/iac/rego/scanner.go @@ -152,6 +152,8 @@ type DynamicMetadata struct { } func NewScanner(source types.Source, opts ...options.ScannerOption) *Scanner { + LoadAndRegister() + schema, ok := schemas.SchemaMap[source] if !ok { schema = schemas.Anything From 56db43c24f4f6be92891be85faaf9492cad516ac Mon Sep 17 00:00:00 2001 From: simar7 <1254783+simar7@users.noreply.github.com> Date: Mon, 16 Sep 2024 22:49:30 -0600 Subject: [PATCH 352/352] fix(misconf): Fix logging typo (#7473) --- pkg/iac/rego/scanner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/iac/rego/scanner.go b/pkg/iac/rego/scanner.go index f6c7cabcb369..f6108ffefd66 100644 --- a/pkg/iac/rego/scanner.go +++ b/pkg/iac/rego/scanner.go @@ -242,7 +242,7 @@ func GetInputsContents(inputs []Input) []any { func (s *Scanner) ScanInput(ctx context.Context, inputs ...Input) (scan.Results, error) { - s.logger.Debug("Scannning inputs", "count", len(inputs)) + s.logger.Debug("Scanning inputs", "count", len(inputs)) var results scan.Results