feat(misconf): Support skipping services (#4686)

* feat(misconf): Add support for `--skip-service` flag.

Fixes: https://github.com/aquasecurity/trivy/issues/4619
Signed-off-by: Simar <simar@linux.com>

* update docs

Signed-off-by: Simar <simar@linux.com>

* update go mod

* refactor processOptions to reduce cyclo complexity

Signed-off-by: Simar <simar@linux.com>

* fix a bug with multiple skip services

Signed-off-by: Simar <simar@linux.com>

* refactor tests

Signed-off-by: Simar <simar@linux.com>

* use x/slice and x/xerrors

Signed-off-by: Simar <simar@linux.com>

* go mod tidy

* lint

---------

Signed-off-by: Simar <simar@linux.com>
This commit is contained in:
simar7
2023-06-26 05:11:59 -06:00
committed by GitHub
parent 46e784c8a9
commit 5d73b47dbc
6 changed files with 1369 additions and 410 deletions

View File

@@ -92,6 +92,7 @@ trivy aws [flags]
--service strings Only scan AWS Service(s) specified with this flag. Can specify multiple services using --service A --service B etc.
-s, --severity string severities of security issues to be displayed (comma separated) (default "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL")
--skip-policy-update skip fetching rego policy updates
--skip-service strings Skip selected AWS Service(s) specified with this flag. Can specify multiple services using --skip-service A --skip-service B etc.
-t, --template string output template
--tf-vars strings specify paths to override the Terraform tfvars files
--trace enable more verbose trace output for custom queries

View File

@@ -3,9 +3,10 @@ package commands
import (
"context"
"errors"
"fmt"
"strings"
"golang.org/x/exp/slices"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sts"
"golang.org/x/xerrors"
@@ -22,6 +23,8 @@ import (
"github.com/aquasecurity/trivy/pkg/types"
)
var allSupportedServicesFunc = awsScanner.AllSupportedServices
func getAccountIDAndRegion(ctx context.Context, region string) (string, string, error) {
log.Logger.Debug("Looking for AWS credentials provider...")
@@ -38,16 +41,31 @@ func getAccountIDAndRegion(ctx context.Context, region string) (string, string,
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)
return "", "", xerrors.Errorf("failed to discover AWS caller identity: %w", err)
}
if result.Account == nil {
return "", "", fmt.Errorf("missing account id for aws account")
return "", "", xerrors.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 validateServicesInput(services, skipServices []string) error {
for _, s := range services {
for _, ss := range skipServices {
if s == ss {
return xerrors.Errorf("service: %s specified to both skip and include", s)
}
}
}
return nil
}
func processOptions(ctx context.Context, opt *flag.Options) error {
if err := validateServicesInput(opt.Services, opt.SkipServices); err != nil {
return err
}
// support comma separated services too
var splitServices []string
for _, service := range opt.Services {
@@ -55,8 +73,14 @@ func processOptions(ctx context.Context, opt *flag.Options) error {
}
opt.Services = splitServices
var splitSkipServices []string
for _, skipService := range opt.SkipServices {
splitSkipServices = append(splitSkipServices, strings.Split(skipService, ",")...)
}
opt.SkipServices = splitSkipServices
if len(opt.Services) != 1 && opt.ARN != "" {
return fmt.Errorf("you must specify the single --service which the --arn relates to")
return xerrors.Errorf("you must specify the single --service which the --arn relates to")
}
if opt.Account == "" || opt.Region == "" {
@@ -67,14 +91,34 @@ func processOptions(ctx context.Context, opt *flag.Options) error {
}
}
if len(opt.Services) == 0 {
err := filterServices(opt)
if err != nil {
return err
}
log.Logger.Debug("scanning services: ", opt.Services)
return nil
}
func filterServices(opt *flag.Options) error {
if len(opt.Services) == 0 && len(opt.SkipServices) == 0 {
log.Logger.Debug("No service(s) specified, scanning all services...")
opt.Services = awsScanner.AllSupportedServices()
} else {
opt.Services = allSupportedServicesFunc()
} else if len(opt.SkipServices) > 0 {
log.Logger.Debug("excluding services: ", opt.SkipServices)
for _, s := range allSupportedServicesFunc() {
if slices.Contains(opt.SkipServices, s) {
continue
}
if !slices.Contains(opt.Services, s) {
opt.Services = append(opt.Services, s)
}
}
} else if len(opt.Services) > 0 {
log.Logger.Debugf("Specific services were requested: [%s]...", strings.Join(opt.Services, ", "))
for _, service := range opt.Services {
var found bool
supported := awsScanner.AllSupportedServices()
supported := allSupportedServicesFunc()
for _, allowed := range supported {
if allowed == service {
found = true
@@ -82,11 +126,10 @@ func processOptions(ctx context.Context, opt *flag.Options) error {
}
}
if !found {
return fmt.Errorf("service '%s' is not currently supported - supported services are: %s", service, strings.Join(supported, ", "))
return xerrors.Errorf("service '%s' is not currently supported - supported services are: %s", service, strings.Join(supported, ", "))
}
}
}
return nil
}
@@ -96,7 +139,7 @@ func Run(ctx context.Context, opt flag.Options) error {
defer cancel()
if err := log.InitLogger(opt.Debug, false); err != nil {
return fmt.Errorf("logger error: %w", err)
return xerrors.Errorf("logger error: %w", err)
}
var err error
@@ -118,7 +161,7 @@ func Run(ctx context.Context, opt flag.Options) error {
log.Logger.Warnf("Adapter error: %s", e)
}
} else {
return fmt.Errorf("aws scan error: %w", err)
return xerrors.Errorf("aws scan error: %w", err)
}
}
@@ -149,7 +192,7 @@ func Run(ctx context.Context, opt flag.Options) error {
r := report.New(cloud.ProviderAWS, opt.Account, opt.Region, res, opt.Services)
if err := report.Write(r, opt, cached); err != nil {
return fmt.Errorf("unable to write results: %w", err)
return xerrors.Errorf("unable to write results: %w", err)
}
operation.Exit(opt, r.Failed())

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,420 @@
{
"schema_version": 2,
"state": {
"AWS": {
"S3": {
"Buckets": [{
"Metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"Name": {
"metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": "examplebucket"
},
"PublicAccessBlock": null,
"BucketPolicies": null,
"Encryption": {
"Metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"Enabled": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": false
},
"Algorithm": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": ""
},
"KMSKeyId": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": ""
}
},
"Versioning": {
"Metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"Enabled": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": false
},
"MFADelete": {
"metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": false
}
},
"Logging": {
"Metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"Enabled": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": false
},
"TargetBucket": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": ""
}
},
"ACL": {
"metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": "private"
}
}]
},
"CloudTrail": {
"Trails": [{
"Metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"unresolvable": false
},
"Name": {
"metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"unresolvable": false
},
"value": "management-events"
},
"EnableLogFileValidation": {
"metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"unresolvable": false
},
"value": false
},
"IsMultiRegion": {
"metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"unresolvable": false
},
"value": true
},
"KMSKeyID": {
"metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"unresolvable": false
},
"value": ""
},
"CloudWatchLogsLogGroupArn": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"unresolvable": false
},
"value": ""
},
"IsLogging": {
"metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"unresolvable": false
},
"value": true
},
"BucketName": {
"metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events",
"unresolvable": false
},
"value": "aws-cloudtrail-logs-12345678-d0a47f2f"
},
"EventSelectors": null
}]
}
}
},
"service_metadata": {
"s3": {
"name": "s3",
"updated": "2022-10-04T14:08:36.659817426+01:00"
},
"cloudtrail": {
"name": "cloudtrail",
"updated": "2022-10-04T14:08:36.659817426+01:00"
}
},
"updated": "2022-10-04T14:08:36.659817426+01:00"
}

View File

@@ -0,0 +1,261 @@
{
"schema_version": 2,
"state": {
"AWS": {
"S3": {
"Buckets": [{
"Metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"Name": {
"metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": "examplebucket"
},
"PublicAccessBlock": null,
"BucketPolicies": null,
"Encryption": {
"Metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"Enabled": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": false
},
"Algorithm": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": ""
},
"KMSKeyId": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": ""
}
},
"Versioning": {
"Metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"Enabled": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": false
},
"MFADelete": {
"metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": false
}
},
"Logging": {
"Metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"Enabled": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": false
},
"TargetBucket": {
"metadata": {
"default": true,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": ""
}
},
"ACL": {
"metadata": {
"default": false,
"explicit": false,
"managed": true,
"parent": null,
"range": {
"endLine": 0,
"filename": "arn:aws:s3:::examplebucket",
"fsKey": "",
"isLogicalSource": false,
"sourcePrefix": "remote",
"startLine": 0
},
"ref": "arn:aws:s3:::examplebucket",
"unresolvable": false
},
"value": "private"
}
}]
}
}
},
"service_metadata": {
"s3": {
"name": "s3",
"updated": "2022-10-04T14:08:36.659817426+01:00"
}
},
"updated": "2022-10-04T14:08:36.659817426+01:00"
}

View File

@@ -19,6 +19,12 @@ var (
Value: []string{},
Usage: "Only scan AWS Service(s) specified with this flag. Can specify multiple services using --service A --service B etc.",
}
awsSkipServicesFlag = Flag{
Name: "skip-service",
ConfigName: "cloud.aws.skip-service",
Value: []string{},
Usage: "Skip selected AWS Service(s) specified with this flag. Can specify multiple services using --skip-service A --skip-service B etc.",
}
awsAccountFlag = Flag{
Name: "account",
ConfigName: "cloud.aws.account",
@@ -34,28 +40,31 @@ var (
)
type AWSFlagGroup struct {
Region *Flag
Endpoint *Flag
Services *Flag
Account *Flag
ARN *Flag
Region *Flag
Endpoint *Flag
Services *Flag
SkipServices *Flag
Account *Flag
ARN *Flag
}
type AWSOptions struct {
Region string
Endpoint string
Services []string
Account string
ARN string
Region string
Endpoint string
Services []string
SkipServices []string
Account string
ARN string
}
func NewAWSFlagGroup() *AWSFlagGroup {
return &AWSFlagGroup{
Region: &awsRegionFlag,
Endpoint: &awsEndpointFlag,
Services: &awsServiceFlag,
Account: &awsAccountFlag,
ARN: &awsARNFlag,
Region: &awsRegionFlag,
Endpoint: &awsEndpointFlag,
Services: &awsServiceFlag,
SkipServices: &awsSkipServicesFlag,
Account: &awsAccountFlag,
ARN: &awsARNFlag,
}
}
@@ -64,15 +73,16 @@ func (f *AWSFlagGroup) Name() string {
}
func (f *AWSFlagGroup) Flags() []*Flag {
return []*Flag{f.Region, f.Endpoint, f.Services, f.Account, f.ARN}
return []*Flag{f.Region, f.Endpoint, f.Services, f.SkipServices, 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),
Region: getString(f.Region),
Endpoint: getString(f.Endpoint),
Services: getStringSlice(f.Services),
SkipServices: getStringSlice(f.SkipServices),
Account: getString(f.Account),
ARN: getString(f.ARN),
}
}