diff --git a/docs/docs/references/configuration/cli/trivy_aws.md b/docs/docs/references/configuration/cli/trivy_aws.md index 739eaa6517..9b9b0cf66b 100644 --- a/docs/docs/references/configuration/cli/trivy_aws.md +++ b/docs/docs/references/configuration/cli/trivy_aws.md @@ -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 diff --git a/pkg/cloud/aws/commands/run.go b/pkg/cloud/aws/commands/run.go index ee9ed09fd3..3d4317f38b 100644 --- a/pkg/cloud/aws/commands/run.go +++ b/pkg/cloud/aws/commands/run.go @@ -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()) diff --git a/pkg/cloud/aws/commands/run_test.go b/pkg/cloud/aws/commands/run_test.go index e6da7a28f5..3772b698cf 100644 --- a/pkg/cloud/aws/commands/run_test.go +++ b/pkg/cloud/aws/commands/run_test.go @@ -17,69 +17,7 @@ import ( "github.com/stretchr/testify/require" ) -func Test_Run(t *testing.T) { - - regoDir := t.TempDir() - - tests := []struct { - name string - options flag.Options - want string - expectErr bool - cacheContent string - regoPolicy string - }{ - { - name: "fail without region", - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, - }, - want: "", - expectErr: true, - }, - { - name: "fail without creds", - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - }, - }, - want: "", - expectErr: true, - }, - { - name: "try to call aws if cache is expired", - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - Services: []string{"s3"}, - Account: "12345678", - }, - CloudOptions: flag.CloudOptions{ - MaxCacheAge: time.Minute, - }, - }, - cacheContent: exampleS3Cache, - expectErr: true, - }, - { - name: "succeed with cached infra", - options: flag.Options{ - RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - Services: []string{"s3"}, - Account: "12345678", - }, - CloudOptions: flag.CloudOptions{ - MaxCacheAge: time.Hour * 24 * 365 * 100, - }, - MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, - }, - cacheContent: exampleS3Cache, - want: `{ +const expectedS3ScanResult = `{ "ArtifactName": "12345678", "ArtifactType": "aws_account", "Metadata": { @@ -348,53 +286,8 @@ func Test_Run(t *testing.T) { } ] } -`, - }, - { - name: "custom rego rule with passed results", - options: flag.Options{ - AWSOptions: flag.AWSOptions{ - Region: "us-east-1", - Services: []string{"s3"}, - Account: "12345678", - }, - CloudOptions: flag.CloudOptions{ - MaxCacheAge: time.Hour * 24 * 365 * 100, - }, - RegoOptions: flag.RegoOptions{ - Trace: true, - PolicyPaths: []string{ - filepath.Join(regoDir, "policies"), - }, - PolicyNamespaces: []string{ - "user", - }, - SkipPolicyUpdate: true, - }, - MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, - }, - regoPolicy: `# METADATA -# title: No example buckets -# description: Buckets should not be named with "example" in the name -# scope: package -# schemas: -# - input: schema["input"] -# custom: -# severity: LOW -# service: s3 -# input: -# selector: -# - type: cloud -package user.whatever - -deny[res] { - bucket := input.aws.s3.buckets[_] - contains(bucket.name.value, "example") - res := result.new("example bucket detected", bucket.name) -} -`, - cacheContent: exampleS3Cache, - want: `{ +` +const expectedCustomScanResult = `{ "ArtifactName": "12345678", "ArtifactType": "aws_account", "Metadata": { @@ -685,7 +578,497 @@ deny[res] { } ] } +` +const expectedS3AndCloudTrailResult = `{ + "ArtifactName": "123456789", + "ArtifactType": "aws_account", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "Class": "config", + "Type": "cloud", + "MisconfSummary": { + "Successes": 1, + "Failures": 3, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "AWS", + "ID": "AVD-AWS-0014", + "AVDID": "AVD-AWS-0014", + "Title": "Cloudtrail should be enabled in all regions regardless of where your AWS resources are generally homed", + "Description": "When creating Cloudtrail in the AWS Management Console the trail is configured by default to be multi-region, this isn't the case with the Terraform resource. Cloudtrail should cover the full AWS account to ensure you can track changes in regions you are not actively operting in.", + "Resolution": "Enable Cloudtrail in all regions", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0014", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0014" + ], + "Status": "PASS", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "Provider": "aws", + "Service": "cloudtrail", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0015", + "AVDID": "AVD-AWS-0015", + "Title": "Cloudtrail should be encrypted at rest to secure access to sensitive trail data", + "Description": "Cloudtrail logs should be encrypted at rest to secure the sensitive data. Cloudtrail logs record all activity that occurs in the the account through API calls and would be one of the first places to look when reacting to a breach.", + "Message": "Trail is not encrypted.", + "Resolution": "Enable encryption at rest", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0015", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0015" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "Provider": "aws", + "Service": "cloudtrail", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0016", + "AVDID": "AVD-AWS-0016", + "Title": "Cloudtrail log validation should be enabled to prevent tampering of log data", + "Description": "Log validation should be activated on Cloudtrail logs to prevent the tampering of the underlying data in the S3 bucket. It is feasible that a rogue actor compromising an AWS account might want to modify the log data to remove trace of their actions.", + "Message": "Trail does not have log validation enabled.", + "Resolution": "Turn on log validation for Cloudtrail", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0016", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0016" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "Provider": "aws", + "Service": "cloudtrail", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0162", + "AVDID": "AVD-AWS-0162", + "Title": "CloudTrail logs should be stored in S3 and also sent to CloudWatch Logs", + "Description": "CloudTrail is a web service that records AWS API calls made in a given account. The recorded information includes the identity of the API caller, the time of the API call, the source IP address of the API caller, the request parameters, and the response elements returned by the AWS service.\n\nCloudTrail uses Amazon S3 for log file storage and delivery, so log files are stored durably. In addition to capturing CloudTrail logs in a specified Amazon S3 bucket for long-term analysis, you can perform real-time analysis by configuring CloudTrail to send logs to CloudWatch Logs.\n\nFor a trail that is enabled in all Regions in an account, CloudTrail sends log files from all those Regions to a CloudWatch Logs log group.", + "Message": "Trail does not have CloudWatch logging configured", + "Resolution": "Enable logging to CloudWatch", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0162", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0162" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:cloudtrail:us-east-1:12345678:trail/management-events", + "Provider": "aws", + "Service": "cloudtrail", + "Code": { + "Lines": null + } + } + } + ] + }, + { + "Target": "arn:aws:s3:::examplebucket", + "Class": "config", + "Type": "cloud", + "MisconfSummary": { + "Successes": 1, + "Failures": 9, + "Exceptions": 0 + }, + "Misconfigurations": [ + { + "Type": "AWS", + "ID": "AVD-AWS-0086", + "AVDID": "AVD-AWS-0086", + "Title": "S3 Access block should block public ACL", + "Description": "S3 buckets should block public ACLs on buckets and any objects they contain. By blocking, PUTs with fail if the object has any public ACL a.", + "Message": "No public access block so not blocking public acls", + "Resolution": "Enable blocking any PUT calls with a public ACL specified", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0086", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0086" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0087", + "AVDID": "AVD-AWS-0087", + "Title": "S3 Access block should block public policy", + "Description": "S3 bucket policy should have block public policy to prevent users from putting a policy that enable public access.", + "Message": "No public access block so not blocking public policies", + "Resolution": "Prevent policies that allow public access being PUT", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0087", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0087" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0088", + "AVDID": "AVD-AWS-0088", + "Title": "Unencrypted S3 bucket.", + "Description": "S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.", + "Message": "Bucket does not have encryption enabled", + "Resolution": "Configure bucket encryption", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0088", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0088" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0089", + "AVDID": "AVD-AWS-0089", + "Title": "S3 Bucket does not have logging enabled.", + "Description": "Buckets should have logging enabled so that access can be audited.", + "Message": "Bucket does not have logging enabled", + "Resolution": "Add a logging block to the resource to enable access logging", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0089", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0089" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0090", + "AVDID": "AVD-AWS-0090", + "Title": "S3 Data should be versioned", + "Description": "Versioning in Amazon S3 is a means of keeping multiple variants of an object in the same bucket. \nYou can use the S3 Versioning feature to preserve, retrieve, and restore every version of every object stored in your buckets. \nWith versioning you can recover more easily from both unintended user actions and application failures.", + "Message": "Bucket does not have versioning enabled", + "Resolution": "Enable versioning to protect against accidental/malicious removal or modification", + "Severity": "MEDIUM", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0090", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0090" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0132", + "AVDID": "AVD-AWS-0132", + "Title": "S3 encryption should use Customer Managed Keys", + "Description": "Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.", + "Message": "Bucket does not encrypt data with a customer managed key.", + "Resolution": "Enable encryption using customer managed keys", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0132", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0132" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0091", + "AVDID": "AVD-AWS-0091", + "Title": "S3 Access Block should Ignore Public Acl", + "Description": "S3 buckets should ignore public ACLs on buckets and any objects they contain. By ignoring rather than blocking, PUT calls with public ACLs will still be applied but the ACL will be ignored.", + "Message": "No public access block so not ignoring public acls", + "Resolution": "Enable ignoring the application of public ACLs in PUT calls", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0091", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0091" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0092", + "AVDID": "AVD-AWS-0092", + "Title": "S3 Buckets not publicly accessible through ACL.", + "Description": "Buckets should not have ACLs that allow public access", + "Resolution": "Don't use canned ACLs or switch to private acl", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0092", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0092" + ], + "Status": "PASS", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0093", + "AVDID": "AVD-AWS-0093", + "Title": "S3 Access block should restrict public bucket to limit access", + "Description": "S3 buckets should restrict public policies for the bucket. By enabling, the restrict_public_buckets, only the bucket owner and AWS Services can access if it has a public policy.", + "Message": "No public access block so not restricting public buckets", + "Resolution": "Limit the access to public buckets to only the owner or AWS Services (eg; CloudFront)", + "Severity": "HIGH", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0093", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0093" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + }, + { + "Type": "AWS", + "ID": "AVD-AWS-0094", + "AVDID": "AVD-AWS-0094", + "Title": "S3 buckets should each define an aws_s3_bucket_public_access_block", + "Description": "The \"block public access\" settings in S3 override individual policies that apply to a given bucket, meaning that all public access can be controlled in one central types for that bucket. It is therefore good practice to define these settings for each bucket in order to clearly define the public access that can be allowed for it.", + "Message": "Bucket does not have a corresponding public access block.", + "Resolution": "Define a aws_s3_bucket_public_access_block for the given bucket to control public access policies", + "Severity": "LOW", + "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-0094", + "References": [ + "https://avd.aquasec.com/misconfig/avd-aws-0094" + ], + "Status": "FAIL", + "Layer": {}, + "CauseMetadata": { + "Resource": "arn:aws:s3:::examplebucket", + "Provider": "aws", + "Service": "s3", + "Code": { + "Lines": null + } + } + } + ] + } + ] +} +` + +func Test_Run(t *testing.T) { + + regoDir := t.TempDir() + + tests := []struct { + name string + options flag.Options + want string + expectErr bool + cacheContent string + regoPolicy string + allServices []string + }{ + { + name: "fail without region", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + }, + want: "", + expectErr: true, + }, + { + name: "fail without creds", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + }, + }, + want: "", + expectErr: true, + }, + { + name: "try to call aws if cache is expired", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Services: []string{"s3"}, + Account: "12345678", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Minute, + }, + }, + cacheContent: "testdata/s3onlycache.json", + expectErr: true, + }, + { + name: "succeed with cached infra", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Services: []string{"s3"}, + Account: "12345678", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + cacheContent: "testdata/s3onlycache.json", + allServices: []string{"s3"}, + want: expectedS3ScanResult, + }, + { + name: "custom rego rule with passed results", + options: flag.Options{ + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Services: []string{"s3"}, + Account: "12345678", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + RegoOptions: flag.RegoOptions{ + Trace: true, + PolicyPaths: []string{ + filepath.Join(regoDir, "policies"), + }, + PolicyNamespaces: []string{ + "user", + }, + SkipPolicyUpdate: true, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + regoPolicy: `# METADATA +# title: No example buckets +# description: Buckets should not be named with "example" in the name +# scope: package +# schemas: +# - input: schema["input"] +# custom: +# severity: LOW +# service: s3 +# input: +# selector: +# - type: cloud +package user.whatever + +deny[res] { + bucket := input.aws.s3.buckets[_] + contains(bucket.name.value, "example") + res := result.new("example bucket detected", bucket.name) +} `, + cacheContent: "testdata/s3onlycache.json", + allServices: []string{"s3"}, + want: expectedCustomScanResult, }, { name: "compliance report summary", @@ -724,7 +1107,8 @@ deny[res] { }, RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, }, - cacheContent: exampleS3Cache, + cacheContent: "testdata/s3onlycache.json", + allServices: []string{"s3"}, want: ` Summary Report for compliance: my-custom-spec ┌─────┬──────────┬───────────────────────┬────────┬────────┐ @@ -734,9 +1118,109 @@ Summary Report for compliance: my-custom-spec └─────┴──────────┴───────────────────────┴────────┴────────┘ `, }, + { + name: "scan an unsupported service", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Account: "123456789", + Services: []string{"theultimateservice"}, + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + cacheContent: "testdata/s3onlycache.json", + expectErr: true, + }, + { + name: "scan every service", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Account: "123456789", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + cacheContent: "testdata/s3andcloudtrailcache.json", + allServices: []string{"s3", "cloudtrail"}, + want: expectedS3AndCloudTrailResult, + }, + { + name: "skip certain services and include specific services", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Services: []string{"s3"}, + SkipServices: []string{"cloudtrail"}, + Account: "123456789", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + cacheContent: "testdata/s3andcloudtrailcache.json", + allServices: []string{"s3", "cloudtrail"}, + // we skip cloudtrail but still expect results from it as it is cached + want: expectedS3AndCloudTrailResult, + }, + { + name: "only skip certain services but scan the rest", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + SkipServices: []string{"cloudtrail", "iam"}, + Account: "12345678", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + allServices: []string{"s3", "cloudtrail", "iam"}, + cacheContent: "testdata/s3onlycache.json", + want: expectedS3ScanResult, + }, + { + name: "fail - service specified to both include and exclude", + options: flag.Options{ + RegoOptions: flag.RegoOptions{SkipPolicyUpdate: true}, + AWSOptions: flag.AWSOptions{ + Region: "us-east-1", + Services: []string{"s3"}, + SkipServices: []string{"s3"}, + Account: "123456789", + }, + CloudOptions: flag.CloudOptions{ + MaxCacheAge: time.Hour * 24 * 365 * 100, + }, + MisconfOptions: flag.MisconfOptions{IncludeNonFailures: true}, + }, + cacheContent: "testdata/s3andcloudtrailcache.json", + expectErr: true, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { + if test.allServices != nil { + oldAllSupportedServicesFunc := allSupportedServicesFunc + allSupportedServicesFunc = func() []string { + return test.allServices + } + defer func() { + allSupportedServicesFunc = oldAllSupportedServicesFunc + }() + } + buffer := new(bytes.Buffer) test.options.Output = buffer test.options.Debug = true @@ -762,7 +1246,11 @@ Summary Report for compliance: my-custom-spec test.options.CacheDir = cacheRoot cacheFile := filepath.Join(cacheRoot, "cloud", "aws", test.options.Account, test.options.Region, "data.json") require.NoError(t, os.MkdirAll(filepath.Dir(cacheFile), 0700)) - require.NoError(t, os.WriteFile(cacheFile, []byte(test.cacheContent), 0600)) + + cacheData, err := os.ReadFile(test.cacheContent) + require.NoError(t, err, test.name) + + require.NoError(t, os.WriteFile(cacheFile, []byte(cacheData), 0600)) } err := Run(context.Background(), test.options) @@ -775,267 +1263,3 @@ Summary Report for compliance: my-custom-spec }) } } - -const exampleS3Cache = `{ - "schema_version":2, - "state":{ - "AWS":{ - "S3":{ - "Buckets":[ - { - "Metadata":{ - "default":false, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "Name":{ - "metadata":{ - "default":false, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "value":"examplebucket" - }, - "PublicAccessBlock":null, - "BucketPolicies":null, - "Encryption":{ - "Metadata":{ - "default":false, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "Enabled":{ - "metadata":{ - "default":true, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "value":false - }, - "Algorithm":{ - "metadata":{ - "default":true, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "value":"" - }, - "KMSKeyId":{ - "metadata":{ - "default":true, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "value":"" - } - }, - "Versioning":{ - "Metadata":{ - "default":false, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "Enabled":{ - "metadata":{ - "default":true, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "value":false - }, - "MFADelete":{ - "metadata":{ - "default":false, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "value":false - } - }, - "Logging":{ - "Metadata":{ - "default":false, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "Enabled":{ - "metadata":{ - "default":true, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "value":false - }, - "TargetBucket":{ - "metadata":{ - "default":true, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "value":"" - } - }, - "ACL":{ - "metadata":{ - "default":false, - "explicit":false, - "managed":true, - "parent":null, - "range":{ - "endLine":0, - "filename":"arn:aws:s3:::examplebucket", - "fsKey":"", - "isLogicalSource":false, - "sourcePrefix":"remote", - "startLine":0 - }, - "ref":"arn:aws:s3:::examplebucket", - "unresolvable":false - }, - "value":"private" - } - } - ] - } - } - }, - "service_metadata":{ - "s3":{ - "name":"s3", - "updated": "2022-10-04T14:08:36.659817426+01:00" - } - }, - "updated": "2022-10-04T14:08:36.659817426+01:00" -}` diff --git a/pkg/cloud/aws/commands/testdata/s3andcloudtrailcache.json b/pkg/cloud/aws/commands/testdata/s3andcloudtrailcache.json new file mode 100644 index 0000000000..f9cfd2abce --- /dev/null +++ b/pkg/cloud/aws/commands/testdata/s3andcloudtrailcache.json @@ -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" +} diff --git a/pkg/cloud/aws/commands/testdata/s3onlycache.json b/pkg/cloud/aws/commands/testdata/s3onlycache.json new file mode 100644 index 0000000000..43a015aa9c --- /dev/null +++ b/pkg/cloud/aws/commands/testdata/s3onlycache.json @@ -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" +} diff --git a/pkg/flag/aws_flags.go b/pkg/flag/aws_flags.go index 278c759a55..43582685e0 100644 --- a/pkg/flag/aws_flags.go +++ b/pkg/flag/aws_flags.go @@ -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), } }