mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-21 14:50:53 -08:00
feat: Add AWS Cloud scanning (#2493)
* feat: Added AWS Cloud scanning Co-authored-by: Owen Rumney <owen.rumney@aquasec.com>
This commit is contained in:
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
@@ -7,8 +7,11 @@ helm/trivy/ @krol3
|
||||
# Misconfiguration scanning
|
||||
examples/misconf/ @owenrumney @liamg @knqyf263
|
||||
docs/docs/misconfiguration @owenrumney @liamg @knqyf263
|
||||
docs/docs/cloud @owenrumney @liamg @knqyf263
|
||||
pkg/fanal/analyzer/config @owenrumney @liamg @knqyf263
|
||||
pkg/fanal/handler/misconf @owenrumney @liamg @knqyf263
|
||||
pkg/cloud @owenrumney @liamg @knqyf263
|
||||
pkg/flag @owenrumney @liamg @knqyf263
|
||||
|
||||
# Kubernetes scanning
|
||||
pkg/k8s/ @josedonizetti @chen-keinan @knqyf263
|
||||
|
||||
55
docs/docs/cloud/aws/scanning.md
Normal file
55
docs/docs/cloud/aws/scanning.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Amazon Web Services
|
||||
|
||||
!!! warning "EXPERIMENTAL"
|
||||
This feature might change without preserving backwards compatibility.
|
||||
|
||||
The Trivy AWS CLI allows you to scan your AWS account for misconfigurations. You can either run the CLI locally or integrate it into your CI/CD pipeline.
|
||||
|
||||
Whilst you can already scan the infrastructure-as-code that defines your AWS resources with `trivy config`, you can now scan your live AWS account(s) directly too.
|
||||
|
||||
The included checks cover all of the aspects of the [AWS CIS 1.2](https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-cis.html) automated benchmarks.
|
||||
|
||||
Trivy uses the same [authentication methods](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) as the AWS CLI to configure and authenticate your access to the AWS platform.
|
||||
|
||||
You will need permissions configured to read all AWS resources - we recommend using a group/role with the `ReadOnlyAccess` and `SecurityAudit` policies attached.
|
||||
|
||||
Once you've scanned your account, you can run additional commands to filter the results without having to run the entire scan again - results are cached locally per AWS account/region.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
Scan a full AWS account (all supported services):
|
||||
|
||||
```shell
|
||||
trivy aws --region us-east-1
|
||||
```
|
||||
|
||||
You can allow Trivy to determine the AWS region etc. by using the standard AWS configuration files and environment variables. The `--region` flag overrides these.
|
||||
|
||||

|
||||
|
||||
The summary view is the default when scanning multiple services.
|
||||
|
||||
Scan a specific service:
|
||||
|
||||
```shell
|
||||
trivy aws --service s3
|
||||
```
|
||||
|
||||
Scan multiple services:
|
||||
|
||||
```shell
|
||||
# --service s3,ec2 works too
|
||||
trivy aws --service s3 --service ec2
|
||||
```
|
||||
|
||||
Show results for a specific AWS resource:
|
||||
|
||||
```shell
|
||||
trivy aws --service s3 --arn arn:aws:s3:::example-bucket
|
||||
```
|
||||
|
||||
All ARNs with detected issues will be displayed when showing results for their associated service.
|
||||
|
||||
## Cached Results
|
||||
|
||||
By default, Trivy will cache results for each service for 24 hours. This means you can filter and view results for a service without having to wait for the scan to run again. If you want to force the cache to be refreshed with the latest data, you can use `--update-cache`. 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`.)
|
||||
@@ -6,7 +6,7 @@ An example is [here][example].
|
||||
|
||||
## Global Options
|
||||
|
||||
```
|
||||
```yaml
|
||||
# Same as '--quiet'
|
||||
# Default is false
|
||||
quiet: false
|
||||
@@ -30,7 +30,7 @@ cache-dir: $HOME/.cache/trivy
|
||||
|
||||
## Report Options
|
||||
|
||||
```
|
||||
```yaml
|
||||
# Same as '--format'
|
||||
# Default is 'table'
|
||||
format: table
|
||||
@@ -80,7 +80,7 @@ severity:
|
||||
## Scan Options
|
||||
Available in client/server mode
|
||||
|
||||
```
|
||||
```yaml
|
||||
scan:
|
||||
# Same as '--skip-dirs'
|
||||
# Default is empty
|
||||
@@ -107,7 +107,7 @@ scan:
|
||||
|
||||
## Cache Options
|
||||
|
||||
```
|
||||
```yaml
|
||||
cache:
|
||||
# Same as '--cache-backend'
|
||||
# Default is 'fs'
|
||||
@@ -134,7 +134,7 @@ cache:
|
||||
|
||||
## DB Options
|
||||
|
||||
```
|
||||
```yaml
|
||||
db:
|
||||
# Same as '--skip-db-update'
|
||||
# Default is false
|
||||
@@ -152,7 +152,7 @@ db:
|
||||
## Image Options
|
||||
Available with container image scanning
|
||||
|
||||
```
|
||||
```yaml
|
||||
image:
|
||||
# Same as '--input' (available with 'trivy image')
|
||||
# Default is empty
|
||||
@@ -166,7 +166,7 @@ image:
|
||||
## Vulnerability Options
|
||||
Available with vulnerability scanning
|
||||
|
||||
```
|
||||
```yaml
|
||||
vulnerability:
|
||||
# Same as '--vuln-type'
|
||||
# Default is 'os,library'
|
||||
@@ -182,7 +182,7 @@ vulnerability:
|
||||
## Secret Options
|
||||
Available with secret scanning
|
||||
|
||||
```
|
||||
```yaml
|
||||
secret:
|
||||
# Same as '--secret-config'
|
||||
# Default is 'trivy-secret.yaml'
|
||||
@@ -193,7 +193,7 @@ secret:
|
||||
## Misconfiguration Options
|
||||
Available with misconfiguration scanning
|
||||
|
||||
```
|
||||
```yaml
|
||||
misconfiguration:
|
||||
# Same as '--file-patterns'
|
||||
# Default is empty
|
||||
@@ -256,7 +256,7 @@ misconfiguration:
|
||||
## Kubernetes Options
|
||||
Available with Kubernetes scanning
|
||||
|
||||
```
|
||||
```yaml
|
||||
kubernetes:
|
||||
# Same as '--context'
|
||||
# Default is empty
|
||||
@@ -270,7 +270,7 @@ kubernetes:
|
||||
## Repository Options
|
||||
Available with git repository scanning (`trivy repo`)
|
||||
|
||||
```
|
||||
```yaml
|
||||
repository:
|
||||
# Same as '--branch'
|
||||
# Default is empty
|
||||
@@ -288,7 +288,7 @@ repository:
|
||||
## Client/Server Options
|
||||
Available in client/server mode
|
||||
|
||||
```
|
||||
```yaml
|
||||
server:
|
||||
# Same as '--server' (available in client mode)
|
||||
# Default is empty
|
||||
@@ -313,4 +313,28 @@ server:
|
||||
listen: 0.0.0.0:10000
|
||||
```
|
||||
|
||||
## Cloud Options
|
||||
|
||||
Available for cloud scanning (currently only `trivy aws`)
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
[example]: https://github.com/aquasecurity/trivy/tree/{{ git.tag }}/examples/trivy-conf/trivy.yaml
|
||||
|
||||
BIN
docs/imgs/trivy-aws.png
Normal file
BIN
docs/imgs/trivy-aws.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
73
go.mod
73
go.mod
@@ -17,6 +17,9 @@ require (
|
||||
github.com/aquasecurity/testdocker v0.0.0-20210911155206-e1e85f5a1516
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20220627104749-930461748b63
|
||||
github.com/aquasecurity/trivy-kubernetes v0.3.1-0.20220727123250-2cfd49c5b6c3
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.8
|
||||
github.com/aws/aws-sdk-go-v2/config v1.15.15
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.10
|
||||
github.com/caarlos0/env/v6 v6.9.3
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||
github.com/cheggaaa/pb/v3 v3.1.0
|
||||
@@ -38,6 +41,7 @@ require (
|
||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/liamg/loading v0.0.4
|
||||
github.com/liamg/memoryfs v1.4.2
|
||||
github.com/liamg/tml v0.6.0
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
@@ -67,8 +71,58 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.12.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/athena v1.18.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.18.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.16.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.19.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.15.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/codebuild v1.19.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/docdb v1.19.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.51.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.18.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/efs v1.17.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/eks v1.21.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.22.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticsearchservice v1.16.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/emr v1.20.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.18.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kafka v1.17.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kinesis v1.15.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.18.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/lambda v1.23.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/mq v1.13.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/neptune v1.17.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.23.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/redshift v1.26.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.17.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/workspaces v1.22.0 // indirect
|
||||
github.com/aws/smithy-go v1.12.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
gonum.org/v1/gonum v0.7.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -92,7 +146,7 @@ require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.2 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.9.3 // indirect
|
||||
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
||||
@@ -107,7 +161,7 @@ require (
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
||||
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/aquasecurity/defsec v0.70.0
|
||||
github.com/aquasecurity/defsec v0.71.5
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.66
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
@@ -173,7 +227,6 @@ require (
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/go-version v1.4.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.13.0 // indirect
|
||||
github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
@@ -182,7 +235,7 @@ require (
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jdkato/prose v1.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.4 // indirect
|
||||
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
|
||||
@@ -194,7 +247,7 @@ require (
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/liamg/iamgo v0.0.9 // indirect
|
||||
github.com/liamg/jfather v0.0.7 // indirect
|
||||
github.com/lib/pq v1.10.4 // indirect
|
||||
github.com/lib/pq v1.10.6 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
@@ -241,7 +294,6 @@ require (
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rubenv/sql-migrate v1.1.1 // indirect
|
||||
github.com/russross/blackfriday v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/shogo82148/go-shuffle v0.0.0-20170808115208-59829097ff3b // indirect
|
||||
@@ -274,11 +326,10 @@ require (
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
|
||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
||||
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 // indirect
|
||||
gonum.org/v1/gonum v0.7.0 // indirect
|
||||
google.golang.org/api v0.81.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f // indirect
|
||||
@@ -292,11 +343,11 @@ require (
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gotest.tools v2.2.0+incompatible
|
||||
gotest.tools/v3 v3.2.0 // indirect
|
||||
helm.sh/helm/v3 v3.9.0 // indirect
|
||||
helm.sh/helm/v3 v3.9.2 // indirect
|
||||
k8s.io/api v0.25.0-alpha.2 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.24.0 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.24.2 // indirect
|
||||
k8s.io/apimachinery v0.25.0-alpha.2 // indirect
|
||||
k8s.io/apiserver v0.24.1 // indirect
|
||||
k8s.io/apiserver v0.24.2 // indirect
|
||||
k8s.io/cli-runtime v0.24.3 // indirect
|
||||
k8s.io/client-go v0.25.0-alpha.2 // indirect
|
||||
k8s.io/component-base v0.24.3 // indirect
|
||||
|
||||
150
go.sum
150
go.sum
@@ -123,8 +123,8 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
|
||||
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
|
||||
github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE=
|
||||
github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||
github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc=
|
||||
github.com/Masterminds/squirrel v1.5.3/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=
|
||||
@@ -204,12 +204,13 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
||||
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30xLN2sUZcMXl50hg+PJCIDdJgIvIbVcKqLJ/ZrtM=
|
||||
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8=
|
||||
github.com/aquasecurity/defsec v0.70.0 h1:tzmKrnR/OssRC/0RwnmmPwnoWOCOY7rPc+3ZaqLumg0=
|
||||
github.com/aquasecurity/defsec v0.70.0/go.mod h1:ZMuvHCXmvdL6EM3ckt/qY/qIJ6WEr5GNeGhNDFgVrcw=
|
||||
github.com/aquasecurity/defsec v0.71.5 h1:HOao1TaP74lhbsLUmYaNgHx1afdYImDicB8b/f54FIM=
|
||||
github.com/aquasecurity/defsec v0.71.5/go.mod h1:+ouYrROGLz3lGutl+K+ilXX5V41S76JIi+L8aXPBsAQ=
|
||||
github.com/aquasecurity/go-dep-parser v0.0.0-20220807122629-b5a21d267b03 h1:Axx5KwV0c83IlPLIIsi/Ht6sGsSJBzABUngXjFHFg4I=
|
||||
github.com/aquasecurity/go-dep-parser v0.0.0-20220807122629-b5a21d267b03/go.mod h1:SONYN1M+sYu6VIJsZnltmVfcGOCvp09HWbhpnHDn3aY=
|
||||
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-20220726154943-99847deb62b0 h1:tihCUjLWkF0b1SAjAKcFltUs3SpsqGrLtI+Frye0D10=
|
||||
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=
|
||||
@@ -239,6 +240,106 @@ github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3A
|
||||
github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.44.66 h1:xdH4EvHyUnkm4I8d536ui7yMQKYzrkbSDQ2LvRRHqsg=
|
||||
github.com/aws/aws-sdk-go v1.44.66/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.8 h1:gOe9UPR98XSf7oEJCcojYg+N2/jCRm4DdeIsP85pIyQ=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.8/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.3 h1:S/ZBwevQkr7gv5YxONYpGQxlMFFYSRfz3RMcjsC9Qhk=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.3/go.mod h1:gNsR5CaXKmQSSzrmGxmwmct/r+ZBfbxorAuXYsj/M5Y=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.15.15 h1:yBV+J7Au5KZwOIrIYhYkTGJbifZPCkAnCFSvGsF3ui8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.15.15/go.mod h1:A1Lzyy/o21I5/s2FbyX5AevQfSVXpvvIDCoVFD0BC4E=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.10 h1:7gGcMQePejwiKoDWjB9cWnpfVdnz/e5JwJFuT6OrroI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.10/go.mod h1:g5eIM5XRs/OzIIK81QMBl+dAuDyoLN0VYaLP+tBqEOk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.9 h1:hz8tc+OW17YqxyFFPSkvfSikbqWcyyHRyPVSTzC0+aI=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.9/go.mod h1:KDCCm4ONIdHtUloDcFvK2+vshZvx4Zmj7UMDfusuz5s=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.15 h1:bx5F2mr6H6FC7zNIQoDoUr8wEKnvmwRncujT3FYRtic=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.15/go.mod h1:pWrr2OoHlT7M/Pd2y4HV3gJyPb3qj5qMmnPkKSNPYK4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.9 h1:5sbyznZC2TeFpa4fvtpvpcGbzeXEEs1l1Jo51ynUNsQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.9/go.mod h1:08tUpeSGN33QKSO7fwxXczNfiwCpbj+GxK6XKwqWVv0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.16 h1:f0ySVcmQhwmzn7zQozd8wBM3yuGBfzdpsOaKQ0/Epzw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.16/go.mod h1:CYmI+7x03jjJih8kBEEFKRQc40UjUokT0k7GbvrhhTc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.6 h1:3L8pcjvgaSOs0zzZcMKzxDSkYKEpwJ2dNVDdxm68jAY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.6/go.mod h1:O7Oc4peGZDEKlddivslfYFvAbgzvl/GH3J8j3JIGBXc=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.11 h1:dLu3dF3ruiSZsG+in4ZzZWL3F7w4TeOX/F257qE2mT0=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.11/go.mod h1:Hb+D/fjqxVd1jAkIjTZF8Cg540F3E4YK5Uu4unA3rS0=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.12.9 h1:MkWoCyvIqAhaMO+LTSFag8s0wd6zV6Pd+X0urDKn2I8=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.12.9/go.mod h1:Va1mvuuqN0pejuszzc1nMPAsqGbIqIxBowdXzPYR9Gw=
|
||||
github.com/aws/aws-sdk-go-v2/service/athena v1.18.1 h1:RzNtlZanMLTYe3dcq7cZEEv40YvHY6hYylHz32jwEbk=
|
||||
github.com/aws/aws-sdk-go-v2/service/athena v1.18.1/go.mod h1:JBXnq5zXBUeQo+bbMrsg1Fx3+7+vxxwYLB+EDJiLP94=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.18.5 h1:MWmwy+Py1HXLNILagezUP9JPEV4CS33tU8xTJR65vMY=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.18.5/go.mod h1:xi7heuDU7iKWmWhvGCpsEvBko0NylAm4cmiJoxJKv9w=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.16.5 h1:dxrJ5ki6GuqZB9AqbE6HsqT8mrLcI2E+POgYt98YWTs=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.16.5/go.mod h1:55qJ5OVAwXAGgoBu9bPqoFlUj0iExM6UgvxiCqrHgYU=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.19.1 h1:JvOaYDuqyFn5JYggztv688+7eRMVtNp81vQ+F6OrBIw=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.19.1/go.mod h1:ZmYbhXLOStOS1+PItLyb9BNm8QtAQWkT5Nbd/tT19c4=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.15.11 h1:d9d/Vg1zkmo4OY0tWDywu5je9fXS4KXL5bW2T8wJ1cU=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.15.11/go.mod h1:0vT2mfhUL63/UT1RvYF/1wuqvvuvY0e+CiLB1paT+qI=
|
||||
github.com/aws/aws-sdk-go-v2/service/codebuild v1.19.9 h1:SBw4owb6Y9cKOmY0Z8PnY75PeceVYxnIgXNkuT3XGRU=
|
||||
github.com/aws/aws-sdk-go-v2/service/codebuild v1.19.9/go.mod h1:cQpAzFHSPsL/an19DbTTRb7kvuzMq8EcCX3WGO3+P0I=
|
||||
github.com/aws/aws-sdk-go-v2/service/docdb v1.19.1 h1:5EL1Sx9cwNXiX5z3gC6lbm/YyleuCwcssiOMi4zg7PI=
|
||||
github.com/aws/aws-sdk-go-v2/service/docdb v1.19.1/go.mod h1:gBnPk1RQP1qnmscOIiezJRsaQDrT6SDG3OwUmx6IA6c=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.10 h1:GLklbtMUQCToju09LyT+AjbwTQ0KCQudNLTA0H2xbBk=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.10/go.mod h1:zM5dQf0mZfcW4s8OsJFXvzedbY5n1rO581X4xei6XcA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.51.1 h1:y88XFO3AJWDVJ3HjcYc+Oo38fB948armdg6ulfphkUM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.51.1/go.mod h1:bKs78Qpk4syfUFXKhA0hIqT3X0sxmvIAPlEHV4qVbP0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.9 h1:9nU17hDiQCBptGMuCnx6UbN/RUGEDV+YOM+6W8i8zII=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.9/go.mod h1:fkIc4qe3SfQhPt/HAmDG7DJMjMBHElHV44axRyUSojA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.18.12 h1:PWpVksq9WWpOM7SiWD4gaiPDwUm8K/rn4nxQkdkYRtw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.18.12/go.mod h1:h1UvIIC+fPNj4PkuQ/o9QyRH0/vC+qlHRNGefwwYzv8=
|
||||
github.com/aws/aws-sdk-go-v2/service/efs v1.17.7 h1:FfmUBdGQ5tuFIIIwjmvy/DeGvvW0myQVFToQjPjjtEQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/efs v1.17.7/go.mod h1:cCrmFuFfPmhBtdw5YD3IzqtrpytrOYDDNhIMwuNrXTU=
|
||||
github.com/aws/aws-sdk-go-v2/service/eks v1.21.5 h1:miWUBz+htptzay+IZl70zYkTlO1FD7JIypv1D+8+rm0=
|
||||
github.com/aws/aws-sdk-go-v2/service/eks v1.21.5/go.mod h1:t2jyBeR+NLVCfPHpqT/1aygIu9yrW29JZREUJjgxnWg=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.22.1 h1:ctpT3Cl9LCSnzfDsulH5kECwXLL0jMXAnjukWeIdSZ4=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.22.1/go.mod h1:1Yuus60M9YJNgRxEYkfcAZs8NIyK2QAutQX2uYFbA+s=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.9 h1:ce76ovZsRsjqBEUHw/6sK1u3lMzrCi253ba1vaqBujQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.9/go.mod h1:HCDI4POpmQJpQK4UaQMDEHd3FsqfdzV8YGCwpznWhak=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticsearchservice v1.16.1 h1:x6/McT+Lxlr1hcADHu3dFzG2jRZope4BeBNTaCF2kYM=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticsearchservice v1.16.1/go.mod h1:A4rBOsc7JmoqJI0QlhMVmaeBA1gY504A9Pt/Z1vVDPc=
|
||||
github.com/aws/aws-sdk-go-v2/service/emr v1.20.2 h1:G66jwQlixBxtbxUh5AxRfeNFrA9FvjtbvxyGl9xY8gw=
|
||||
github.com/aws/aws-sdk-go-v2/service/emr v1.20.2/go.mod h1:FFLSJvJVSw9px5ZHi5KRq/JNOBu1d9n95V40SD/QWfs=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.18.10 h1:lB6TiFIJR0sZNWC2rGZ9+7OMtGpUEh/u/wYAn6HfbKk=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.18.10/go.mod h1:fhDORN+qPbMYyu98/RaDDiV60LXb9gvJ5UNZXY2hBNs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.3 h1:4n4KCtv5SUoT5Er5XV41huuzrCqepxlW3SDI9qHQebc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.3/go.mod h1:gkb2qADY+OHaGLKNTYxMaQNacfeyQpZ4csDTQMeFmcw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.10 h1:7LJcuRalaLw+GYQTMGmVUl4opg2HrDZkvn/L3KvIQfw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.10/go.mod h1:Qks+dxK3O+Z2deAhNo6cJ8ls1bam3tUGUAcgxQP1c70=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.9 h1:COsLtfmOSgPGnKUreE99/5pIgtmGLzmLtVrQa12QzU4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.9/go.mod h1:IixPDVckNk0HhYDQwUmTonTAfQlfABg9E72whAbq5k0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.9 h1:sHfDuhbOuuWSIAEDd3pma6p0JgUcR2iePxtCE8gfCxQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.9/go.mod h1:yQowTpvdZkFVuHrLBXmczat4W+WJKg/PafBZnGBLga0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.9 h1:sJdKvydGYDML9LTFcp6qq6Z5fIjN0Rdq2Gvw1hUg8tc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.9/go.mod h1:Rc5+wn2k8gFSi3V1Ch4mhxOzjMh+bYSXVFfVaqowQOY=
|
||||
github.com/aws/aws-sdk-go-v2/service/kafka v1.17.10 h1:ZsFXMWeNEkUjLEuVZY0jZb1uvAcDIYX67BI16ISG8LE=
|
||||
github.com/aws/aws-sdk-go-v2/service/kafka v1.17.10/go.mod h1:j3dSazeOhP6nWt7C3FAnYAwEGhYeLfneaapKIFJSlPk=
|
||||
github.com/aws/aws-sdk-go-v2/service/kinesis v1.15.10 h1:MKiqeOllGwLLP3PawduTfkQqPavNtGrSG9J9gahaSwA=
|
||||
github.com/aws/aws-sdk-go-v2/service/kinesis v1.15.10/go.mod h1:0Nz7L2pwh2bOumoDyt5oWFaC+qqw7BCzM46wxwR68O4=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.18.1 h1:y07kzPdcjuuyDVYWf1CCsQQ6kcAWMbFy+yIJ71xQBS0=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.18.1/go.mod h1:4PZMUkc9rXHWGVB5J9vKaZy3D7Nai79ORworQ3ASMiM=
|
||||
github.com/aws/aws-sdk-go-v2/service/lambda v1.23.5 h1:/tq5WZODNF3juZkpTIIMfzeJx6c8kLk73SjTTvOAphY=
|
||||
github.com/aws/aws-sdk-go-v2/service/lambda v1.23.5/go.mod h1:7YjiELsNgxpiMMG2KapRbAnOF1O+e1UnoLwARPNHKYc=
|
||||
github.com/aws/aws-sdk-go-v2/service/mq v1.13.5 h1:ztNwJLLJxGWc140Ixh+5316UxJd2N4sSCViA6lT1UUk=
|
||||
github.com/aws/aws-sdk-go-v2/service/mq v1.13.5/go.mod h1:Ap0H9UgOdD2eP1CEFGA50iIQFpJ/qxXogr4UDSozjTA=
|
||||
github.com/aws/aws-sdk-go-v2/service/neptune v1.17.3 h1:w3a/x4gSzMcHcS/ZiflrX+PygI9xr7T8po4uU3jPcGQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/neptune v1.17.3/go.mod h1:yIMXrISmxkkek9J7e61+c1gP2PwJk2hFjyxBQ+mgaG4=
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.23.2 h1:PiW9+dKNwnRCfpln8UukyBBOHhOGfS4NV0qkZQg+uPM=
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.23.2/go.mod h1:OiFKbn0c0/8hLpOLFg4P8Pw9bofLnuweWWqZPY7chBM=
|
||||
github.com/aws/aws-sdk-go-v2/service/redshift v1.26.1 h1:PXlUX4ErwlY1u7lZoMt3fuWSWebdSLMxsBDd0DqnpiA=
|
||||
github.com/aws/aws-sdk-go-v2/service/redshift v1.26.1/go.mod h1:XTvP5x9LIIgImxvUtXUHXdi3R56P+8BsSI7UeXCPz2U=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.2 h1:NvzGue25jKnuAsh6yQ+TZ4ResMcnp49AWgWGm2L4b5o=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.2/go.mod h1:u+566cosFI+d+motIz3USXEh6sN8Nq4GrNXSg2RXVMo=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.14 h1:dvvIB9OYsOH10RUNAY7yiCq5fQwGebXx1auBOkBTUlg=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.14/go.mod h1:xakbH8KMsQQKqzX87uyyzTHshc/0/Df8bsTneTS5pFU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.17.10 h1:ZZuqucIwjbUEJqxxR++VDZX9BcMbX5ZcQaKoWul/ELk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.17.10/go.mod h1:uITsRNVMeCB3MkWpXxXw0eDz8pW4TYLzj+eyQtbhSxM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.1 h1:HaQD4g8eumwEW218TgQzhnwTXmq77ZogA67SxBnGyPc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.1/go.mod h1:A94o564Gj+Yn+7QO1eLFeI7UVv3riy/YBFOfICVqFvU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.13 h1:DQpf+al+aWozOEmVEdml67qkVZ6vdtGUi71BZZWw40k=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.13/go.mod h1:d7ptRksDDgvXaUvxyHZ9SYh+iMDymm94JbVcgvSYSzU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.10 h1:7tquJrhjYz2EsCBvA9VTl+sBAAh1bv7h/sGASdZOGGo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.10/go.mod h1:cftkHYN6tCDNfkSasAmclSfl4l7cySoay8vz7p/ce0E=
|
||||
github.com/aws/aws-sdk-go-v2/service/workspaces v1.22.0 h1:6CPEYECdt2tRdtGObCxYN+NXFc46vC0tYpwY4mf2tS4=
|
||||
github.com/aws/aws-sdk-go-v2/service/workspaces v1.22.0/go.mod h1:ziCHySWl+3sgDxO+9lXeXZOmKtiUqXf1RPqcbYDlsb8=
|
||||
github.com/aws/smithy-go v1.12.0 h1:gXpeZel/jPoWQ7OEmLIgCUnhkFftqNfwWUwAHSlp1v0=
|
||||
github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
@@ -502,8 +603,8 @@ github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2 h1:lx1ZQgST/imDh
|
||||
github.com/dgryski/go-spooky v0.0.0-20170606183049-ed3d087f40e2/go.mod h1:hgHYKsoIw7S/hlWtP7wD1wZ7SX1jPTtKko5X9jrOgPQ=
|
||||
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-20211118083504-a29a3c99a684 h1:DBZ2sN7CK6dgvHVpQsQj4sRMCbWTmd17l+5SUCjnQSY=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684/go.mod h1:UfCu3YXJJCI+IdnqGgYP82dk2+Joxmv+mUTVBES6wac=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk=
|
||||
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=
|
||||
@@ -940,8 +1041,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
||||
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/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=
|
||||
github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
@@ -1019,14 +1120,16 @@ 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=
|
||||
github.com/liamg/jfather v0.0.7/go.mod h1:xXBGiBoiZ6tmHhfy5Jzw8sugzajwYdi6VosIpB3/cPM=
|
||||
github.com/liamg/loading v0.0.4 h1:i3+8cxqCbwVnz6RLqRZG4zHPKnY31T6NfM0h48mucvg=
|
||||
github.com/liamg/loading v0.0.4/go.mod h1:MpUOigKhyrByiW/te5JtMB9/f2MbZ4ZDk4wjorOwlpI=
|
||||
github.com/liamg/memoryfs v1.4.2 h1:6T9Oy1DdWxGCzIY89p0Ykeya5H0uAlzG2xHEGcvo6MU=
|
||||
github.com/liamg/memoryfs v1.4.2/go.mod h1:z7mfqXFQS8eSeBBsFjYLlxYRMRyiPktytvYCYTb3BSk=
|
||||
github.com/liamg/tml v0.6.0 h1:yOC/Q9p9Io3J11U9LdYVIwpRTnTE1GPMNFLrygkmE2Y=
|
||||
github.com/liamg/tml v0.6.0/go.mod h1:0h4EAV/zBOsqI91EWONedjRpO8O0itjGJVd+wG5eC+E=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
|
||||
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
|
||||
github.com/lib/pq v1.10.6/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/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
|
||||
@@ -1264,8 +1367,8 @@ github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
|
||||
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/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
|
||||
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=
|
||||
@@ -2290,8 +2393,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.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I=
|
||||
gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
|
||||
helm.sh/helm/v3 v3.9.0 h1:qDSWViuF6SzZX5s5AB/NVRGWmdao7T5j4S4ebIkMGag=
|
||||
helm.sh/helm/v3 v3.9.0/go.mod h1:fzZfyslcPAWwSdkXrXlpKexFeE2Dei8N27FFQWt+PN0=
|
||||
helm.sh/helm/v3 v3.9.2 h1:bx7kdhr5VAhYoWv9bIdT1C6qWR+/7SIoPCwLx22l78g=
|
||||
helm.sh/helm/v3 v3.9.2/go.mod h1:y/dJc/0Lzcn40jgd85KQXnufhFF7sr4v6L/vYMLRaRM=
|
||||
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=
|
||||
@@ -2303,20 +2406,18 @@ 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.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs=
|
||||
k8s.io/api v0.24.0/go.mod h1:5Jl90IUrJHUJYEMANRURMiVvJ0g7Ax7r3R1bqO8zx8I=
|
||||
k8s.io/api v0.24.1/go.mod h1:JhoOvNiLXKTPQ60zh2g0ewpA+bnEYf5q44Flhquh4vQ=
|
||||
k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg=
|
||||
k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI=
|
||||
k8s.io/api v0.25.0-alpha.2 h1:azwXduCht76Ecuv80QzZkCDzcFcLotKPXiE9/+jx5Qk=
|
||||
k8s.io/api v0.25.0-alpha.2/go.mod h1:wOntqHYj8WveLW2sh6q4tkE2vMZTtxe0MrFyVwO8JCM=
|
||||
k8s.io/apiextensions-apiserver v0.24.0 h1:JfgFqbA8gKJ/uDT++feAqk9jBIwNnL9YGdQvaI9DLtY=
|
||||
k8s.io/apiextensions-apiserver v0.24.0/go.mod h1:iuVe4aEpe6827lvO6yWQVxiPSpPoSKVjkq+MIdg84cM=
|
||||
k8s.io/apiextensions-apiserver v0.24.2 h1:/4NEQHKlEz1MlaK/wHT5KMKC9UKYz6NZz6JE6ov4G6k=
|
||||
k8s.io/apiextensions-apiserver v0.24.2/go.mod h1:e5t2GMFVngUEHUd0wuCJzw8YDwZoqZfJiGOW6mm2hLQ=
|
||||
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.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
|
||||
k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U=
|
||||
k8s.io/apimachinery v0.24.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
|
||||
k8s.io/apimachinery v0.24.1/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
|
||||
k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
|
||||
k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
|
||||
k8s.io/apimachinery v0.25.0-alpha.2 h1:y6uTWaiqsPTPRewnXJ15IFyGmBo2qPt6enm4zszG8Z0=
|
||||
k8s.io/apimachinery v0.25.0-alpha.2/go.mod h1:h34FtK3eCxige6ZIACdBSYExtDaKAUxoc7hVe2LOxzw=
|
||||
@@ -2324,29 +2425,26 @@ 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.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ=
|
||||
k8s.io/apiserver v0.24.0/go.mod h1:WFx2yiOMawnogNToVvUYT9nn1jaIkMKj41ZYCVycsBA=
|
||||
k8s.io/apiserver v0.24.1 h1:LAA5UpPOeaREEtFAQRUQOI3eE5So/j5J3zeQJjeLdz4=
|
||||
k8s.io/apiserver v0.24.1/go.mod h1:dQWNMx15S8NqJMp0gpYfssyvhYnkilc1LpExd/dkLh0=
|
||||
k8s.io/apiserver v0.24.2 h1:orxipm5elPJSkkFNlwH9ClqaKEDJJA3yR2cAAlCnyj4=
|
||||
k8s.io/apiserver v0.24.2/go.mod h1:pSuKzr3zV+L+MWqsEo0kHHYwCo77AT5qXbFXP2jbvFI=
|
||||
k8s.io/cli-runtime v0.24.3 h1:O9YvUHrDSCQUPlsqVmaqDrueqjpJ7IO6Yas9B6xGSoo=
|
||||
k8s.io/cli-runtime v0.24.3/go.mod h1:In84wauoMOqa7JDvDSXGbf8lTNlr70fOGpYlYfJtSqA=
|
||||
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.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y=
|
||||
k8s.io/client-go v0.24.0/go.mod h1:VFPQET+cAFpYxh6Bq6f4xyMY80G6jKKktU6G0m00VDw=
|
||||
k8s.io/client-go v0.24.1/go.mod h1:f1kIDqcEYmwXS/vTbbhopMUbhKp2JhOeVTfxgaCIlF8=
|
||||
k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30=
|
||||
k8s.io/client-go v0.24.3/go.mod h1:AAovolf5Z9bY1wIg2FZ8LPQlEdKHjLI7ZD4rw920BJw=
|
||||
k8s.io/client-go v0.25.0-alpha.2 h1:kXlDl2L/CmdubzbRTPOCXj9JDPv9U0MuEjRXSCltQ00=
|
||||
k8s.io/client-go v0.25.0-alpha.2/go.mod h1:AN5W2BkXTu2lNm2BANn5lC6VnGlv6AM5HNPQLsriBOA=
|
||||
k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0=
|
||||
k8s.io/code-generator v0.24.0/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w=
|
||||
k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w=
|
||||
k8s.io/code-generator v0.24.3/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w=
|
||||
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.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI=
|
||||
k8s.io/component-base v0.24.0/go.mod h1:Dgazgon0i7KYUsS8krG8muGiMVtUZxG037l1MKyXgrA=
|
||||
k8s.io/component-base v0.24.1/go.mod h1:DW5vQGYVCog8WYpNob3PMmmsY8A3L9QZNg4j/dV3s38=
|
||||
k8s.io/component-base v0.24.2/go.mod h1:ucHwW76dajvQ9B7+zecZAP3BVqvrHoOxm8olHEg0nmM=
|
||||
k8s.io/component-base v0.24.3 h1:u99WjuHYCRJjS1xeLOx72DdRaghuDnuMgueiGMFy1ec=
|
||||
k8s.io/component-base v0.24.3/go.mod h1:bqom2IWN9Lj+vwAkPNOv2TflsP1PeVDIwIN0lRthxYY=
|
||||
k8s.io/component-helpers v0.24.3/go.mod h1:/1WNW8TfBOijQ1ED2uCHb4wtXYWDVNMqUll8h36iNVo=
|
||||
|
||||
@@ -69,6 +69,8 @@ nav:
|
||||
- Scanning: docs/kubernetes/cli/scanning.md
|
||||
- Operator:
|
||||
- Overview: docs/kubernetes/operator/index.md
|
||||
- Cloud:
|
||||
- AWS: docs/cloud/aws/scanning.md
|
||||
- SBOM:
|
||||
- Overview: docs/sbom/index.md
|
||||
- CycloneDX: docs/sbom/cyclonedx.md
|
||||
|
||||
187
pkg/cloud/aws/commands/run.go
Normal file
187
pkg/cloud/aws/commands/run.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aquasecurity/defsec/pkg/errs"
|
||||
|
||||
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/cloud"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/cloud/cache"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/flag"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/cloud/aws/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/cloud/report"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
|
||||
awsScanner "github.com/aquasecurity/defsec/pkg/scanners/cloud/aws"
|
||||
)
|
||||
|
||||
func getAccountIDAndRegion(ctx context.Context, region string) (string, string, error) {
|
||||
log.Logger.Debug("Looking for AWS credentials provider...")
|
||||
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO())
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if region != "" {
|
||||
cfg.Region = region
|
||||
}
|
||||
|
||||
svc := sts.NewFromConfig(cfg)
|
||||
|
||||
log.Logger.Debug("Looking up AWS caller identity...")
|
||||
result, err := svc.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to discover AWS caller identity: %w", err)
|
||||
}
|
||||
if result.Account == nil {
|
||||
return "", "", fmt.Errorf("missing account id for aws account")
|
||||
}
|
||||
log.Logger.Debugf("Verified AWS credentials for account %s!", *result.Account)
|
||||
return *result.Account, cfg.Region, nil
|
||||
}
|
||||
|
||||
func processOptions(ctx context.Context, opt *flag.Options) error {
|
||||
// support comma separated services too
|
||||
var splitServices []string
|
||||
for _, service := range opt.Services {
|
||||
splitServices = append(splitServices, strings.Split(service, ",")...)
|
||||
}
|
||||
opt.Services = splitServices
|
||||
|
||||
if len(opt.Services) != 1 && opt.ARN != "" {
|
||||
return fmt.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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(opt.Services) == 0 {
|
||||
log.Logger.Debug("No service(s) specified, scanning all services...")
|
||||
opt.Services = awsScanner.AllSupportedServices()
|
||||
} else {
|
||||
log.Logger.Debugf("Specific services were requested: [%s]...", strings.Join(opt.Services, ", "))
|
||||
for _, service := range opt.Services {
|
||||
var found bool
|
||||
supported := awsScanner.AllSupportedServices()
|
||||
for _, allowed := range supported {
|
||||
if allowed == service {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.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()
|
||||
|
||||
if err := log.InitLogger(opt.Debug, false); err != nil {
|
||||
return fmt.Errorf("logger error: %w", err)
|
||||
}
|
||||
|
||||
var err error
|
||||
defer func() {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
log.Logger.Warn("Increase --timeout value")
|
||||
}
|
||||
}()
|
||||
|
||||
if err := processOptions(ctx, &opt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cached := cache.New(opt.CacheDir, opt.MaxCacheAge, cloud.ProviderAWS, opt.Account, opt.Region)
|
||||
servicesInCache := cached.ListAvailableServices(false)
|
||||
var servicesToLoadFromCache []string
|
||||
var servicesToScan []string
|
||||
for _, service := range opt.Services {
|
||||
if cached != nil {
|
||||
var inCache bool
|
||||
for _, cacheSvc := range servicesInCache {
|
||||
if cacheSvc == service {
|
||||
log.Logger.Debugf("Results for service '%s' found in cache.", service)
|
||||
inCache = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if inCache && !opt.UpdateCache {
|
||||
servicesToLoadFromCache = append(servicesToLoadFromCache, service)
|
||||
continue
|
||||
}
|
||||
}
|
||||
servicesToScan = append(servicesToScan, service)
|
||||
}
|
||||
|
||||
var r *report.Report
|
||||
|
||||
// if there is anything we need that wasn't in the cache, scan it now
|
||||
if len(servicesToScan) > 0 {
|
||||
log.Logger.Debugf("Scanning the following services using the AWS API: [%s]...", strings.Join(servicesToScan, ", "))
|
||||
opt.Services = servicesToScan
|
||||
results, err := scanner.NewScanner().Scan(ctx, opt)
|
||||
if err != nil {
|
||||
var aerr errs.AdapterError
|
||||
if errors.As(err, &aerr) {
|
||||
for _, e := range aerr.Errors() {
|
||||
log.Logger.Warnf("Adapter error: %s", e)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("aws scan error: %w", err)
|
||||
}
|
||||
r = report.New(cloud.ProviderAWS, opt.Account, opt.Region, results.GetFailed(), opt.Services)
|
||||
} else {
|
||||
log.Logger.Debug("No more services to scan - everything was found in the cache.")
|
||||
r = report.New(cloud.ProviderAWS, opt.Account, opt.Region, nil, opt.Services)
|
||||
}
|
||||
if len(servicesToLoadFromCache) > 0 {
|
||||
log.Logger.Debug("Loading cached results...")
|
||||
cachedReport, err := cached.LoadReport(servicesToLoadFromCache...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for service, results := range cachedReport.Results {
|
||||
log.Logger.Debugf("Adding cached results for '%s'...", service)
|
||||
r.AddResultsForService(service, results.Results, results.CreationTime)
|
||||
}
|
||||
}
|
||||
|
||||
if len(servicesToScan) > 0 { // don't write cache if we didn't scan anything new
|
||||
log.Logger.Debugf("Writing results to cache for services [%s]...", strings.Join(r.ServicesInScope, ", "))
|
||||
if err := cached.Save(r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Logger.Debug("Writing report to output...")
|
||||
if err := report.Write(r, opt, len(servicesToLoadFromCache) > 0); err != nil {
|
||||
return fmt.Errorf("unable to write results: %w", err)
|
||||
}
|
||||
|
||||
cmd.Exit(opt, r.Failed())
|
||||
return nil
|
||||
}
|
||||
79
pkg/cloud/aws/scanner/progress.go
Normal file
79
pkg/cloud/aws/scanner/progress.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/liamg/loading/pkg/bar"
|
||||
)
|
||||
|
||||
type progressTracker struct {
|
||||
serviceBar *bar.Bar
|
||||
serviceTotal int
|
||||
serviceCurrent int
|
||||
isTTY bool
|
||||
}
|
||||
|
||||
func newProgressTracker() *progressTracker {
|
||||
var isTTY bool
|
||||
if stat, err := os.Stdout.Stat(); err == nil {
|
||||
isTTY = stat.Mode()&os.ModeCharDevice == os.ModeCharDevice
|
||||
}
|
||||
return &progressTracker{
|
||||
isTTY: isTTY,
|
||||
}
|
||||
}
|
||||
|
||||
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.Printf("[%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...")
|
||||
}
|
||||
74
pkg/cloud/aws/scanner/scanner.go
Normal file
74
pkg/cloud/aws/scanner/scanner.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/aquasecurity/defsec/pkg/framework"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/flag"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
|
||||
"github.com/aquasecurity/defsec/pkg/scan"
|
||||
"github.com/aquasecurity/defsec/pkg/scanners/cloud/aws"
|
||||
"github.com/aquasecurity/defsec/pkg/scanners/options"
|
||||
)
|
||||
|
||||
type AWSScanner struct {
|
||||
}
|
||||
|
||||
func NewScanner() *AWSScanner {
|
||||
return &AWSScanner{}
|
||||
}
|
||||
|
||||
func (s *AWSScanner) Scan(ctx context.Context, option flag.Options) (scan.Results, error) {
|
||||
|
||||
var scannerOpts []options.ScannerOption
|
||||
if !option.NoProgress {
|
||||
tracker := newProgressTracker()
|
||||
defer tracker.Finish()
|
||||
scannerOpts = append(scannerOpts, aws.ScannerWithProgressTracker(tracker))
|
||||
}
|
||||
|
||||
if len(option.Services) > 0 {
|
||||
scannerOpts = append(scannerOpts, aws.ScannerWithAWSServices(option.Services...))
|
||||
}
|
||||
|
||||
if option.Debug {
|
||||
scannerOpts = append(scannerOpts, options.ScannerWithDebug(&defsecLogger{}))
|
||||
}
|
||||
|
||||
if option.Region != "" {
|
||||
scannerOpts = append(
|
||||
scannerOpts,
|
||||
aws.ScannerWithAWSRegion(option.Region),
|
||||
)
|
||||
}
|
||||
|
||||
if option.Endpoint != "" {
|
||||
scannerOpts = append(
|
||||
scannerOpts,
|
||||
aws.ScannerWithAWSEndpoint(option.Endpoint),
|
||||
)
|
||||
}
|
||||
|
||||
scannerOpts = append(scannerOpts, options.ScannerWithFrameworks(
|
||||
framework.Default,
|
||||
framework.CIS_AWS_1_2,
|
||||
))
|
||||
|
||||
defsecResults, err := aws.New(scannerOpts...).Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return defsecResults, nil
|
||||
}
|
||||
|
||||
type defsecLogger struct {
|
||||
}
|
||||
|
||||
func (d *defsecLogger) Write(p []byte) (n int, err error) {
|
||||
log.Logger.Debug("[defsec] " + strings.TrimSpace(string(p)))
|
||||
return len(p), nil
|
||||
}
|
||||
65
pkg/cloud/cache/cache.go
vendored
Normal file
65
pkg/cloud/cache/cache.go
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
metadataFilename = "metadata.json"
|
||||
cacheFilename = "cache.json"
|
||||
dataDirName = "data"
|
||||
cacheSubDir = "cloud"
|
||||
)
|
||||
|
||||
var ErrCacheNotFound = fmt.Errorf("cache record not found")
|
||||
|
||||
type Cache struct {
|
||||
path string
|
||||
provider string
|
||||
accountID string
|
||||
region string
|
||||
maxAge time.Duration
|
||||
}
|
||||
|
||||
func New(basePath string, maxAge time.Duration, provider string, accountID string, region string) *Cache {
|
||||
return &Cache{
|
||||
path: path.Join(basePath, cacheSubDir, strings.ToLower(provider), accountID, strings.ToLower(region)),
|
||||
provider: provider,
|
||||
accountID: accountID,
|
||||
region: region,
|
||||
maxAge: maxAge,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ListAvailableServices(includeExpired bool) []string {
|
||||
metadata, err := c.loadMetadata()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
r, err := c.LoadReport(metadata.ServicesInScope...)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var available []string
|
||||
for _, service := range metadata.ServicesInScope {
|
||||
if entry, ok := r.Results[service]; ok {
|
||||
if includeExpired || entry.CreationTime.Add(c.maxAge).After(time.Now()) {
|
||||
available = append(available, service)
|
||||
}
|
||||
}
|
||||
}
|
||||
return available
|
||||
}
|
||||
|
||||
func (c *Cache) getServicePath(service string) string {
|
||||
service = strings.NewReplacer(" ", "_", ".", "_").Replace(service)
|
||||
return filepath.Join(c.path, dataDirName, service, cacheFilename)
|
||||
}
|
||||
|
||||
func (c *Cache) getMetadataPath() string {
|
||||
return filepath.Join(c.path, metadataFilename)
|
||||
}
|
||||
166
pkg/cloud/cache/cache_test.go
vendored
Normal file
166
pkg/cloud/cache/cache_test.go
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/cloud/report"
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input report.Report
|
||||
services []string
|
||||
}{
|
||||
{
|
||||
name: "no services",
|
||||
input: report.Report{
|
||||
Provider: "AWS",
|
||||
AccountID: "1234567890",
|
||||
Region: "us-east-1",
|
||||
Results: make(map[string]report.ResultsAtTime),
|
||||
ServicesInScope: nil,
|
||||
},
|
||||
services: nil,
|
||||
},
|
||||
{
|
||||
name: "all services",
|
||||
input: report.Report{
|
||||
Provider: "AWS",
|
||||
AccountID: "1234567890",
|
||||
Region: "us-east-1",
|
||||
Results: map[string]report.ResultsAtTime{
|
||||
"s3": {
|
||||
Results: nil,
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
"ec2": {
|
||||
Results: nil,
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
},
|
||||
ServicesInScope: []string{"ec2", "s3"},
|
||||
},
|
||||
services: []string{"ec2", "s3"},
|
||||
},
|
||||
{
|
||||
name: "partial services",
|
||||
input: report.Report{
|
||||
Provider: "AWS",
|
||||
AccountID: "1234567890",
|
||||
Region: "us-east-1",
|
||||
Results: map[string]report.ResultsAtTime{
|
||||
"s3": {
|
||||
Results: nil,
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
"ec2": {
|
||||
Results: nil,
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
},
|
||||
ServicesInScope: []string{"ec2", "s3"},
|
||||
},
|
||||
services: []string{"ec2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
baseDir := t.TempDir()
|
||||
|
||||
// ensure saving doesn't error
|
||||
cache := New(baseDir, time.Hour, test.input.Provider, test.input.AccountID, test.input.Region)
|
||||
require.NoError(t, cache.Save(&test.input))
|
||||
|
||||
// ensure all scoped services were cached
|
||||
available := cache.ListAvailableServices(false)
|
||||
assert.Equal(t, test.input.ServicesInScope, available)
|
||||
|
||||
// ensure all cached services are really available
|
||||
fullReport, err := cache.LoadReport(available...)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, available, fullReport.ServicesInScope)
|
||||
|
||||
// ensure loading restores all (specified) data
|
||||
loaded, err := cache.LoadReport(test.services...)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.input.Provider, loaded.Provider)
|
||||
assert.Equal(t, test.input.AccountID, loaded.AccountID)
|
||||
assert.Equal(t, test.input.Region, loaded.Region)
|
||||
assert.ElementsMatch(t, test.services, loaded.ServicesInScope)
|
||||
|
||||
var actualServices []string
|
||||
for service := range loaded.Results {
|
||||
actualServices = append(actualServices, service)
|
||||
}
|
||||
assert.ElementsMatch(t, test.services, actualServices)
|
||||
|
||||
for _, service := range test.services {
|
||||
assert.Equal(t, test.input.Results[service].CreationTime.Format(time.RFC3339), loaded.Results[service].CreationTime.Format(time.RFC3339))
|
||||
assert.Equal(t, test.input.Results[service].Results, loaded.Results[service].Results)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartialCacheOverwrite(t *testing.T) {
|
||||
baseDir := t.TempDir()
|
||||
|
||||
r1 := report.Report{
|
||||
Provider: "AWS",
|
||||
AccountID: "1234567890",
|
||||
Region: "us-east-1",
|
||||
Results: map[string]report.ResultsAtTime{
|
||||
"a": {
|
||||
Results: nil,
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
"b": {
|
||||
Results: nil,
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
"c": {
|
||||
Results: nil,
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
"d": {
|
||||
Results: nil,
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
},
|
||||
ServicesInScope: []string{"a", "b", "c", "d"},
|
||||
}
|
||||
|
||||
// ensure saving doesn't error
|
||||
cache := New(baseDir, time.Hour, "AWS", "1234567890", "us-east-1")
|
||||
require.NoError(t, cache.Save(&r1))
|
||||
|
||||
r2 := report.Report{
|
||||
Provider: "AWS",
|
||||
AccountID: "1234567890",
|
||||
Region: "us-east-1",
|
||||
Results: map[string]report.ResultsAtTime{
|
||||
"a": {
|
||||
Results: nil,
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
"b": {
|
||||
Results: nil,
|
||||
CreationTime: time.Now(),
|
||||
},
|
||||
},
|
||||
ServicesInScope: []string{"a", "b"},
|
||||
}
|
||||
require.NoError(t, cache.Save(&r2))
|
||||
|
||||
assert.ElementsMatch(t, []string{"a", "b", "c", "d"}, cache.ListAvailableServices(false))
|
||||
}
|
||||
59
pkg/cloud/cache/load.go
vendored
Normal file
59
pkg/cloud/cache/load.go
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/cloud/report"
|
||||
)
|
||||
|
||||
func (c *Cache) loadMetadata() (*Metadata, error) {
|
||||
metadataFile := c.getMetadataPath()
|
||||
m, err := os.Open(metadataFile)
|
||||
if err != nil {
|
||||
return nil, ErrCacheNotFound
|
||||
}
|
||||
|
||||
var metadata Metadata
|
||||
if err := json.NewDecoder(m).Decode(&metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &metadata, nil
|
||||
}
|
||||
|
||||
func (c *Cache) LoadReport(services ...string) (*report.Report, error) {
|
||||
|
||||
metadata, err := c.loadMetadata()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base := report.New(c.provider, c.accountID, c.region, nil, nil)
|
||||
|
||||
for _, service := range services {
|
||||
if !contains(metadata.ServicesInScope, service) {
|
||||
continue
|
||||
}
|
||||
serviceFile := c.getServicePath(service)
|
||||
s, err := os.Open(serviceFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var serviceRecord Record
|
||||
if err := json.NewDecoder(s).Decode(&serviceRecord); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
base.AddResultsForService(service, serviceRecord.Results, serviceRecord.CreationTime)
|
||||
}
|
||||
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
77
pkg/cloud/cache/save.go
vendored
Normal file
77
pkg/cloud/cache/save.go
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/cloud/report"
|
||||
)
|
||||
|
||||
func (c *Cache) Save(r *report.Report) error {
|
||||
|
||||
existingServices := c.ListAvailableServices(true)
|
||||
|
||||
if err := os.MkdirAll(
|
||||
filepath.Dir(c.getMetadataPath()),
|
||||
0700,
|
||||
); err != nil { // only the current user is allowed to see this report
|
||||
return err
|
||||
}
|
||||
|
||||
var retainedServices []string
|
||||
for _, existing := range existingServices {
|
||||
var found bool
|
||||
for _, service := range r.ServicesInScope {
|
||||
if service == existing {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
retainedServices = append(retainedServices, existing)
|
||||
}
|
||||
|
||||
for _, service := range r.ServicesInScope {
|
||||
serviceFile := c.getServicePath(service)
|
||||
if err := os.MkdirAll(
|
||||
filepath.Dir(serviceFile),
|
||||
0700,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
resultSet, err := r.GetResultsForService(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := os.Create(serviceFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record := Record{
|
||||
SchemaVersion: SchemaVersion,
|
||||
Service: service,
|
||||
Results: resultSet.Results,
|
||||
CreationTime: resultSet.CreationTime,
|
||||
}
|
||||
if err := json.NewEncoder(s).Encode(record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
metadataFile := c.getMetadataPath()
|
||||
metadata := Metadata{
|
||||
SchemaVersion: SchemaVersion,
|
||||
Provider: c.provider,
|
||||
AccountID: c.accountID,
|
||||
Region: c.region,
|
||||
ServicesInScope: append(r.ServicesInScope, retainedServices...),
|
||||
}
|
||||
m, err := os.Create(metadataFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.NewEncoder(m).Encode(metadata)
|
||||
}
|
||||
24
pkg/cloud/cache/schema.go
vendored
Normal file
24
pkg/cloud/cache/schema.go
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
const SchemaVersion = 1
|
||||
|
||||
type Metadata struct {
|
||||
SchemaVersion int `json:"schema_version"`
|
||||
Provider string `json:"provider"`
|
||||
AccountID string `json:"account_id"`
|
||||
Region string `json:"region"`
|
||||
ServicesInScope []string `json:"services"`
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
SchemaVersion int `json:"schema_version"`
|
||||
Service string `json:"service"`
|
||||
Results types.Results `json:"results"`
|
||||
CreationTime time.Time `json:"creation_time"`
|
||||
}
|
||||
5
pkg/cloud/provider.go
Normal file
5
pkg/cloud/provider.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package cloud
|
||||
|
||||
const (
|
||||
ProviderAWS = "AWS"
|
||||
)
|
||||
95
pkg/cloud/report/convert.go
Normal file
95
pkg/cloud/report/convert.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aquasecurity/defsec/pkg/scan"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"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 {
|
||||
existingService, ok := resultsByServiceAndARN[result.Rule().Service]
|
||||
if !ok {
|
||||
existingService = make(map[string]scan.Results)
|
||||
}
|
||||
resource := result.Flatten().Resource
|
||||
|
||||
existingService[resource] = append(existingService[resource], result)
|
||||
resultsByServiceAndARN[result.Rule().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() == "" || strings.HasPrefix(result.RegoNamespace(), "builtin.") {
|
||||
primaryURL = fmt.Sprintf("https://avd.aquasec.com/misconfig/%s", strings.ToLower(result.Rule().AVDID))
|
||||
}
|
||||
|
||||
status := types.StatusFailure
|
||||
switch result.Status() {
|
||||
case scan.StatusPassed:
|
||||
status = types.StatusPassed
|
||||
case scan.StatusIgnored:
|
||||
status = types.StatusException
|
||||
}
|
||||
|
||||
flat := result.Flatten()
|
||||
|
||||
arnResult.Misconfigurations = append(arnResult.Misconfigurations, types.DetectedMisconfiguration{
|
||||
Type: provider,
|
||||
ID: 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: flat.RuleService,
|
||||
StartLine: flat.Location.StartLine,
|
||||
EndLine: flat.Location.EndLine,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
convertedArnResults = append(convertedArnResults, arnResult)
|
||||
}
|
||||
convertedResults[service] = ResultsAtTime{
|
||||
Results: convertedArnResults,
|
||||
CreationTime: time.Now(),
|
||||
}
|
||||
}
|
||||
return convertedResults
|
||||
}
|
||||
241
pkg/cloud/report/convert_test.go
Normal file
241
pkg/cloud/report/convert_test.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
fanaltypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws/arn"
|
||||
|
||||
defsecTypes "github.com/aquasecurity/defsec/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aquasecurity/defsec/pkg/scan"
|
||||
)
|
||||
|
||||
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",
|
||||
defsecTypes.NewRemoteMetadata((arn.ARN{
|
||||
Partition: "aws",
|
||||
Service: "s3",
|
||||
Region: "us-east-1",
|
||||
AccountID: "1234567890",
|
||||
Resource: "bucket1",
|
||||
}).String()),
|
||||
)
|
||||
s3Results.Add(
|
||||
"something else failed",
|
||||
defsecTypes.NewRemoteMetadata((arn.ARN{
|
||||
Partition: "aws",
|
||||
Service: "s3",
|
||||
Region: "us-east-1",
|
||||
AccountID: "1234567890",
|
||||
Resource: "bucket2",
|
||||
}).String()),
|
||||
)
|
||||
s3Results.Add(
|
||||
"something else failed again",
|
||||
defsecTypes.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",
|
||||
defsecTypes.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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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)
|
||||
}
|
||||
}
|
||||
175
pkg/cloud/report/report.go
Normal file
175
pkg/cloud/report/report.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
|
||||
"github.com/liamg/tml"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/flag"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/report"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
|
||||
"github.com/aquasecurity/defsec/pkg/scan"
|
||||
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
tableFormat = "table"
|
||||
)
|
||||
|
||||
// Report represents a kubernetes 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(rep *Report, opt flag.Options, fromCache bool) error {
|
||||
|
||||
var filtered []types.Result
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// filter results
|
||||
for _, resultsAtTime := range rep.Results {
|
||||
for _, res := range resultsAtTime.Results {
|
||||
resCopy := res
|
||||
if err := result.Filter(
|
||||
ctx,
|
||||
&resCopy,
|
||||
opt.Severities,
|
||||
false,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
nil,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Slice(resCopy.Misconfigurations, func(i, j int) bool {
|
||||
return resCopy.Misconfigurations[i].CauseMetadata.Resource < resCopy.Misconfigurations[i].CauseMetadata.Resource
|
||||
})
|
||||
filtered = append(filtered, resCopy)
|
||||
}
|
||||
}
|
||||
sort.Slice(filtered, func(i, j int) bool {
|
||||
return filtered[i].Target < filtered[j].Target
|
||||
})
|
||||
|
||||
base := types.Report{
|
||||
ArtifactName: rep.AccountID,
|
||||
ArtifactType: ftypes.ArtifactAWSAccount,
|
||||
Results: filtered,
|
||||
}
|
||||
|
||||
switch opt.Format {
|
||||
case tableFormat:
|
||||
|
||||
// ensure color/formatting is disabled for pipes/non-pty
|
||||
var useANSI bool
|
||||
if opt.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, opt.Output, opt.Services[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
case len(opt.Services) == 1 && opt.ARN != "":
|
||||
if err := writeResultsForARN(rep, filtered, opt.Output, opt.Services[0], opt.ARN, opt.Severities); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if err := writeServiceTable(rep, filtered, opt.Output); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// render cache info
|
||||
if fromCache {
|
||||
_ = tml.Fprintf(opt.Output, "\n<blue>This scan report was loaded from cached results. If you'd like to run a fresh scan, use --update-cache.</blue>\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
default:
|
||||
return report.Write(base, pkgReport.Option{
|
||||
Format: opt.Format,
|
||||
Output: opt.Output,
|
||||
Severities: opt.Severities,
|
||||
OutputTemplate: opt.Template,
|
||||
IncludeNonFailures: opt.IncludeNonFailures,
|
||||
Trace: opt.Trace,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Report) GetResultsForService(service string) (*ResultsAtTime, error) {
|
||||
if set, ok := r.Results[service]; ok {
|
||||
return &set, nil
|
||||
}
|
||||
for _, scoped := range r.ServicesInScope {
|
||||
if scoped == service {
|
||||
return &ResultsAtTime{
|
||||
Results: nil,
|
||||
CreationTime: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("service %q not found", service)
|
||||
}
|
||||
|
||||
func (r *Report) AddResultsForService(service string, results types.Results, creation time.Time) {
|
||||
r.Results[service] = ResultsAtTime{
|
||||
Results: results,
|
||||
CreationTime: creation,
|
||||
}
|
||||
for _, exists := range r.ServicesInScope {
|
||||
if exists == service {
|
||||
return
|
||||
}
|
||||
}
|
||||
r.ServicesInScope = append(r.ServicesInScope, service)
|
||||
}
|
||||
89
pkg/cloud/report/resource.go
Normal file
89
pkg/cloud/report/resource.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/liamg/tml"
|
||||
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/aquasecurity/table"
|
||||
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, "\n<bold>Resource Summary for Service '%s' (%s Account %s)</bold>\n", service, report.Provider, report.AccountID)
|
||||
|
||||
// render table
|
||||
if len(sortable) > 0 {
|
||||
t.Render()
|
||||
} else {
|
||||
_, _ = fmt.Fprint(output, "\nNo problems detected.\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
123
pkg/cloud/report/resource_test.go
Normal file
123
pkg/cloud/report/resource_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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,
|
||||
)
|
||||
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
tt.options.Output = buffer
|
||||
require.NoError(t, Write(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, buffer.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
37
pkg/cloud/report/result.go
Normal file
37
pkg/cloud/report/result.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/liamg/tml"
|
||||
|
||||
renderer "github.com/aquasecurity/trivy/pkg/report/table"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"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, "\n<bold>Results for '%s' (%s Account %s)</bold>\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
|
||||
}
|
||||
82
pkg/cloud/report/result_test.go
Normal file
82
pkg/cloud/report/result_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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,
|
||||
)
|
||||
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
tt.options.Output = buffer
|
||||
require.NoError(t, Write(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(buffer.String(), "\r\n", "\n"))
|
||||
})
|
||||
}
|
||||
}
|
||||
86
pkg/cloud/report/service.go
Normal file
86
pkg/cloud/report/service.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/liamg/tml"
|
||||
|
||||
"github.com/aquasecurity/table"
|
||||
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, "\n<bold>Scan Overview for %s Account %s</bold>\n", report.Provider, report.AccountID)
|
||||
|
||||
// render table
|
||||
t.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
407
pkg/cloud/report/service_test.go
Normal file
407
pkg/cloud/report/service_test.go
Normal file
@@ -0,0 +1,407 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/types"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/flag"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws/arn"
|
||||
|
||||
"github.com/aquasecurity/defsec/pkg/scan"
|
||||
defsecTypes "github.com/aquasecurity/defsec/pkg/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: `{
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
}`,
|
||||
},
|
||||
}
|
||||
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,
|
||||
)
|
||||
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
tt.options.Output = buffer
|
||||
require.NoError(t, Write(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, buffer.String())
|
||||
} else {
|
||||
assert.Equal(t, tt.expected, buffer.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",
|
||||
defsecTypes.NewRemoteMetadata((arn.ARN{
|
||||
Partition: "aws",
|
||||
Service: "s3",
|
||||
Region: "us-east-1",
|
||||
AccountID: "1234567890",
|
||||
Resource: "bucket1",
|
||||
}).String()),
|
||||
)
|
||||
s3Results.Add(
|
||||
"something else failed",
|
||||
defsecTypes.NewRemoteMetadata((arn.ARN{
|
||||
Partition: "aws",
|
||||
Service: "s3",
|
||||
Region: "us-east-1",
|
||||
AccountID: "1234567890",
|
||||
Resource: "bucket2",
|
||||
}).String()),
|
||||
)
|
||||
s3Results.Add(
|
||||
"something else failed again",
|
||||
defsecTypes.NewRemoteMetadata((arn.ARN{
|
||||
Partition: "aws",
|
||||
Service: "s3",
|
||||
Region: "us-east-1",
|
||||
AccountID: "1234567890",
|
||||
Resource: "bucket2",
|
||||
}).String()),
|
||||
)
|
||||
s3Results.AddPassed(
|
||||
defsecTypes.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",
|
||||
defsecTypes.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...)
|
||||
}
|
||||
@@ -6,12 +6,17 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
awsScanner "github.com/aquasecurity/defsec/pkg/scanners/cloud/aws"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
||||
awscommands "github.com/aquasecurity/trivy/pkg/cloud/aws/commands"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/server"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
|
||||
@@ -81,6 +86,7 @@ func NewApp(version string) *cobra.Command {
|
||||
NewKubernetesCommand(globalFlags),
|
||||
NewSBOMCommand(globalFlags),
|
||||
NewVersionCommand(globalFlags),
|
||||
NewAWSCommand(globalFlags),
|
||||
)
|
||||
rootCmd.AddCommand(loadPluginCommands()...)
|
||||
|
||||
@@ -787,6 +793,66 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewAWSCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
|
||||
awsFlags := &flag.Flags{
|
||||
AWSFlagGroup: flag.NewAWSFlagGroup(),
|
||||
CloudFlagGroup: flag.NewCloudFlagGroup(),
|
||||
MisconfFlagGroup: flag.NewMisconfFlagGroup(),
|
||||
ReportFlagGroup: flag.NewReportFlagGroup(),
|
||||
}
|
||||
|
||||
services := awsScanner.AllSupportedServices()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "aws [flags]",
|
||||
Aliases: []string{},
|
||||
Args: cobra.ExactArgs(0),
|
||||
Short: "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(cmd.Version, args, globalFlags, outputWriter)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
if opts.Timeout < time.Hour {
|
||||
opts.Timeout = time.Hour
|
||||
log.Logger.Debug("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,
|
||||
}
|
||||
cmd.SetFlagErrorFunc(flagErrorFunc)
|
||||
awsFlags.AddFlags(cmd)
|
||||
cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, awsFlags.Usages(cmd)))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
reportFlagGroup := flag.NewReportFlagGroup()
|
||||
reportFlagGroup.DependencyTree = nil // disable '--dependency-tree'
|
||||
|
||||
@@ -533,7 +533,7 @@ func scan(ctx context.Context, opts flag.Options, initializeScanner InitializeSc
|
||||
|
||||
report, err := s.ScanArtifact(ctx, scanOptions)
|
||||
if err != nil {
|
||||
return types.Report{}, xerrors.Errorf("image scan failed: %w", err)
|
||||
return types.Report{}, xerrors.Errorf("scan failed: %w", err)
|
||||
}
|
||||
return report, nil
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ const (
|
||||
ArtifactFilesystem ArtifactType = "filesystem"
|
||||
ArtifactRemoteRepository ArtifactType = "repository"
|
||||
ArtifactCycloneDX ArtifactType = "cyclonedx"
|
||||
ArtifactAWSAccount ArtifactType = "aws_account"
|
||||
)
|
||||
|
||||
// ArtifactReference represents a reference of container image, local filesystem and repository
|
||||
|
||||
@@ -38,6 +38,7 @@ const (
|
||||
Ansible = "ansible"
|
||||
Helm = "helm"
|
||||
Rbac = "rbac"
|
||||
Cloud = "cloud"
|
||||
|
||||
// Licensing
|
||||
License = "license"
|
||||
|
||||
78
pkg/flag/aws_flags.go
Normal file
78
pkg/flag/aws_flags.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package flag
|
||||
|
||||
var (
|
||||
awsRegionFlag = Flag{
|
||||
Name: "region",
|
||||
ConfigName: "cloud.aws.region",
|
||||
Value: "",
|
||||
Usage: "AWS Region to scan",
|
||||
}
|
||||
awsEndpointFlag = Flag{
|
||||
Name: "endpoint",
|
||||
ConfigName: "cloud.aws.endpoint",
|
||||
Value: "",
|
||||
Usage: "AWS Endpoint override",
|
||||
}
|
||||
awsServiceFlag = Flag{
|
||||
Name: "service",
|
||||
ConfigName: "cloud.aws.service",
|
||||
Value: []string{},
|
||||
Usage: "Only scan AWS Service(s) specified with this flag. Can specify multiple services using --service A --service B etc.",
|
||||
}
|
||||
awsAccountFlag = Flag{
|
||||
Name: "account",
|
||||
ConfigName: "cloud.aws.account",
|
||||
Value: "",
|
||||
Usage: "The AWS account to scan. It's useful to specify this when reviewing cached results for multipel accounts.",
|
||||
}
|
||||
awsARNFlag = Flag{
|
||||
Name: "arn",
|
||||
ConfigName: "cloud.aws.arn",
|
||||
Value: "",
|
||||
Usage: "The AWS ARN to show results for. Useful to filter results once a scan is cached.",
|
||||
}
|
||||
)
|
||||
|
||||
type AWSFlagGroup struct {
|
||||
Region *Flag
|
||||
Endpoint *Flag
|
||||
Services *Flag
|
||||
Account *Flag
|
||||
ARN *Flag
|
||||
}
|
||||
|
||||
type AWSOptions struct {
|
||||
Region string
|
||||
Endpoint string
|
||||
Services []string
|
||||
Account string
|
||||
ARN string
|
||||
}
|
||||
|
||||
func NewAWSFlagGroup() *AWSFlagGroup {
|
||||
return &AWSFlagGroup{
|
||||
Region: &awsRegionFlag,
|
||||
Endpoint: &awsEndpointFlag,
|
||||
Services: &awsServiceFlag,
|
||||
Account: &awsAccountFlag,
|
||||
ARN: &awsARNFlag,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *AWSFlagGroup) Name() string {
|
||||
return "AWS"
|
||||
}
|
||||
|
||||
func (f *AWSFlagGroup) Flags() []*Flag {
|
||||
return []*Flag{f.Region, f.Endpoint, f.Services, f.Account, f.ARN}
|
||||
}
|
||||
|
||||
func (f *AWSFlagGroup) ToOptions() AWSOptions {
|
||||
return AWSOptions{
|
||||
Region: getString(f.Region),
|
||||
Endpoint: getString(f.Endpoint),
|
||||
Services: getStringSlice(f.Services),
|
||||
Account: getString(f.Account),
|
||||
ARN: getString(f.ARN),
|
||||
}
|
||||
}
|
||||
50
pkg/flag/cloud_flags.go
Normal file
50
pkg/flag/cloud_flags.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package flag
|
||||
|
||||
import "time"
|
||||
|
||||
var (
|
||||
cloudUpdateCacheFlag = Flag{
|
||||
Name: "update-cache",
|
||||
ConfigName: "cloud.update-cache",
|
||||
Value: false,
|
||||
Usage: "Update the cache for the applicable cloud provider instead of using cached results.",
|
||||
}
|
||||
cloudMaxCacheAgeFlag = Flag{
|
||||
Name: "max-cache-age",
|
||||
ConfigName: "cloud.max-cache-age",
|
||||
Value: 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.",
|
||||
}
|
||||
)
|
||||
|
||||
type CloudFlagGroup struct {
|
||||
UpdateCache *Flag
|
||||
MaxCacheAge *Flag
|
||||
}
|
||||
|
||||
type CloudOptions struct {
|
||||
MaxCacheAge time.Duration
|
||||
UpdateCache bool
|
||||
}
|
||||
|
||||
func NewCloudFlagGroup() *CloudFlagGroup {
|
||||
return &CloudFlagGroup{
|
||||
UpdateCache: &cloudUpdateCacheFlag,
|
||||
MaxCacheAge: &cloudMaxCacheAgeFlag,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *CloudFlagGroup) Name() string {
|
||||
return "Cloud"
|
||||
}
|
||||
|
||||
func (f *CloudFlagGroup) Flags() []*Flag {
|
||||
return []*Flag{f.UpdateCache, f.MaxCacheAge}
|
||||
}
|
||||
|
||||
func (f *CloudFlagGroup) ToOptions() CloudOptions {
|
||||
return CloudOptions{
|
||||
UpdateCache: getBool(f.UpdateCache),
|
||||
MaxCacheAge: getDuration(f.MaxCacheAge),
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,9 @@ type FlagGroup interface {
|
||||
}
|
||||
|
||||
type Flags struct {
|
||||
AWSFlagGroup *AWSFlagGroup
|
||||
CacheFlagGroup *CacheFlagGroup
|
||||
CloudFlagGroup *CloudFlagGroup
|
||||
DBFlagGroup *DBFlagGroup
|
||||
ImageFlagGroup *ImageFlagGroup
|
||||
K8sFlagGroup *K8sFlagGroup
|
||||
@@ -64,7 +66,9 @@ type Flags struct {
|
||||
// Options holds all the runtime configuration
|
||||
type Options struct {
|
||||
GlobalOptions
|
||||
AWSOptions
|
||||
CacheOptions
|
||||
CloudOptions
|
||||
DBOptions
|
||||
ImageOptions
|
||||
K8sOptions
|
||||
@@ -221,6 +225,12 @@ func (f *Flags) groups() []FlagGroup {
|
||||
if f.LicenseFlagGroup != nil {
|
||||
groups = append(groups, f.LicenseFlagGroup)
|
||||
}
|
||||
if f.CloudFlagGroup != nil {
|
||||
groups = append(groups, f.CloudFlagGroup)
|
||||
}
|
||||
if f.AWSFlagGroup != nil {
|
||||
groups = append(groups, f.AWSFlagGroup)
|
||||
}
|
||||
if f.K8sFlagGroup != nil {
|
||||
groups = append(groups, f.K8sFlagGroup)
|
||||
}
|
||||
@@ -279,6 +289,7 @@ func (f *Flags) Bind(cmd *cobra.Command) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint: gocyclo
|
||||
func (f *Flags) ToOptions(appVersion string, args []string, globalFlags *GlobalFlagGroup, output io.Writer) (Options, error) {
|
||||
var err error
|
||||
opts := Options{
|
||||
@@ -286,6 +297,14 @@ func (f *Flags) ToOptions(appVersion string, args []string, globalFlags *GlobalF
|
||||
GlobalOptions: globalFlags.ToOptions(),
|
||||
}
|
||||
|
||||
if f.AWSFlagGroup != nil {
|
||||
opts.AWSOptions = f.AWSFlagGroup.ToOptions()
|
||||
}
|
||||
|
||||
if f.CloudFlagGroup != nil {
|
||||
opts.CloudOptions = f.CloudFlagGroup.ToOptions()
|
||||
}
|
||||
|
||||
if f.CacheFlagGroup != nil {
|
||||
opts.CacheOptions, err = f.CacheFlagGroup.ToOptions()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user